From e82194a23a5e46808ce6d1029a3e3131f9731038 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Wed, 12 Mar 2025 22:12:19 +0800 Subject: [PATCH 01/55] integrated i18n into sicp repository --- i18n/controllers/gitComm.ts | 54 ++++++++ i18n/controllers/path.ts | 19 +++ i18n/controllers/translate.ts | 228 ++++++++++++++++++++++++++++++++ i18n/index.ts | 29 ++++ i18n/initializers/initialize.ts | 38 ++++++ i18n/package.json | 15 +++ package.json | 8 +- 7 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 i18n/controllers/gitComm.ts create mode 100644 i18n/controllers/path.ts create mode 100644 i18n/controllers/translate.ts create mode 100644 i18n/index.ts create mode 100644 i18n/initializers/initialize.ts create mode 100644 i18n/package.json diff --git a/i18n/controllers/gitComm.ts b/i18n/controllers/gitComm.ts new file mode 100644 index 000000000..73548cf05 --- /dev/null +++ b/i18n/controllers/gitComm.ts @@ -0,0 +1,54 @@ +import { Octokit } from "octokit"; +import dotenv from "dotenv"; +import fs from "fs"; +import path from "path"; +dotenv.config(); + +if (process.env.GITHUB_OWNER === undefined || process.env.GITHUB_REPO === undefined) { + throw Error("Please specify GITHUB_OWNER, GITHUB_REPO to pull EN XML from!"); +} + +// initialize GitHub API +const octokit = new Octokit(); + +async function getSource(filePath: string): Promise { + let toTranslate; + + try { + const result = await octokit.request( + "GET /repos/{owner}/{repo}/contents/{path}", + { + owner: process.env.GITHUB_OWNER!, + repo: process.env.GITHUB_REPO!, + path: filePath, + headers: { + accept: "application/vnd.github.raw+json", + }, + } + ); + + toTranslate = result.data; + const output_dir = path.join(import.meta.dirname, "../../ori"); + + // Ensure directory exists + const dir = path.dirname(path.join(output_dir, filePath)); + fs.mkdirSync(dir, { recursive: true }); + + const fullPath = path.resolve(path.join(output_dir, filePath)); + fs.writeFileSync(fullPath, toTranslate); + + console.log( + `Successfully retrieved ${filePath} from GitHub, retrieval status: ${result.status}` + ); + } catch (error) { + console.log( + `Error retrieving ${filePath} from GitHub.\n Status: ${error.status}.\n Rate limit remaining: ${error.response.headers["x-ratelimit-remaining"]}.\n Message: ${error.response.data.message}` + ); + } + + return toTranslate as string; +} + +async function commitTranslated() {} + +export { getSource, commitTranslated }; diff --git a/i18n/controllers/path.ts b/i18n/controllers/path.ts new file mode 100644 index 000000000..8ea58a449 --- /dev/null +++ b/i18n/controllers/path.ts @@ -0,0 +1,19 @@ +export default function PathGenerator(path: string) { + const pathArray: string[] = path.split("."); + let gitPath: string = ""; + const gitPathPrefix: string[] = ["chapter", "section", "subsection"]; + + let i = 0; + const len = pathArray.length; + + while (i <= len && i < 3) { + if (i === len) { + gitPath += `/${gitPathPrefix[i - 1]}${pathArray[i - 1]}`; + } else { + gitPath += `/${gitPathPrefix[i]}${pathArray[i]}`; + } + i++; + } + + return gitPath + ".xml"; +} diff --git a/i18n/controllers/translate.ts b/i18n/controllers/translate.ts new file mode 100644 index 000000000..bae3d0da2 --- /dev/null +++ b/i18n/controllers/translate.ts @@ -0,0 +1,228 @@ +import fs from "fs"; +import OpenAI from "openai"; +import path from "path"; +import createAssistant from "../initializers/initialize"; +import dotenv from "dotenv"; +import sax from "sax"; +import { Readable } from "stream"; +import { fileURLToPath } from "url"; + +dotenv.config(); + +if (process.env.AI_MODEL === undefined || process.env.API_KEY === undefined) { + throw Error("Please specify AI_MODEL and API_KEY!"); +} + +// initialize OpenAI API +const ai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.AI_BASEURL +}); + +// TODO: change the toTranslate to a file path, read the file and translate the content +async function translate(language: string, filePath: string) { + // Create a SAX parser in strict mode to split source into chunks. + const parser = (sax as any).createStream(true, { trim: false }); + + // const assistant = await createAssistant(language, ai); + const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; + const thread = await ai.beta.threads.create(); + let translated = ""; + + console.dir(thread); + // Variables to track current depth and segments. + let currentDepth = 0; + let currentSegment = ""; + const segments: [boolean, string][] = []; + + // In this context: + // - Depth 0: Before any element is opened. + // - Depth 1: The root element (). + // - Depth 2: Each direct child of the root that we want to capture. + let isRecording = false; + + parser.on("opentag", node => { + currentDepth++; + + // If we're at depth 2, this is the start of a new segment. + if (currentDepth === 2 || isRecording) { + isRecording = true; + currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; + } else { + segments.push([ + false, + `<${node.name}${formatAttributes(node.attributes)}>` + ]); + } + }); + + parser.on("text", text => { + if (isRecording) { + currentSegment += `${text}`; + } else { + segments.push([false, text]); + } + }); + + parser.on("cdata", cdata => { + if (isRecording) { + currentSegment += ``; + } + }); + + parser.on("closetag", tagName => { + if (isRecording) { + currentSegment += ``; + } + + if (currentDepth === 2) { + // We are closing a segment element. + segments.push([true, currentSegment]); + currentSegment = ""; + isRecording = false; + } + + if (currentDepth === 1) { + // We are closing the root element. + segments.push([false, ``]); + } + + currentDepth--; + }); + + parser.on("comment", comment => { + if (isRecording) { + currentSegment += ``; + } else { + segments.push([false, ``]); + } + }); + + parser.on("end", async () => { + for (const segment of segments) { + if (segment[0]) { + translated += await translateChunk(segment[1]); + } else { + translated += segment[1]; + } + } + console.log(`Done translating all segments.`); + const output_path = fileURLToPath( + import.meta.resolve("../../xml/translations" + filePath) + ); + + // Ensure directory exists + const dir = path.dirname(output_path); + fs.mkdirSync(dir, { recursive: true }); + + fs.writeFileSync(output_path, translated); + console.log(`Translation saved to ${output_path}`); + }); + + try { + // Pipe the XML file into the parser. + const input_dir = fileURLToPath( + import.meta.resolve("../../xml" + filePath) + ); + console.log(input_dir); + fs.createReadStream(input_dir).pipe(parser); + } catch (parseErr) { + console.error("Error parsing XML:", parseErr); + } + + async function translateChunk(chunk: string) { + // console.log("translating chunk: " + chunk); + // Create a SAX parser in strict mode for cleaning up translations. + const clean = (sax as any).createStream(true, { trim: false }); + + // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags + let currDepth = -1; + + clean.on("text", text => { + if (currDepth >= 1) { + translated += text; + } + }); + + clean.on("opentag", node => { + currDepth++; + if (node.name != "WRAPPER") { + translated += `<${node.name}${formatAttributes(node.attributes)}>`; + } + }); + + clean.on("closetag", tagName => { + if (tagName != "WRAPPER") { + translated += ``; + } + currDepth--; + }); + + clean.on("cdata", cdata => { + translated += ``; + }); + + clean.on("comment", comment => { + translated += ``; + }); + + let translated = ""; + + try { + await ai.beta.threads.messages.create(thread.id, { + role: "user", + content: `Translate this content to ${language}. + IMPORTANT: You MUST search the uploaded reference file for any technical terms and use EXACTLY the translations specified there. + If a term exists in the reference file, use that translation without deviation. + Do not modify XML tags, content of XML tags and structure. Do not say anything else. Only translate the content and return the xml as is. + Content to translate: + ${chunk}` + }); + const run = await ai.beta.threads.runs.createAndPoll(thread.id, { + assistant_id: assistant_id + }); + + const messages = await ai.beta.threads.messages.list(thread.id, { + run_id: run.id + }); + const message = messages.data.pop()!; + const messageContent = message.content[0]; + + if (messageContent.type !== "text") { + throw new Error( + `Unexpected message content type: ${messageContent.type}` + ); + } + + const text = messageContent.text; + // console.log(text.value); + + const safeText = escapeXML(text.value); + const textStream = Readable.from("" + safeText + ""); + + await new Promise((resolve, reject) => { + clean.once("end", resolve); + clean.once("error", reject); + textStream.pipe(clean); + }); + + return translated; + } catch (err) { + console.log(`Error occured while translating ${filePath}:\n ` + err); + } + } +} + +export default translate; + +// Helper function to format attributes into a string. +function formatAttributes(attrs) { + const attrStr = Object.entries(attrs) + .map(([key, val]) => `${key}="${val}"`) + .join(" "); + return attrStr ? " " + attrStr : ""; +} + +function escapeXML(str: string): string { + return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); +} \ No newline at end of file diff --git a/i18n/index.ts b/i18n/index.ts new file mode 100644 index 000000000..87fb510b4 --- /dev/null +++ b/i18n/index.ts @@ -0,0 +1,29 @@ +import { getSource } from "./controllers/gitComm.ts"; +import PathGenerator from "./controllers/path.ts"; +import translate from "./controllers/translate.ts"; + +export default async function fancyName(path: string) { + const startTime = new Date().getTime(); + + const fullPath = PathGenerator(path); + console.log("Translating: " + fullPath); + await translate("Chinese", fullPath); + + const elapsed = new Date().getTime() - startTime; + console.log(fullPath + " took " + elapsed / 1000.0 + " seconds"); +} + + + +await Promise.all([ + // fancyName("2") + // fancyName("1.1"), + // fancyName("1.1.2"), + // fancyName("1.1.3"), + fancyName("1.1.4"), + // fancyName("1.1.5"), + // fancyName("1.1.6"), + // fancyName("1.1.7"), + // fancyName("1.1.8"), + // translate("Chinese", "1"), +]); diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts new file mode 100644 index 000000000..d92d968dd --- /dev/null +++ b/i18n/initializers/initialize.ts @@ -0,0 +1,38 @@ +import fs from "fs"; +import OpenAI from "openai/index.mjs"; + +export default async function createAssistant(language: string, ai: OpenAI) { + const assistant = await ai.beta.assistants.create({ + name: "SICP Translator", + instructions: `You are a professional translator with high technical skills in computer science. + You MUST adhere to the following rules strictly: + 1. ALWAYS use the exact translations for technical terms found in the uploaded reference file. + 2. If a term appears in the reference file, you MUST use the provided translation without exception. + 3. Preserve all XML tags and structure exactly as given. + 4. Do not add any additional information or explanatory notes. + 5. When translating, search the reference file first for any technical terms before translating them. + 6. Do not format your response using markdown or any other formatting.`, + model: process.env.AI_MODEL as string, + tools: [{ type: "file_search" }] + }); + + const fileStreams = [ + "/home/yihao/projects/XML_translater/metadatas/try.txt" + ].map(path => fs.createReadStream(path)); + + // Create a vector store including our two files. + const vectorStore = await ai.beta.vectorStores.create({ + name: "Translation instructions" + }); + + await ai.beta.vectorStores.fileBatches.uploadAndPoll(vectorStore.id, { + files: fileStreams + }); + + await ai.beta.assistants.update(assistant.id, { + tool_resources: { file_search: { vector_store_ids: [vectorStore.id] } } + }); + + const updatedAssistant = await ai.beta.assistants.retrieve(assistant.id); + return updatedAssistant; +} diff --git a/i18n/package.json b/i18n/package.json new file mode 100644 index 000000000..fbf5a3ac2 --- /dev/null +++ b/i18n/package.json @@ -0,0 +1,15 @@ +{ + "type": "module", + "contributors": [ + { + "name": "yihao", + "url": "https://github.com/yihao03/" + }, + { + "name": "Haodong", + "url": "https://github.com/coder114514" + } + ], + "dependencies": { + } +} diff --git a/package.json b/package.json index ede73904a..9be94f19f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,10 @@ "lz-string": "^1.5.0", "prettier": "^3.5.2", "xmldom": "^0.6.0", - "xpath": "0.0.33" + "xpath": "0.0.33", + "dotenv": "^16.4.7", + "openai": "^4.81.0", + "sax": "^1.4.1" }, "resolutions": { "**/gl": "^8.0.2" @@ -57,7 +60,8 @@ "test": "node ./scripts/test.js", "try": "cd docs_out; http-server --cors --port 8080", "tryjson": "http-server --cors --port 8080", - "nodetest": "./scripts/nodetest.sh" + "nodetest": "./scripts/nodetest.sh", + "trans": "npx tsx ./i18n/index.ts" }, "husky": { "hooks": { From 7e793ded569d18efcccf72e4597b51a621aaaab5 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Wed, 12 Mar 2025 22:24:06 +0800 Subject: [PATCH 02/55] Add .env to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9f1f7b4f9..a440c0d43 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ test_node_env/expect.txt test_node_env/result.txt test_node_env/node_modules .DS_Store +.env *.icloud From 098de277aa0d0fdc5a95bac8d31667ae27b173b5 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 10:28:24 +0800 Subject: [PATCH 03/55] attempt to create a translate function that attempts to recursively splits the text into manageable chunk --- i18n/controllers/translate copy.ts | 362 +++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 i18n/controllers/translate copy.ts diff --git a/i18n/controllers/translate copy.ts b/i18n/controllers/translate copy.ts new file mode 100644 index 000000000..0fa60a92f --- /dev/null +++ b/i18n/controllers/translate copy.ts @@ -0,0 +1,362 @@ +import fs from "fs"; +import OpenAI from "openai"; +import path from "path"; +import createAssistant from "../initializers/initialize"; +import dotenv from "dotenv"; +import sax from "sax"; +import { Readable } from "stream"; +import { fileURLToPath } from "url"; + +dotenv.config(); + +if (process.env.AI_MODEL === undefined || process.env.API_KEY === undefined) { + throw Error("Please specify AI_MODEL and API_KEY!"); +} + +// initialize OpenAI API +const ai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.AI_BASEURL +}); + +const MAXLEN = 2000; + +async function translate(language: string, filePath: string): Promise { + try { + // Pipe the XML file into the parser. + const input_dir = fileURLToPath( + import.meta.resolve("../../xml" + filePath) + ); + console.log(input_dir); + const translated: string = await recursivelyTranslate(language, input_dir); + + const output_path = fileURLToPath( + import.meta.resolve("../../xml_cn" + filePath) + ); + + // Ensure directory exists + const dir = path.dirname(output_path); + fs.mkdirSync(dir, { recursive: true }); + + fs.writeFileSync(output_path, translated); + console.log(`Translation saved to ${output_path}`); + } catch (parseErr) { + console.error("Error parsing XML:", parseErr); + } +} + +// TODO: change the toTranslate to a file path, read the file and translate the content +async function recursivelyTranslate( + language: string, + path: string +): Promise { + // Recursive function to split and translate + async function helper(ori: string, force: boolean): Promise { + if (ori.length < MAXLEN && !force) { + return await translateChunk(ori); // translate the chunk + } + + let subTranslated = ""; + // continue splitting the chunk + // Create a SAX parser in strict mode to split source into chunks. + await new Promise((resolve, reject) => { + const subParser = (sax as any).createStream(true, { trim: false }); + + let subCurrentDepth = 0; + let subCurrentSegment = ""; + const subSegments: [boolean, string][] = []; + let subIsRecording = false; + + subParser.on("opentag", node => { + subCurrentDepth++; + + // If we're at depth 2, this is the start of a new segment. + if (subCurrentDepth === 2 || subIsRecording) { + subIsRecording = true; + subCurrentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; + } else { + subSegments.push([ + false, + `<${node.name}${formatAttributes(node.attributes)}>` + ]); + } + }); + + subParser.on("text", text => { + if (subIsRecording) { + subCurrentSegment += `${text}`; + } else { + subSegments.push([false, text]); + } + }); + + subParser.on("cdata", cdata => { + if (subIsRecording) { + subCurrentSegment += ``; + } + }); + + subParser.on("closetag", tagName => { + if (subIsRecording) { + subCurrentSegment += ``; + } + + if (subCurrentDepth === 2) { + // We are closing a segment element. + subSegments.push([true, subCurrentSegment]); + subCurrentSegment = ""; + subIsRecording = false; + } + + if (subCurrentDepth === 1) { + // We are closing the root element. + subSegments.push([false, ``]); + } + + subCurrentDepth--; + }); + + subParser.on("comment", comment => { + if (subIsRecording) { + subCurrentSegment += ``; + } else { + subSegments.push([false, ``]); + } + }); + + subParser.on("end", async () => { + for (const segment of subSegments) { + if (segment[0]) { + subTranslated += await helper(segment[1], false); + } else { + subTranslated += segment[1]; + } + } + console.log(`Done translating all segments.`); + resolve(); + }); + + subParser.on("error", reject); + + Readable.from(ori).pipe(subParser); + }); + + return subTranslated; + } + + // Create a SAX parser in strict mode to split source into chunks. + const parser = (sax as any).createStream(true, { trim: false }); + + // const assistant = await createAssistant(language, ai); + const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; + const thread = await ai.beta.threads.create(); + let translated = ""; + + try { + await new Promise((resolve, reject) => { + console.log("Translating " + path + " at " + thread.id); + // Variables to track current depth and segments. + let currentDepth = 0; + let currentSegment = ""; + const segments: [boolean, string][] = []; + + // In this context: + // - Depth 0: Before any element is opened. + // - Depth 1: The root element (). + // - Depth 2: Each direct child of the root that we want to capture. + let isRecording = false; + + parser.on("opentag", node => { + currentDepth++; + + // If we're at depth 2, this is the start of a new segment. + if (currentDepth === 2 || isRecording) { + isRecording = true; + currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; + } else { + segments.push([ + false, + `<${node.name}${formatAttributes(node.attributes)}>` + ]); + } + }); + + parser.on("text", text => { + if (isRecording) { + currentSegment += `${text}`; + } else { + segments.push([false, text]); + } + }); + + parser.on("cdata", cdata => { + if (isRecording) { + currentSegment += ``; + } + }); + + parser.on("closetag", tagName => { + if (isRecording) { + currentSegment += ``; + } + + if (currentDepth === 2) { + // We are closing a segment element. + segments.push([true, currentSegment]); + currentSegment = ""; + isRecording = false; + } + + if (currentDepth === 1) { + // We are closing the root element. + segments.push([false, ``]); + } + + currentDepth--; + }); + + parser.on("comment", comment => { + if (isRecording) { + currentSegment += ``; + } else { + segments.push([false, ``]); + } + }); + + parser.on("end", async () => { + for (const segment of segments) { + if (segment[0]) { + translated += await helper(segment[1], false); + } else { + translated += segment[1]; + } + } + console.log(`Done translating all segments.`); + resolve() + }); + + parser.on("error", reject); + + fs.createReadStream(path).pipe(parser); + }); + + return translated; + } catch (parseErr) { + console.error("Error parsing XML:", parseErr); + return translated + ""; + } + + async function translateChunk(chunk: string): Promise { + // console.log("translating chunk: " + chunk); + // Create a SAX parser in strict mode for cleaning up translations. + const clean = (sax as any).createStream(true, { trim: false }); + + // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags + let currDepth = -1; + + clean.on("text", text => { + if (currDepth >= 1) { + translatedChunk += escapeXML(text); + } + }); + + clean.on("opentag", node => { + currDepth++; + if (node.name != "WRAPPER") { + translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; + } + }); + + clean.on("closetag", tagName => { + if (tagName != "WRAPPER") { + translatedChunk += ``; + } + currDepth--; + }); + + clean.on("cdata", cdata => { + translatedChunk += ``; + }); + + clean.on("comment", comment => { + translatedChunk += ``; + }); + + clean.on("error", error => { + console.log( + "error encountered when validating XML: " + + error + + "\nvalidating section: " + + chunk.substring(0, 100) + + "..." + ); + + // Attempt to recover using the internal parser + try { + clean._parser.resume(); + } catch (e) { + console.log("Failed to resume parser:", e); + } + }); + + let translatedChunk = ""; + + try { + await ai.beta.threads.messages.create(thread.id, { + role: "user", + content: `Translate this content to ${language}. + IMPORTANT: You MUST search the uploaded reference file for any technical terms and use EXACTLY the translations specified there. + If a term exists in the reference file, use that translation without deviation. + Do not modify XML tags, attributes of XML tags and structure. Do not say anything else. + Content to translate: + ${chunk}` + }); + const run = await ai.beta.threads.runs.createAndPoll(thread.id, { + assistant_id: assistant_id + }); + + const messages = await ai.beta.threads.messages.list(thread.id, { + run_id: run.id + }); + const message = messages.data.pop()!; + const messageContent = message.content[0]; + + if (messageContent.type !== "text") { + throw new Error( + `Unexpected message content type: ${messageContent.type}` + ); + } + + const text = messageContent.text; + // console.log(text.value); + + const safeText = escapeXML(text.value); + const textStream = Readable.from("" + safeText + ""); + + await new Promise((resolve, reject) => { + clean.once("end", resolve); + clean.once("error", reject); + textStream.pipe(clean); + }); + + return translatedChunk; + } catch (err) { + console.log(`Error occured while translating ${path}:\n ` + err); + return translated + ""; + } + } +} + +export default translate; + +// Helper function to format attributes into a string. +function formatAttributes(attrs) { + const attrStr = Object.entries(attrs) + .map(([key, val]) => `${key}="${val}"`) + .join(" "); + return attrStr ? " " + attrStr : ""; +} + +function escapeXML(str: string): string { + return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); +} From 10856a1913947371fd16d49079e975c9a4690bf9 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 10:31:13 +0800 Subject: [PATCH 04/55] Refactor translate function to improve XML error handling and update output path --- i18n/controllers/translate.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/i18n/controllers/translate.ts b/i18n/controllers/translate.ts index bae3d0da2..ab6990323 100644 --- a/i18n/controllers/translate.ts +++ b/i18n/controllers/translate.ts @@ -108,7 +108,7 @@ async function translate(language: string, filePath: string) { } console.log(`Done translating all segments.`); const output_path = fileURLToPath( - import.meta.resolve("../../xml/translations" + filePath) + import.meta.resolve("../../xml_cn" + filePath) ); // Ensure directory exists @@ -140,7 +140,7 @@ async function translate(language: string, filePath: string) { clean.on("text", text => { if (currDepth >= 1) { - translated += text; + translated += escapeXML(text); } }); @@ -166,6 +166,22 @@ async function translate(language: string, filePath: string) { translated += ``; }); + clean.on("error", error => { + console.log( + "error encountered when validating XML: " + + error + + "\nvalidating section: " + + chunk.substring(0, 100) + "..." + ); + + // Attempt to recover using the internal parser + try { + clean._parser.resume(); + } catch (e) { + console.log("Failed to resume parser:", e); + } + }); + let translated = ""; try { @@ -174,7 +190,7 @@ async function translate(language: string, filePath: string) { content: `Translate this content to ${language}. IMPORTANT: You MUST search the uploaded reference file for any technical terms and use EXACTLY the translations specified there. If a term exists in the reference file, use that translation without deviation. - Do not modify XML tags, content of XML tags and structure. Do not say anything else. Only translate the content and return the xml as is. + Do not modify XML tags, attributes of XML tags and structure. Do not say anything else. Content to translate: ${chunk}` }); @@ -225,4 +241,4 @@ function formatAttributes(attrs) { function escapeXML(str: string): string { return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); -} \ No newline at end of file +} From 4922e984b60e3467ae202c18294851317cd3cf9a Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 12:34:56 +0800 Subject: [PATCH 05/55] Update .gitignore and refactor translation logic to use recurTranslate function --- .gitignore | 2 + .../{translate copy.ts => recurTranslate.ts} | 135 +++++---- i18n/controllers/translate.ts | 4 - i18n/index.ts | 6 +- i18n/yarn.lock | 278 ++++++++++++++++++ yarn.lock | 275 +++++++++++++++++ 6 files changed, 629 insertions(+), 71 deletions(-) rename i18n/controllers/{translate copy.ts => recurTranslate.ts} (78%) create mode 100644 i18n/yarn.lock diff --git a/.gitignore b/.gitignore index a440c0d43..a80ea8faf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ test_node_env/node_modules .DS_Store .env *.icloud + +/xml_* \ No newline at end of file diff --git a/i18n/controllers/translate copy.ts b/i18n/controllers/recurTranslate.ts similarity index 78% rename from i18n/controllers/translate copy.ts rename to i18n/controllers/recurTranslate.ts index 0fa60a92f..ef91385b8 100644 --- a/i18n/controllers/translate copy.ts +++ b/i18n/controllers/recurTranslate.ts @@ -19,7 +19,7 @@ const ai = new OpenAI({ baseURL: process.env.AI_BASEURL }); -const MAXLEN = 2000; +const MAXLEN = 5000; async function translate(language: string, filePath: string): Promise { try { @@ -27,7 +27,7 @@ async function translate(language: string, filePath: string): Promise { const input_dir = fileURLToPath( import.meta.resolve("../../xml" + filePath) ); - console.log(input_dir); + console.log("Translating file: " + input_dir); const translated: string = await recursivelyTranslate(language, input_dir); const output_path = fileURLToPath( @@ -52,10 +52,14 @@ async function recursivelyTranslate( ): Promise { // Recursive function to split and translate async function helper(ori: string, force: boolean): Promise { + ori = escapeXML(ori); + if (ori.length < MAXLEN && !force) { + console.log("Translating chunk: " + ori.substring(0, 50) + "..."); return await translateChunk(ori); // translate the chunk } + console.log("Chunk too large, splitting..."); let subTranslated = ""; // continue splitting the chunk // Create a SAX parser in strict mode to split source into chunks. @@ -86,7 +90,11 @@ async function recursivelyTranslate( if (subIsRecording) { subCurrentSegment += `${text}`; } else { - subSegments.push([false, text]); + if (text == "\n " || text == "\r\n " || text == ", \n" || text == ", \r\n") { + subSegments.push([false, text]); + } else { + subSegments.push([true, text]); + } } }); @@ -132,7 +140,7 @@ async function recursivelyTranslate( subTranslated += segment[1]; } } - console.log(`Done translating all segments.`); + console.log(`Completed chunk translation, continuing...`); resolve(); }); @@ -232,7 +240,7 @@ async function recursivelyTranslate( } } console.log(`Done translating all segments.`); - resolve() + resolve(); }); parser.on("error", reject); @@ -247,69 +255,17 @@ async function recursivelyTranslate( } async function translateChunk(chunk: string): Promise { - // console.log("translating chunk: " + chunk); - // Create a SAX parser in strict mode for cleaning up translations. - const clean = (sax as any).createStream(true, { trim: false }); - - // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags - let currDepth = -1; - - clean.on("text", text => { - if (currDepth >= 1) { - translatedChunk += escapeXML(text); - } - }); - - clean.on("opentag", node => { - currDepth++; - if (node.name != "WRAPPER") { - translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; - } - }); - - clean.on("closetag", tagName => { - if (tagName != "WRAPPER") { - translatedChunk += ``; - } - currDepth--; - }); - - clean.on("cdata", cdata => { - translatedChunk += ``; - }); - - clean.on("comment", comment => { - translatedChunk += ``; - }); - - clean.on("error", error => { - console.log( - "error encountered when validating XML: " + - error + - "\nvalidating section: " + - chunk.substring(0, 100) + - "..." - ); - - // Attempt to recover using the internal parser - try { - clean._parser.resume(); - } catch (e) { - console.log("Failed to resume parser:", e); - } - }); - let translatedChunk = ""; try { await ai.beta.threads.messages.create(thread.id, { role: "user", content: `Translate this content to ${language}. - IMPORTANT: You MUST search the uploaded reference file for any technical terms and use EXACTLY the translations specified there. - If a term exists in the reference file, use that translation without deviation. - Do not modify XML tags, attributes of XML tags and structure. Do not say anything else. - Content to translate: - ${chunk}` + IMPORTANT: You MUST search the uploaded reference file for any technical terms and use EXACTLY the translations specified there. + If a term exists in the reference file, use that translation without deviation. + Do not modify XML tags, attributes of XML tags and structure. Do not say anything else. + Content to translate: + ${chunk}` }); const run = await ai.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant_id @@ -328,14 +284,65 @@ async function recursivelyTranslate( } const text = messageContent.text; - // console.log(text.value); const safeText = escapeXML(text.value); const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { + // Create a SAX parser in strict mode for cleaning up translations. + const clean = (sax as any).createStream(true, { trim: false }); + + // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags + let currDepth = -1; + + clean.on("text", text => { + if (currDepth >= 1) { + translatedChunk += escapeXML(text); + } + }); + + clean.on("opentag", node => { + currDepth++; + if (node.name != "WRAPPER") { + translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; + } + }); + + clean.on("closetag", tagName => { + if (tagName != "WRAPPER") { + translatedChunk += ``; + } + currDepth--; + }); + + clean.on("cdata", cdata => { + translatedChunk += ``; + }); + + clean.on("comment", comment => { + translatedChunk += ``; + }); + + clean.on("error", error => { + console.log( + "error encountered when validating XML: " + + error + + "\nvalidating section: " + + chunk.substring(0, 100) + + "..." + ); + + // Attempt to recover using the internal parser + try { + clean._parser.resume(); + } catch (e) { + console.log("Failed to resume parser:", e); + reject; + } + }); + clean.once("end", resolve); - clean.once("error", reject); + textStream.pipe(clean); }); diff --git a/i18n/controllers/translate.ts b/i18n/controllers/translate.ts index ab6990323..465b1d136 100644 --- a/i18n/controllers/translate.ts +++ b/i18n/controllers/translate.ts @@ -188,10 +188,6 @@ async function translate(language: string, filePath: string) { await ai.beta.threads.messages.create(thread.id, { role: "user", content: `Translate this content to ${language}. - IMPORTANT: You MUST search the uploaded reference file for any technical terms and use EXACTLY the translations specified there. - If a term exists in the reference file, use that translation without deviation. - Do not modify XML tags, attributes of XML tags and structure. Do not say anything else. - Content to translate: ${chunk}` }); const run = await ai.beta.threads.runs.createAndPoll(thread.id, { diff --git a/i18n/index.ts b/i18n/index.ts index 87fb510b4..fb0b56279 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,6 +1,6 @@ import { getSource } from "./controllers/gitComm.ts"; import PathGenerator from "./controllers/path.ts"; -import translate from "./controllers/translate.ts"; +import translate from "./controllers/recurTranslate.ts"; export default async function fancyName(path: string) { const startTime = new Date().getTime(); @@ -16,11 +16,11 @@ export default async function fancyName(path: string) { await Promise.all([ - // fancyName("2") + fancyName("2") // fancyName("1.1"), // fancyName("1.1.2"), // fancyName("1.1.3"), - fancyName("1.1.4"), + // fancyName("1.1.4"), // fancyName("1.1.5"), // fancyName("1.1.6"), // fancyName("1.1.7"), diff --git a/i18n/yarn.lock b/i18n/yarn.lock new file mode 100644 index 000000000..7680b3fca --- /dev/null +++ b/i18n/yarn.lock @@ -0,0 +1,278 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node-fetch@^2.6.4": + version "2.6.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + +"@types/node@*": + version "22.13.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" + integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== + dependencies: + undici-types "~6.20.0" + +"@types/node@^18.11.18": + version "18.19.80" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.80.tgz#6d6008e8920dddcd23f9dd33da24684ef57d487c" + integrity sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ== + dependencies: + undici-types "~5.26.4" + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +agentkeepalive@^4.2.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +ms@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +openai@^4.81.0: + version "4.87.3" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.87.3.tgz#82679f09d91f0e8e9da94b9ee0369c44733577da" + integrity sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + +sax@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" diff --git a/yarn.lock b/yarn.lock index 376fb1851..01d14f98b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -971,11 +971,40 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/node-fetch@^2.6.4": + version "2.6.12" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + +"@types/node@*": + version "22.13.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" + integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== + dependencies: + undici-types "~6.20.0" + +"@types/node@^18.11.18": + version "18.19.80" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.80.tgz#6d6008e8920dddcd23f9dd33da24684ef57d487c" + integrity sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ== + dependencies: + undici-types "~5.26.4" + abbrev@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-class-fields@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/acorn-class-fields/-/acorn-class-fields-1.0.0.tgz#b413793e6b3ddfcd17a02f9c7a850f4bbfdc1c7a" @@ -1017,6 +1046,13 @@ agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: dependencies: debug "^4.3.4" +agentkeepalive@^4.2.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1059,6 +1095,11 @@ async@^2.6.2: dependencies: lodash "^4.17.14" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + babel-plugin-polyfill-corejs2@^0.4.10: version "0.4.10" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" @@ -1186,6 +1227,14 @@ cacache@^18.0.0: tar "^6.1.11" unique-filename "^3.0.0" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1248,6 +1297,13 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^12.0.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -1332,11 +1388,30 @@ define-properties@^1.1.2, define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detect-libc@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== +dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -1403,6 +1478,33 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -1422,6 +1524,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -1496,6 +1603,29 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data-encoder@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" + integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + +formdata-node@^4.3.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" + integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.3" + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -1529,6 +1659,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1544,6 +1679,30 @@ get-intrinsic@^1.0.2: has-proto "^1.0.1" has-symbols "^1.0.3" +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -1598,6 +1757,11 @@ glsl-tokenizer@^2.1.5: dependencies: through2 "^0.6.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + gpu-mock.js@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/gpu-mock.js/-/gpu-mock.js-1.3.1.tgz#f7deaa09da3f672762eda944ecf948fd2c4b1490" @@ -1634,6 +1798,18 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1641,6 +1817,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -1709,6 +1892,13 @@ https-proxy-agent@^7.0.1: agent-base "^7.0.2" debug "4" +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + husky@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" @@ -1981,6 +2171,11 @@ make-fetch-happen@^13.0.0: promise-retry "^2.0.1" ssri "^10.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1994,6 +2189,18 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -2109,6 +2316,11 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + nan@^2.18.0: version "2.20.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" @@ -2131,6 +2343,11 @@ node-abi@^3.3.0, node-abi@^3.56.0: dependencies: semver "^7.3.5" +node-domexception@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-environment-flags@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -2139,6 +2356,13 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-gyp@^10.0.1: version "10.2.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.2.0.tgz#80101c4aa4f7ab225f13fcc8daaaac4eb1a8dd86" @@ -2207,6 +2431,19 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +openai@^4.81.0: + version "4.87.3" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.87.3.tgz#82679f09d91f0e8e9da94b9ee0369c44733577da" + integrity sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ== + dependencies: + "@types/node" "^18.11.18" + "@types/node-fetch" "^2.6.4" + abort-controller "^3.0.0" + agentkeepalive "^4.2.1" + form-data-encoder "1.7.2" + formdata-node "^4.3.2" + node-fetch "^2.6.7" + opener@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" @@ -2526,6 +2763,11 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + secure-compare@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" @@ -2817,6 +3059,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tslib@^1.9.2: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -2829,6 +3076,16 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -2908,11 +3165,21 @@ v8flags@^3.1.1: dependencies: homedir-polyfill "^1.0.1" +web-streams-polyfill@4.0.0-beta.3: + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" + integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== + webgpu@^0.1.16: version "0.1.16" resolved "https://registry.yarnpkg.com/webgpu/-/webgpu-0.1.16.tgz#dec416373e308181b28864b58c8a914461d7ceee" integrity sha512-KAXn/f8lnL8o4B718zzdfi1l0nEWQpuoWlC1L5WM/svAbeHjShCEI0l5ZcZBEEUm9FF3ZTgRjWk8iwbJfnGKTA== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" @@ -2920,6 +3187,14 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From 9f21e76b977c3f7d56f59a3a35e14151fac5e9bf Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 14:48:21 +0800 Subject: [PATCH 06/55] updated splitting logic to concat orphaned texts --- i18n/controllers/recurTranslate.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index ef91385b8..060c86a88 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -90,8 +90,9 @@ async function recursivelyTranslate( if (subIsRecording) { subCurrentSegment += `${text}`; } else { - if (text == "\n " || text == "\r\n " || text == ", \n" || text == ", \r\n") { - subSegments.push([false, text]); + if (subSegments.length > 0 && subSegments[subSegments.length - 1][1] != undefined) { + subSegments[subSegments.length - 1][1] += text; + subSegments[subSegments.length - 1][0] = true; } else { subSegments.push([true, text]); } From 29f144146806177f243f1ee8122628fa66fe19f7 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 14:51:27 +0800 Subject: [PATCH 07/55] Refactor XML parsing logic to use strict mode and enhance text escaping --- i18n/controllers/recurTranslate.ts | 45 +++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 060c86a88..a3ee67d3a 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -6,6 +6,7 @@ import dotenv from "dotenv"; import sax from "sax"; import { Readable } from "stream"; import { fileURLToPath } from "url"; +import { strict } from "assert"; dotenv.config(); @@ -21,13 +22,15 @@ const ai = new OpenAI({ const MAXLEN = 5000; +const createParser = () => (sax as any).createStream(true, { trim: false }, { strictEntities: true }); + async function translate(language: string, filePath: string): Promise { try { // Pipe the XML file into the parser. const input_dir = fileURLToPath( import.meta.resolve("../../xml" + filePath) ); - console.log("Translating file: " + input_dir); + const translated: string = await recursivelyTranslate(language, input_dir); const output_path = fileURLToPath( @@ -52,19 +55,15 @@ async function recursivelyTranslate( ): Promise { // Recursive function to split and translate async function helper(ori: string, force: boolean): Promise { - ori = escapeXML(ori); - if (ori.length < MAXLEN && !force) { - console.log("Translating chunk: " + ori.substring(0, 50) + "..."); return await translateChunk(ori); // translate the chunk } - console.log("Chunk too large, splitting..."); let subTranslated = ""; // continue splitting the chunk // Create a SAX parser in strict mode to split source into chunks. await new Promise((resolve, reject) => { - const subParser = (sax as any).createStream(true, { trim: false }); + const subParser = createParser(); let subCurrentDepth = 0; let subCurrentSegment = ""; @@ -87,12 +86,22 @@ async function recursivelyTranslate( }); subParser.on("text", text => { + text = strongEscapeXML(text); if (subIsRecording) { - subCurrentSegment += `${text}`; + subCurrentSegment += text; } else { - if (subSegments.length > 0 && subSegments[subSegments.length - 1][1] != undefined) { + if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][1] != undefined + ) { subSegments[subSegments.length - 1][1] += text; subSegments[subSegments.length - 1][0] = true; + + // if (text == "\n " || text == "\r\n " || text == ", \n" || text == ", \r\n") { + // subSegments.push([false, text]); + // } else { + // subSegments.push([true, text]); + // } } else { subSegments.push([true, text]); } @@ -141,7 +150,6 @@ async function recursivelyTranslate( subTranslated += segment[1]; } } - console.log(`Completed chunk translation, continuing...`); resolve(); }); @@ -154,7 +162,7 @@ async function recursivelyTranslate( } // Create a SAX parser in strict mode to split source into chunks. - const parser = (sax as any).createStream(true, { trim: false }); + const parser = createParser(); // const assistant = await createAssistant(language, ai); const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; @@ -191,8 +199,9 @@ async function recursivelyTranslate( }); parser.on("text", text => { + text = strongEscapeXML(text); if (isRecording) { - currentSegment += `${text}`; + currentSegment += text; } else { segments.push([false, text]); } @@ -287,18 +296,19 @@ async function recursivelyTranslate( const text = messageContent.text; const safeText = escapeXML(text.value); + console.log(safeText); const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { // Create a SAX parser in strict mode for cleaning up translations. - const clean = (sax as any).createStream(true, { trim: false }); + const clean = createParser(); // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags let currDepth = -1; clean.on("text", text => { if (currDepth >= 1) { - translatedChunk += escapeXML(text); + translatedChunk += strongEscapeXML(text); } }); @@ -368,3 +378,12 @@ function formatAttributes(attrs) { function escapeXML(str: string): string { return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); } + +function strongEscapeXML(str: string): string { + return str + .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} \ No newline at end of file From f0316dcd09ebc55ae753d2d661189d7a2f0485dd Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 15:13:30 +0800 Subject: [PATCH 08/55] Fix error handling in recursivelyTranslate function and remove debug log --- i18n/controllers/recurTranslate.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index a3ee67d3a..5c07bd0d7 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -296,7 +296,6 @@ async function recursivelyTranslate( const text = messageContent.text; const safeText = escapeXML(text.value); - console.log(safeText); const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { @@ -348,7 +347,7 @@ async function recursivelyTranslate( clean._parser.resume(); } catch (e) { console.log("Failed to resume parser:", e); - reject; + reject(); } }); @@ -360,7 +359,7 @@ async function recursivelyTranslate( return translatedChunk; } catch (err) { console.log(`Error occured while translating ${path}:\n ` + err); - return translated + ""; + return translatedChunk + ""; } } } From e45ede2cd271b029f4bac3ac04f9284c767930d7 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 17:02:04 +0800 Subject: [PATCH 09/55] Refactor recursion logic in translate function to improve orphaned text handling and error reporting --- i18n/controllers/recurTranslate.ts | 44 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 5c07bd0d7..d4705fe2f 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -6,7 +6,6 @@ import dotenv from "dotenv"; import sax from "sax"; import { Readable } from "stream"; import { fileURLToPath } from "url"; -import { strict } from "assert"; dotenv.config(); @@ -20,9 +19,10 @@ const ai = new OpenAI({ baseURL: process.env.AI_BASEURL }); -const MAXLEN = 5000; +const MAXLEN = 3000; -const createParser = () => (sax as any).createStream(true, { trim: false }, { strictEntities: true }); +const createParser = () => + (sax as any).createStream(true, { trim: false }, { strictEntities: true }); async function translate(language: string, filePath: string): Promise { try { @@ -92,18 +92,20 @@ async function recursivelyTranslate( } else { if ( subSegments.length > 0 && - subSegments[subSegments.length - 1][1] != undefined + subSegments[subSegments.length - 1][0] ) { subSegments[subSegments.length - 1][1] += text; subSegments[subSegments.length - 1][0] = true; - - // if (text == "\n " || text == "\r\n " || text == ", \n" || text == ", \r\n") { - // subSegments.push([false, text]); - // } else { - // subSegments.push([true, text]); - // } } else { - subSegments.push([true, text]); + if ( + text.trim() !== "" || + text.trim() === "," || + text.trim() === "." + ) { + subSegments.push([false, text]); + } else { + subSegments.push([true, text]); + } } } }); @@ -121,7 +123,11 @@ async function recursivelyTranslate( if (subCurrentDepth === 2) { // We are closing a segment element. - subSegments.push([true, subCurrentSegment]); + if (tagName === "LATEXINLINE") { + subSegments.push([false, subCurrentSegment]); + } else { + subSegments.push([true, subCurrentSegment]); + } subCurrentSegment = ""; subIsRecording = false; } @@ -336,10 +342,9 @@ async function recursivelyTranslate( clean.on("error", error => { console.log( "error encountered when validating XML: " + - error + - "\nvalidating section: " + - chunk.substring(0, 100) + - "..." + error + "\nfile: " + path + + "\n section: " + + (safeText.length > 50 ? safeText.substring(0, 100) + "..." : safeText ) ); // Attempt to recover using the internal parser @@ -347,7 +352,7 @@ async function recursivelyTranslate( clean._parser.resume(); } catch (e) { console.log("Failed to resume parser:", e); - reject(); + reject(e); } }); @@ -375,7 +380,10 @@ function formatAttributes(attrs) { } function escapeXML(str: string): string { - return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); + return str + .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") + .replace(/<([^a-zA-Z\/])/g, "<$1") // Fix lone < characters + .replace(/([^a-zA-Z0-9"'\s\/])>/g, "$1>"); // Fix lone > characters; } function strongEscapeXML(str: string): string { From ba88b6373129f3d2a9e412764cd4bce1e6a871e1 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 23:35:28 +0800 Subject: [PATCH 10/55] Enhance translation performance by measuring execution time and refactoring segment handling to use arrays for better management --- i18n/controllers/recurTranslate.ts | 67 ++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index d4705fe2f..421c7eed2 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -25,6 +25,7 @@ const createParser = () => (sax as any).createStream(true, { trim: false }, { strictEntities: true }); async function translate(language: string, filePath: string): Promise { + const startTime = new Date().getTime(); try { // Pipe the XML file into the parser. const input_dir = fileURLToPath( @@ -45,6 +46,9 @@ async function translate(language: string, filePath: string): Promise { console.log(`Translation saved to ${output_path}`); } catch (parseErr) { console.error("Error parsing XML:", parseErr); + } finally { + const elapsed = new Date().getTime() - startTime; + console.log(filePath + " took " + elapsed / 1000.0 + " seconds"); } } @@ -59,7 +63,7 @@ async function recursivelyTranslate( return await translateChunk(ori); // translate the chunk } - let subTranslated = ""; + let subTranslated: string[] = []; // continue splitting the chunk // Create a SAX parser in strict mode to split source into chunks. await new Promise((resolve, reject) => { @@ -71,6 +75,10 @@ async function recursivelyTranslate( let subIsRecording = false; subParser.on("opentag", node => { + if (node.name === "WRAPPER") { + return; + } + subCurrentDepth++; // If we're at depth 2, this is the start of a new segment. @@ -95,9 +103,7 @@ async function recursivelyTranslate( subSegments[subSegments.length - 1][0] ) { subSegments[subSegments.length - 1][1] += text; - subSegments[subSegments.length - 1][0] = true; - } else { - if ( + } else if ( text.trim() !== "" || text.trim() === "," || text.trim() === "." @@ -105,7 +111,6 @@ async function recursivelyTranslate( subSegments.push([false, text]); } else { subSegments.push([true, text]); - } } } }); @@ -117,16 +122,36 @@ async function recursivelyTranslate( }); subParser.on("closetag", tagName => { + if (tagName === "WRAPPER") { + return; + } + if (subIsRecording) { subCurrentSegment += ``; } if (subCurrentDepth === 2) { // We are closing a segment element. - if (tagName === "LATEXINLINE") { + if ( + tagName === "LATEXINLINE" || + tagName === "LATEX" || + tagName === "SNIPPET" || + tagName === "SCHEMEINLINE" + ) { subSegments.push([false, subCurrentSegment]); } else { + if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] && + (subSegments[subSegments.length - 1][1].length + + subCurrentSegment.length) < + MAXLEN + ) { + console.log("Merging segments"); + subSegments[subSegments.length - 1][1] += subCurrentSegment; + } else { subSegments.push([true, subCurrentSegment]); + } } subCurrentSegment = ""; subIsRecording = false; @@ -151,9 +176,9 @@ async function recursivelyTranslate( subParser.on("end", async () => { for (const segment of subSegments) { if (segment[0]) { - subTranslated += await helper(segment[1], false); + subTranslated.push(await helper(segment[1], false)); } else { - subTranslated += segment[1]; + subTranslated.push(segment[1]); } } resolve(); @@ -161,10 +186,10 @@ async function recursivelyTranslate( subParser.on("error", reject); - Readable.from(ori).pipe(subParser); + Readable.from("" + ori + "").pipe(subParser); }); - return subTranslated; + return subTranslated.join(""); } // Create a SAX parser in strict mode to split source into chunks. @@ -173,7 +198,7 @@ async function recursivelyTranslate( // const assistant = await createAssistant(language, ai); const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; const thread = await ai.beta.threads.create(); - let translated = ""; + let translated: String[] = []; try { await new Promise((resolve, reject) => { @@ -250,9 +275,9 @@ async function recursivelyTranslate( parser.on("end", async () => { for (const segment of segments) { if (segment[0]) { - translated += await helper(segment[1], false); + translated.push(await helper(segment[1], false)); } else { - translated += segment[1]; + translated.push(segment[1]); } } console.log(`Done translating all segments.`); @@ -264,14 +289,19 @@ async function recursivelyTranslate( fs.createReadStream(path).pipe(parser); }); - return translated; + return translated.join(""); } catch (parseErr) { console.error("Error parsing XML:", parseErr); - return translated + ""; + return translated.join("") + ""; } async function translateChunk(chunk: string): Promise { + if (chunk.trim() === "" || chunk.trim() === "," || chunk.trim() === ".") { + return chunk; + } + let translatedChunk = ""; + console.log("Translating chunk of length: " + chunk.length + "\n" + chunk); try { await ai.beta.threads.messages.create(thread.id, { @@ -364,7 +394,10 @@ async function recursivelyTranslate( return translatedChunk; } catch (err) { console.log(`Error occured while translating ${path}:\n ` + err); - return translatedChunk + ""; + return ( + translatedChunk + + `\n` + ); } } } @@ -393,4 +426,4 @@ function strongEscapeXML(str: string): string { .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); -} \ No newline at end of file +} From d443ffb889c970a38c753571726f485a44c10ff1 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 23:36:19 +0800 Subject: [PATCH 11/55] Refactor async execution in fancyName function and improve error handling in Promise.all --- i18n/index.ts | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/i18n/index.ts b/i18n/index.ts index fb0b56279..5afd99788 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -3,27 +3,26 @@ import PathGenerator from "./controllers/path.ts"; import translate from "./controllers/recurTranslate.ts"; export default async function fancyName(path: string) { - const startTime = new Date().getTime(); const fullPath = PathGenerator(path); - console.log("Translating: " + fullPath); await translate("Chinese", fullPath); - - const elapsed = new Date().getTime() - startTime; - console.log(fullPath + " took " + elapsed / 1000.0 + " seconds"); } - - -await Promise.all([ - fancyName("2") - // fancyName("1.1"), - // fancyName("1.1.2"), - // fancyName("1.1.3"), - // fancyName("1.1.4"), - // fancyName("1.1.5"), - // fancyName("1.1.6"), - // fancyName("1.1.7"), - // fancyName("1.1.8"), - // translate("Chinese", "1"), -]); +(async () => { + try { + await Promise.all([ + // fancyName("2") + // fancyName("1.1"), + // fancyName("1.1.2"), + // fancyName("1.1.3"), + // fancyName("1.1.4"), + // fancyName("1.1.5"), + fancyName("1.1.6"), + // fancyName("1.1.7") + // fancyName("1.1.8"), + // translate("Chinese", "1"), + ]); + } catch (e) { + console.error(e); + } +})(); From da52eb500856733998099fba06dc3f30b7deb1c0 Mon Sep 17 00:00:00 2001 From: yihao03 Date: Tue, 18 Mar 2025 23:37:33 +0800 Subject: [PATCH 12/55] Refactor XML escaping logic to use strongEscapeXML for improved security and consistency --- i18n/controllers/translate.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/i18n/controllers/translate.ts b/i18n/controllers/translate.ts index 465b1d136..9298dd08c 100644 --- a/i18n/controllers/translate.ts +++ b/i18n/controllers/translate.ts @@ -58,7 +58,7 @@ async function translate(language: string, filePath: string) { parser.on("text", text => { if (isRecording) { - currentSegment += `${text}`; + currentSegment += strongEscapeXML(text); } else { segments.push([false, text]); } @@ -140,7 +140,7 @@ async function translate(language: string, filePath: string) { clean.on("text", text => { if (currDepth >= 1) { - translated += escapeXML(text); + translated += strongEscapeXML(text); } }); @@ -209,7 +209,7 @@ async function translate(language: string, filePath: string) { const text = messageContent.text; // console.log(text.value); - const safeText = escapeXML(text.value); + const safeText: String = escapeXML(text.value); const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { @@ -238,3 +238,14 @@ function formatAttributes(attrs) { function escapeXML(str: string): string { return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); } + + + +function strongEscapeXML(str: string): string { + return str + .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} \ No newline at end of file From 3370e5075fbd47934e76479c00e78822efe35eeb Mon Sep 17 00:00:00 2001 From: yihao03 Date: Wed, 19 Mar 2025 10:13:08 +0800 Subject: [PATCH 13/55] Refactor segment handling in recursivelyTranslate function to improve merging logic and remove unnecessary debug logs --- i18n/controllers/recurTranslate.ts | 34 ++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 421c7eed2..8bb42425b 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -147,7 +147,6 @@ async function recursivelyTranslate( subCurrentSegment.length) < MAXLEN ) { - console.log("Merging segments"); subSegments[subSegments.length - 1][1] += subCurrentSegment; } else { subSegments.push([true, subCurrentSegment]); @@ -251,9 +250,27 @@ async function recursivelyTranslate( if (currentDepth === 2) { // We are closing a segment element. - segments.push([true, currentSegment]); - currentSegment = ""; - isRecording = false; + if ( + tagName === "LATEXINLINE" || + tagName === "LATEX" || + tagName === "SNIPPET" || + tagName === "SCHEMEINLINE" || + tagName === "SCHEME" + ) { + segments.push([false, currentSegment]); + } else { + if ( + segments.length > 0 && + segments[segments.length - 1][0] && + (segments[segments.length - 1][1].length + + currentSegment.length) < + MAXLEN + ) { + segments[segments.length - 1][1] += currentSegment; + } else { + segments.push([true, currentSegment]); + } + } } if (currentDepth === 1) { @@ -280,7 +297,6 @@ async function recursivelyTranslate( translated.push(segment[1]); } } - console.log(`Done translating all segments.`); resolve(); }); @@ -299,9 +315,13 @@ async function recursivelyTranslate( if (chunk.trim() === "" || chunk.trim() === "," || chunk.trim() === ".") { return chunk; } - + + console.log("Translating chunk of length: " + chunk.length); + if (chunk.length < 100) { + console.log("\nchunk: " + chunk) + } + let translatedChunk = ""; - console.log("Translating chunk of length: " + chunk.length + "\n" + chunk); try { await ai.beta.threads.messages.create(thread.id, { From d48e42592d507e2f50f387492b3cfe3c1521fb6c Mon Sep 17 00:00:00 2001 From: yihao Date: Fri, 28 Mar 2025 10:40:35 +0800 Subject: [PATCH 14/55] Add .gitignore for node_modules and update translation logic to handle TRANSLATE tags --- i18n/.gitignore | 1 + i18n/controllers/recurTranslate.ts | 6 +++--- i18n/index.ts | 16 ++++++++-------- i18n/package.json | 5 +++++ package.json | 7 ++----- yarn.lock | 9 ++++++++- 6 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 i18n/.gitignore diff --git a/i18n/.gitignore b/i18n/.gitignore new file mode 100644 index 000000000..30bc16279 --- /dev/null +++ b/i18n/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 8bb42425b..265941caa 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -331,7 +331,7 @@ async function recursivelyTranslate( If a term exists in the reference file, use that translation without deviation. Do not modify XML tags, attributes of XML tags and structure. Do not say anything else. Content to translate: - ${chunk}` + ${chunk} ` }); const run = await ai.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant_id @@ -369,13 +369,13 @@ async function recursivelyTranslate( clean.on("opentag", node => { currDepth++; - if (node.name != "WRAPPER") { + if (node.name != "WRAPPER" && node.name != "TRANSLATE") { translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; } }); clean.on("closetag", tagName => { - if (tagName != "WRAPPER") { + if (tagName != "WRAPPER" && tagName != "TRANSLATE") { translatedChunk += ``; } currDepth--; diff --git a/i18n/index.ts b/i18n/index.ts index 5afd99788..073ba8cb0 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -11,14 +11,14 @@ export default async function fancyName(path: string) { (async () => { try { await Promise.all([ - // fancyName("2") - // fancyName("1.1"), - // fancyName("1.1.2"), - // fancyName("1.1.3"), - // fancyName("1.1.4"), - // fancyName("1.1.5"), - fancyName("1.1.6"), - // fancyName("1.1.7") + // fancyName("2"), + fancyName("1.1"), + fancyName("1.1.2"), + fancyName("1.1.3"), + fancyName("1.1.4"), + fancyName("1.1.5"), + // fancyName("1.1.6"), + // fancyName("1.1.7"), // fancyName("1.1.8"), // translate("Chinese", "1"), ]); diff --git a/i18n/package.json b/i18n/package.json index fbf5a3ac2..e39b2713d 100644 --- a/i18n/package.json +++ b/i18n/package.json @@ -11,5 +11,10 @@ } ], "dependencies": { + }, + "devDependencies": { + "dotenv": "^16.4.7", + "openai": "^4.81.0", + "sax": "^1.4.1" } } diff --git a/package.json b/package.json index 9be94f19f..032f42b40 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,7 @@ "lz-string": "^1.5.0", "prettier": "^3.5.2", "xmldom": "^0.6.0", - "xpath": "0.0.33", - "dotenv": "^16.4.7", - "openai": "^4.81.0", - "sax": "^1.4.1" + "xpath": "0.0.33" }, "resolutions": { "**/gl": "^8.0.2" @@ -61,7 +58,7 @@ "try": "cd docs_out; http-server --cors --port 8080", "tryjson": "http-server --cors --port 8080", "nodetest": "./scripts/nodetest.sh", - "trans": "npx tsx ./i18n/index.ts" + "trans": "cd ./i18n && yarn install && npx tsx index.ts" }, "husky": { "hooks": { diff --git a/yarn.lock b/yarn.lock index 01d14f98b..4fbfb697f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1276,6 +1276,13 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-progress@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" + integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== + dependencies: + string-width "^4.2.3" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2914,7 +2921,7 @@ ssri@^10.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0: +string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== From 806a15264325194f14654ed2c1e022991f8844f6 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:05:35 +0800 Subject: [PATCH 15/55] update lockfile --- yarn.lock | 284 +----------------------------------------------------- 1 file changed, 1 insertion(+), 283 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4fbfb697f..376fb1851 100644 --- a/yarn.lock +++ b/yarn.lock @@ -971,40 +971,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/node-fetch@^2.6.4": - version "2.6.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" - integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== - dependencies: - "@types/node" "*" - form-data "^4.0.0" - -"@types/node@*": - version "22.13.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" - integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== - dependencies: - undici-types "~6.20.0" - -"@types/node@^18.11.18": - version "18.19.80" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.80.tgz#6d6008e8920dddcd23f9dd33da24684ef57d487c" - integrity sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ== - dependencies: - undici-types "~5.26.4" - abbrev@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-class-fields@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/acorn-class-fields/-/acorn-class-fields-1.0.0.tgz#b413793e6b3ddfcd17a02f9c7a850f4bbfdc1c7a" @@ -1046,13 +1017,6 @@ agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: dependencies: debug "^4.3.4" -agentkeepalive@^4.2.1: - version "4.6.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" - integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== - dependencies: - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1095,11 +1059,6 @@ async@^2.6.2: dependencies: lodash "^4.17.14" -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - babel-plugin-polyfill-corejs2@^0.4.10: version "0.4.10" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" @@ -1227,14 +1186,6 @@ cacache@^18.0.0: tar "^6.1.11" unique-filename "^3.0.0" -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1276,13 +1227,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-progress@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" - integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== - dependencies: - string-width "^4.2.3" - clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -1304,13 +1248,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - commander@^12.0.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -1395,30 +1332,11 @@ define-properties@^1.1.2, define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - detect-libc@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== -dotenv@^16.4.7: - version "16.4.7" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" - integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -1485,33 +1403,6 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -1531,11 +1422,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -1610,29 +1496,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - -form-data@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" - integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - mime-types "^2.1.12" - -formdata-node@^4.3.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" - integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.3" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -1666,11 +1529,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1686,30 +1544,6 @@ get-intrinsic@^1.0.2: has-proto "^1.0.1" has-symbols "^1.0.3" -get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -1764,11 +1598,6 @@ glsl-tokenizer@^2.1.5: dependencies: through2 "^0.6.3" -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - gpu-mock.js@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/gpu-mock.js/-/gpu-mock.js-1.3.1.tgz#f7deaa09da3f672762eda944ecf948fd2c4b1490" @@ -1805,18 +1634,6 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1824,13 +1641,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -1899,13 +1709,6 @@ https-proxy-agent@^7.0.1: agent-base "^7.0.2" debug "4" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - husky@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" @@ -2178,11 +1981,6 @@ make-fetch-happen@^13.0.0: promise-retry "^2.0.1" ssri "^10.0.0" -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -2196,18 +1994,6 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -2323,11 +2109,6 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - nan@^2.18.0: version "2.20.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" @@ -2350,11 +2131,6 @@ node-abi@^3.3.0, node-abi@^3.56.0: dependencies: semver "^7.3.5" -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - node-environment-flags@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -2363,13 +2139,6 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-gyp@^10.0.1: version "10.2.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.2.0.tgz#80101c4aa4f7ab225f13fcc8daaaac4eb1a8dd86" @@ -2438,19 +2207,6 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -openai@^4.81.0: - version "4.87.3" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.87.3.tgz#82679f09d91f0e8e9da94b9ee0369c44733577da" - integrity sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ== - dependencies: - "@types/node" "^18.11.18" - "@types/node-fetch" "^2.6.4" - abort-controller "^3.0.0" - agentkeepalive "^4.2.1" - form-data-encoder "1.7.2" - formdata-node "^4.3.2" - node-fetch "^2.6.7" - opener@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" @@ -2770,11 +2526,6 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" - integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== - secure-compare@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" @@ -2921,7 +2672,7 @@ ssri@^10.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.3: +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3066,11 +2817,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - tslib@^1.9.2: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -3083,16 +2829,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -3172,21 +2908,11 @@ v8flags@^3.1.1: dependencies: homedir-polyfill "^1.0.1" -web-streams-polyfill@4.0.0-beta.3: - version "4.0.0-beta.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" - integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== - webgpu@^0.1.16: version "0.1.16" resolved "https://registry.yarnpkg.com/webgpu/-/webgpu-0.1.16.tgz#dec416373e308181b28864b58c8a914461d7ceee" integrity sha512-KAXn/f8lnL8o4B718zzdfi1l0nEWQpuoWlC1L5WM/svAbeHjShCEI0l5ZcZBEEUm9FF3ZTgRjWk8iwbJfnGKTA== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" @@ -3194,14 +2920,6 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From 3329c548f9870c7cfa85a3556016992db4809ce5 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 10 Apr 2025 22:41:57 +0800 Subject: [PATCH 16/55] reorganised file structure and yarn prepare json to translate all languages in xml --- i18n/controllers/recurTranslate.ts | 18 +- i18n/index.ts | 8 +- i18n/initializers/initialize.ts | 4 +- javascript/index.js | 49 +- xml/cn/chapter1/section1/section1.xml | 188 + xml/cn/chapter1/section1/subsection2.xml | 1036 +++++ xml/cn/chapter1/section1/subsection3.xml | 460 +++ xml/cn/chapter1/section1/subsection4.xml | 2423 ++++++++++++ xml/cn/chapter1/section1/subsection5.xml | 3394 +++++++++++++++++ xml/cn/chapter1/section1/subsection6.xml | 952 +++++ xml/cn/chapter1/section1/subsection7.xml | 659 ++++ xml/cn/chapter1/section1/subsection8.xml | 1038 +++++ xml/cn/chapter2/chapter2.xml | 345 ++ xml/{ => en}/Makefile | 0 xml/{ => en}/chapter1/chapter1.xml | 0 xml/{ => en}/chapter1/section1/section1.xml | 0 .../chapter1/section1/subsection1.xml | 0 .../chapter1/section1/subsection2.xml | 0 .../chapter1/section1/subsection3.xml | 0 .../chapter1/section1/subsection4.xml | 0 .../chapter1/section1/subsection5.xml | 0 .../chapter1/section1/subsection6.xml | 0 .../chapter1/section1/subsection7.xml | 0 .../chapter1/section1/subsection8.xml | 0 xml/{ => en}/chapter1/section2/section2.xml | 0 .../chapter1/section2/subsection1.xml | 0 .../chapter1/section2/subsection2.xml | 0 .../chapter1/section2/subsection3.xml | 0 .../chapter1/section2/subsection4.xml | 0 .../chapter1/section2/subsection5.xml | 0 .../chapter1/section2/subsection6.xml | 0 xml/{ => en}/chapter1/section3/section3.xml | 0 .../chapter1/section3/subsection1.xml | 0 .../chapter1/section3/subsection2.xml | 0 .../chapter1/section3/subsection3.xml | 0 .../chapter1/section3/subsection4.xml | 0 xml/{ => en}/chapter2/chapter2.xml | 0 xml/{ => en}/chapter2/section1/section1.xml | 0 .../chapter2/section1/subsection1.xml | 0 .../chapter2/section1/subsection2.xml | 0 .../chapter2/section1/subsection3.xml | 0 .../chapter2/section1/subsection4.xml | 0 xml/{ => en}/chapter2/section2/section2.xml | 0 .../chapter2/section2/subsection1.xml | 0 .../chapter2/section2/subsection2.xml | 0 .../chapter2/section2/subsection3.xml | 0 .../chapter2/section2/subsection4.xml | 0 xml/{ => en}/chapter2/section3/section3.xml | 0 .../chapter2/section3/subsection1.xml | 0 .../chapter2/section3/subsection2.xml | 0 .../chapter2/section3/subsection3.xml | 0 .../chapter2/section3/subsection4.xml | 0 xml/{ => en}/chapter2/section4/section4.xml | 0 .../chapter2/section4/subsection1.xml | 0 .../chapter2/section4/subsection2.xml | 0 .../chapter2/section4/subsection3.xml | 0 xml/{ => en}/chapter2/section5/section5.xml | 0 .../chapter2/section5/subsection1.xml | 0 .../chapter2/section5/subsection2.xml | 0 .../chapter2/section5/subsection3.xml | 0 xml/{ => en}/chapter3/chapter3.xml | 0 xml/{ => en}/chapter3/section1/section1.xml | 0 .../chapter3/section1/subsection1.xml | 0 .../chapter3/section1/subsection2.xml | 0 .../chapter3/section1/subsection3.xml | 0 xml/{ => en}/chapter3/section2/section2.xml | 0 .../chapter3/section2/subsection1.xml | 0 .../chapter3/section2/subsection2.xml | 0 .../chapter3/section2/subsection3.xml | 0 .../chapter3/section2/subsection4.xml | 0 .../chapter3/section2/subsection5.xml | 0 xml/{ => en}/chapter3/section3/section3.xml | 0 .../chapter3/section3/subsection1.xml | 0 .../chapter3/section3/subsection2.xml | 0 .../chapter3/section3/subsection3.xml | 0 .../chapter3/section3/subsection4.xml | 0 .../chapter3/section3/subsection5.xml | 0 xml/{ => en}/chapter3/section4/section4.xml | 0 .../chapter3/section4/subsection1.xml | 0 .../chapter3/section4/subsection2.xml | 0 xml/{ => en}/chapter3/section5/section5.xml | 0 .../chapter3/section5/subsection1.xml | 0 .../chapter3/section5/subsection2.xml | 0 .../chapter3/section5/subsection3.xml | 0 .../chapter3/section5/subsection4.xml | 0 .../chapter3/section5/subsection5.xml | 0 xml/{ => en}/chapter4/chapter4.xml | 0 xml/{ => en}/chapter4/section1/section1.xml | 0 .../chapter4/section1/subsection1.xml | 0 .../chapter4/section1/subsection2.xml | 0 .../chapter4/section1/subsection3.xml | 0 .../chapter4/section1/subsection4.xml | 0 .../chapter4/section1/subsection5.xml | 0 .../chapter4/section1/subsection6.xml | 0 .../chapter4/section1/subsection7.xml | 0 xml/{ => en}/chapter4/section2/section2.xml | 0 .../chapter4/section2/subsection1.xml | 0 .../chapter4/section2/subsection2.xml | 0 .../chapter4/section2/subsection3.xml | 0 xml/{ => en}/chapter4/section3/section3.xml | 0 .../chapter4/section3/subsection1.xml | 0 .../chapter4/section3/subsection2.xml | 0 .../chapter4/section3/subsection3.xml | 0 xml/{ => en}/chapter4/section4/section4.xml | 0 .../chapter4/section4/subsection1.xml | 0 .../chapter4/section4/subsection2.xml | 0 .../chapter4/section4/subsection3.xml | 0 .../chapter4/section4/subsection4.xml | 0 xml/{ => en}/chapter5/chapter5.xml | 0 xml/{ => en}/chapter5/section1/section1.xml | 0 .../chapter5/section1/subsection1.xml | 0 .../chapter5/section1/subsection2.xml | 0 .../chapter5/section1/subsection3.xml | 0 .../chapter5/section1/subsection4.xml | 0 .../chapter5/section1/subsection5.xml | 0 xml/{ => en}/chapter5/section2/section2.xml | 0 .../chapter5/section2/subsection1.xml | 0 .../chapter5/section2/subsection2.xml | 0 .../chapter5/section2/subsection3.xml | 0 .../chapter5/section2/subsection4.xml | 0 xml/{ => en}/chapter5/section3/section3.xml | 0 .../chapter5/section3/subsection1.xml | 0 .../chapter5/section3/subsection2.xml | 0 xml/{ => en}/chapter5/section4/section4.xml | 0 .../chapter5/section4/subsection1.xml | 0 .../chapter5/section4/subsection2.xml | 0 .../chapter5/section4/subsection3.xml | 0 .../chapter5/section4/subsection4.xml | 0 xml/{ => en}/chapter5/section5/section5.xml | 0 .../chapter5/section5/subsection1.xml | 0 .../chapter5/section5/subsection2.xml | 0 .../chapter5/section5/subsection3.xml | 0 .../chapter5/section5/subsection4.xml | 0 .../chapter5/section5/subsection5.xml | 0 .../chapter5/section5/subsection6.xml | 0 .../chapter5/section5/subsection7.xml | 0 xml/{ => en}/contents.ent | 0 xml/{ => en}/others/02foreword02.xml | 0 xml/{ => en}/others/02foreword84.xml | 0 xml/{ => en}/others/03prefaces03.xml | 0 xml/{ => en}/others/03prefaces96.xml | 0 xml/{ => en}/others/04acknowledgements04.xml | 0 xml/{ => en}/others/06see06.xml | 0 xml/{ => en}/others/97references97.xml | 0 xml/{ => en}/others/98indexpreface98.xml | 0 xml/{ => en}/others/99making99.xml | 0 yarn.lock | 284 +- 147 files changed, 10546 insertions(+), 312 deletions(-) create mode 100644 xml/cn/chapter1/section1/section1.xml create mode 100644 xml/cn/chapter1/section1/subsection2.xml create mode 100644 xml/cn/chapter1/section1/subsection3.xml create mode 100644 xml/cn/chapter1/section1/subsection4.xml create mode 100644 xml/cn/chapter1/section1/subsection5.xml create mode 100644 xml/cn/chapter1/section1/subsection6.xml create mode 100644 xml/cn/chapter1/section1/subsection7.xml create mode 100644 xml/cn/chapter1/section1/subsection8.xml create mode 100644 xml/cn/chapter2/chapter2.xml rename xml/{ => en}/Makefile (100%) rename xml/{ => en}/chapter1/chapter1.xml (100%) rename xml/{ => en}/chapter1/section1/section1.xml (100%) rename xml/{ => en}/chapter1/section1/subsection1.xml (100%) rename xml/{ => en}/chapter1/section1/subsection2.xml (100%) rename xml/{ => en}/chapter1/section1/subsection3.xml (100%) rename xml/{ => en}/chapter1/section1/subsection4.xml (100%) rename xml/{ => en}/chapter1/section1/subsection5.xml (100%) rename xml/{ => en}/chapter1/section1/subsection6.xml (100%) rename xml/{ => en}/chapter1/section1/subsection7.xml (100%) rename xml/{ => en}/chapter1/section1/subsection8.xml (100%) rename xml/{ => en}/chapter1/section2/section2.xml (100%) rename xml/{ => en}/chapter1/section2/subsection1.xml (100%) rename xml/{ => en}/chapter1/section2/subsection2.xml (100%) rename xml/{ => en}/chapter1/section2/subsection3.xml (100%) rename xml/{ => en}/chapter1/section2/subsection4.xml (100%) rename xml/{ => en}/chapter1/section2/subsection5.xml (100%) rename xml/{ => en}/chapter1/section2/subsection6.xml (100%) rename xml/{ => en}/chapter1/section3/section3.xml (100%) rename xml/{ => en}/chapter1/section3/subsection1.xml (100%) rename xml/{ => en}/chapter1/section3/subsection2.xml (100%) rename xml/{ => en}/chapter1/section3/subsection3.xml (100%) rename xml/{ => en}/chapter1/section3/subsection4.xml (100%) rename xml/{ => en}/chapter2/chapter2.xml (100%) rename xml/{ => en}/chapter2/section1/section1.xml (100%) rename xml/{ => en}/chapter2/section1/subsection1.xml (100%) rename xml/{ => en}/chapter2/section1/subsection2.xml (100%) rename xml/{ => en}/chapter2/section1/subsection3.xml (100%) rename xml/{ => en}/chapter2/section1/subsection4.xml (100%) rename xml/{ => en}/chapter2/section2/section2.xml (100%) rename xml/{ => en}/chapter2/section2/subsection1.xml (100%) rename xml/{ => en}/chapter2/section2/subsection2.xml (100%) rename xml/{ => en}/chapter2/section2/subsection3.xml (100%) rename xml/{ => en}/chapter2/section2/subsection4.xml (100%) rename xml/{ => en}/chapter2/section3/section3.xml (100%) rename xml/{ => en}/chapter2/section3/subsection1.xml (100%) rename xml/{ => en}/chapter2/section3/subsection2.xml (100%) rename xml/{ => en}/chapter2/section3/subsection3.xml (100%) rename xml/{ => en}/chapter2/section3/subsection4.xml (100%) rename xml/{ => en}/chapter2/section4/section4.xml (100%) rename xml/{ => en}/chapter2/section4/subsection1.xml (100%) rename xml/{ => en}/chapter2/section4/subsection2.xml (100%) rename xml/{ => en}/chapter2/section4/subsection3.xml (100%) rename xml/{ => en}/chapter2/section5/section5.xml (100%) rename xml/{ => en}/chapter2/section5/subsection1.xml (100%) rename xml/{ => en}/chapter2/section5/subsection2.xml (100%) rename xml/{ => en}/chapter2/section5/subsection3.xml (100%) rename xml/{ => en}/chapter3/chapter3.xml (100%) rename xml/{ => en}/chapter3/section1/section1.xml (100%) rename xml/{ => en}/chapter3/section1/subsection1.xml (100%) rename xml/{ => en}/chapter3/section1/subsection2.xml (100%) rename xml/{ => en}/chapter3/section1/subsection3.xml (100%) rename xml/{ => en}/chapter3/section2/section2.xml (100%) rename xml/{ => en}/chapter3/section2/subsection1.xml (100%) rename xml/{ => en}/chapter3/section2/subsection2.xml (100%) rename xml/{ => en}/chapter3/section2/subsection3.xml (100%) rename xml/{ => en}/chapter3/section2/subsection4.xml (100%) rename xml/{ => en}/chapter3/section2/subsection5.xml (100%) rename xml/{ => en}/chapter3/section3/section3.xml (100%) rename xml/{ => en}/chapter3/section3/subsection1.xml (100%) rename xml/{ => en}/chapter3/section3/subsection2.xml (100%) rename xml/{ => en}/chapter3/section3/subsection3.xml (100%) rename xml/{ => en}/chapter3/section3/subsection4.xml (100%) rename xml/{ => en}/chapter3/section3/subsection5.xml (100%) rename xml/{ => en}/chapter3/section4/section4.xml (100%) rename xml/{ => en}/chapter3/section4/subsection1.xml (100%) rename xml/{ => en}/chapter3/section4/subsection2.xml (100%) rename xml/{ => en}/chapter3/section5/section5.xml (100%) rename xml/{ => en}/chapter3/section5/subsection1.xml (100%) rename xml/{ => en}/chapter3/section5/subsection2.xml (100%) rename xml/{ => en}/chapter3/section5/subsection3.xml (100%) rename xml/{ => en}/chapter3/section5/subsection4.xml (100%) rename xml/{ => en}/chapter3/section5/subsection5.xml (100%) rename xml/{ => en}/chapter4/chapter4.xml (100%) rename xml/{ => en}/chapter4/section1/section1.xml (100%) rename xml/{ => en}/chapter4/section1/subsection1.xml (100%) rename xml/{ => en}/chapter4/section1/subsection2.xml (100%) rename xml/{ => en}/chapter4/section1/subsection3.xml (100%) rename xml/{ => en}/chapter4/section1/subsection4.xml (100%) rename xml/{ => en}/chapter4/section1/subsection5.xml (100%) rename xml/{ => en}/chapter4/section1/subsection6.xml (100%) rename xml/{ => en}/chapter4/section1/subsection7.xml (100%) rename xml/{ => en}/chapter4/section2/section2.xml (100%) rename xml/{ => en}/chapter4/section2/subsection1.xml (100%) rename xml/{ => en}/chapter4/section2/subsection2.xml (100%) rename xml/{ => en}/chapter4/section2/subsection3.xml (100%) rename xml/{ => en}/chapter4/section3/section3.xml (100%) rename xml/{ => en}/chapter4/section3/subsection1.xml (100%) rename xml/{ => en}/chapter4/section3/subsection2.xml (100%) rename xml/{ => en}/chapter4/section3/subsection3.xml (100%) rename xml/{ => en}/chapter4/section4/section4.xml (100%) rename xml/{ => en}/chapter4/section4/subsection1.xml (100%) rename xml/{ => en}/chapter4/section4/subsection2.xml (100%) rename xml/{ => en}/chapter4/section4/subsection3.xml (100%) rename xml/{ => en}/chapter4/section4/subsection4.xml (100%) rename xml/{ => en}/chapter5/chapter5.xml (100%) rename xml/{ => en}/chapter5/section1/section1.xml (100%) rename xml/{ => en}/chapter5/section1/subsection1.xml (100%) rename xml/{ => en}/chapter5/section1/subsection2.xml (100%) rename xml/{ => en}/chapter5/section1/subsection3.xml (100%) rename xml/{ => en}/chapter5/section1/subsection4.xml (100%) rename xml/{ => en}/chapter5/section1/subsection5.xml (100%) rename xml/{ => en}/chapter5/section2/section2.xml (100%) rename xml/{ => en}/chapter5/section2/subsection1.xml (100%) rename xml/{ => en}/chapter5/section2/subsection2.xml (100%) rename xml/{ => en}/chapter5/section2/subsection3.xml (100%) rename xml/{ => en}/chapter5/section2/subsection4.xml (100%) rename xml/{ => en}/chapter5/section3/section3.xml (100%) rename xml/{ => en}/chapter5/section3/subsection1.xml (100%) rename xml/{ => en}/chapter5/section3/subsection2.xml (100%) rename xml/{ => en}/chapter5/section4/section4.xml (100%) rename xml/{ => en}/chapter5/section4/subsection1.xml (100%) rename xml/{ => en}/chapter5/section4/subsection2.xml (100%) rename xml/{ => en}/chapter5/section4/subsection3.xml (100%) rename xml/{ => en}/chapter5/section4/subsection4.xml (100%) rename xml/{ => en}/chapter5/section5/section5.xml (100%) rename xml/{ => en}/chapter5/section5/subsection1.xml (100%) rename xml/{ => en}/chapter5/section5/subsection2.xml (100%) rename xml/{ => en}/chapter5/section5/subsection3.xml (100%) rename xml/{ => en}/chapter5/section5/subsection4.xml (100%) rename xml/{ => en}/chapter5/section5/subsection5.xml (100%) rename xml/{ => en}/chapter5/section5/subsection6.xml (100%) rename xml/{ => en}/chapter5/section5/subsection7.xml (100%) rename xml/{ => en}/contents.ent (100%) rename xml/{ => en}/others/02foreword02.xml (100%) rename xml/{ => en}/others/02foreword84.xml (100%) rename xml/{ => en}/others/03prefaces03.xml (100%) rename xml/{ => en}/others/03prefaces96.xml (100%) rename xml/{ => en}/others/04acknowledgements04.xml (100%) rename xml/{ => en}/others/06see06.xml (100%) rename xml/{ => en}/others/97references97.xml (100%) rename xml/{ => en}/others/98indexpreface98.xml (100%) rename xml/{ => en}/others/99making99.xml (100%) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 265941caa..1421d542d 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -19,7 +19,7 @@ const ai = new OpenAI({ baseURL: process.env.AI_BASEURL }); -const MAXLEN = 3000; +const MAXLEN = Number(process.env.MAX_LEN) || 3000; const createParser = () => (sax as any).createStream(true, { trim: false }, { strictEntities: true }); @@ -35,7 +35,7 @@ async function translate(language: string, filePath: string): Promise { const translated: string = await recursivelyTranslate(language, input_dir); const output_path = fileURLToPath( - import.meta.resolve("../../xml_cn" + filePath) + import.meta.resolve("../../xml/cn" + filePath) ); // Ensure directory exists @@ -69,7 +69,7 @@ async function recursivelyTranslate( await new Promise((resolve, reject) => { const subParser = createParser(); - let subCurrentDepth = 0; + let subCurrentDepth = 1; let subCurrentSegment = ""; const subSegments: [boolean, string][] = []; let subIsRecording = false; @@ -78,7 +78,7 @@ async function recursivelyTranslate( if (node.name === "WRAPPER") { return; } - + subCurrentDepth++; // If we're at depth 2, this is the start of a new segment. @@ -160,7 +160,6 @@ async function recursivelyTranslate( // We are closing the root element. subSegments.push([false, ``]); } - subCurrentDepth--; }); @@ -194,7 +193,7 @@ async function recursivelyTranslate( // Create a SAX parser in strict mode to split source into chunks. const parser = createParser(); - // const assistant = await createAssistant(language, ai); + // const assistant = await createAssistant(language, ai as any); const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; const thread = await ai.beta.threads.create(); let translated: String[] = []; @@ -316,7 +315,7 @@ async function recursivelyTranslate( return chunk; } - console.log("Translating chunk of length: " + chunk.length); + // console.log("Translating chunk of length: " + chunk.length); if (chunk.length < 100) { console.log("\nchunk: " + chunk) } @@ -333,6 +332,7 @@ async function recursivelyTranslate( Content to translate: ${chunk} ` }); + const run = await ai.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant_id }); @@ -340,6 +340,7 @@ async function recursivelyTranslate( const messages = await ai.beta.threads.messages.list(thread.id, { run_id: run.id }); + const message = messages.data.pop()!; const messageContent = message.content[0]; @@ -352,6 +353,7 @@ async function recursivelyTranslate( const text = messageContent.text; const safeText = escapeXML(text.value); + console.log(safeText); const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { @@ -416,7 +418,7 @@ async function recursivelyTranslate( console.log(`Error occured while translating ${path}:\n ` + err); return ( translatedChunk + - `\n` + `\n` ); } } diff --git a/i18n/index.ts b/i18n/index.ts index 073ba8cb0..bce72edbf 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -12,11 +12,11 @@ export default async function fancyName(path: string) { try { await Promise.all([ // fancyName("2"), - fancyName("1.1"), + // fancyName("1.1"), fancyName("1.1.2"), - fancyName("1.1.3"), - fancyName("1.1.4"), - fancyName("1.1.5"), + // fancyName("1.1.3"), + // fancyName("1.1.4"), + // fancyName("1.1.5"), // fancyName("1.1.6"), // fancyName("1.1.7"), // fancyName("1.1.8"), diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index d92d968dd..b98733453 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -21,11 +21,11 @@ export default async function createAssistant(language: string, ai: OpenAI) { ].map(path => fs.createReadStream(path)); // Create a vector store including our two files. - const vectorStore = await ai.beta.vectorStores.create({ + const vectorStore = await ai.vectorStores.create({ name: "Translation instructions" }); - await ai.beta.vectorStores.fileBatches.uploadAndPoll(vectorStore.id, { + await ai.vectorStores.fileBatches.uploadAndPoll(vectorStore.id, { files: fileStreams }); diff --git a/javascript/index.js b/javascript/index.js index 2271540f7..485753cdb 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -42,6 +42,7 @@ import { writeRewritedSearchData } from "./searchRewrite"; import { setupSnippetsJson } from "./processingFunctions/processSnippetJson"; import { createTocJson } from "./generateTocJson"; import { setupReferencesJson } from "./processingFunctions/processReferenceJson"; +import { SourceTextModule } from "vm"; export let parseType; let version; @@ -58,6 +59,11 @@ const ensureDirectoryExists = (path, cb) => { }); }; +const getDirectories = async source => + (await readdir(source, { withFileTypes: true })) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + async function translateXml(filepath, filename, option) { const fullFilepath = path.join(inputDir, filepath, filename); const fileToRead = await open(fullFilepath, "r"); @@ -200,8 +206,14 @@ async function recursiveXmlToHtmlInOrder(option) { } } -async function recursiveTranslateXml(filepath, option) { +async function recursiveTranslateXml(filepath, option, lang = "en") { let files; + + if (lang != null) { + filepath = path.join(filepath, lang); + console.log(filepath); + } + const fullPath = path.join(inputDir, filepath); files = await readdir(fullPath); const promises = []; @@ -222,7 +234,9 @@ async function recursiveTranslateXml(filepath, option) { promises.push(translateXml(filepath, file, option)); } } else if (fs.lstatSync(path.join(fullPath, file)).isDirectory()) { - promises.push(recursiveTranslateXml(path.join(filepath, file), option)); + promises.push( + recursiveTranslateXml(path.join(filepath, file), option, null) + ); } }); await Promise.all(promises); @@ -349,23 +363,28 @@ async function main() { console.log("setup snippets done\n"); recursiveTranslateXml("", "parseXml"); } else if (parseType == "json") { - outputDir = path.join(__dirname, "../json"); + const languages = await getDirectories(path.join(__dirname, "../xml")); + console.dir(languages) - createMain(); - console.log("\ngenerate table of content\n"); - await recursiveTranslateXml("", "generateTOC"); - allFilepath = sortTOC(allFilepath); - createTocJson(outputDir); + languages.forEach(async lang => { + outputDir = path.join(__dirname, "../json", lang); + createMain(); - console.log("setup snippets and references\n"); - await recursiveXmlToHtmlInOrder("setupSnippet"); - console.log("setup snippets and references done\n"); + console.log("\ngenerate table of content\n"); + await recursiveTranslateXml("", "generateTOC", lang); + allFilepath = sortTOC(allFilepath); + createTocJson(outputDir); + + console.log("setup snippets and references\n"); + await recursiveXmlToHtmlInOrder("setupSnippet"); + console.log("setup snippets and references done\n"); - await recursiveXmlToHtmlInOrder("parseXml"); - writeRewritedSearchData(); - // this is meant to be temp; also, will remove the original "generateSearchData" after the updation at the frontend is completed. - //testIndexSearch(); + await recursiveXmlToHtmlInOrder("parseXml"); + writeRewritedSearchData(); + // this is meant to be temp; also, will remove the original "generateSearchData" after the updation at the frontend is completed. + //testIndexSearch(); + }); } } diff --git a/xml/cn/chapter1/section1/section1.xml b/xml/cn/chapter1/section1/section1.xml new file mode 100644 index 000000000..e2f436f4a --- /dev/null +++ b/xml/cn/chapter1/section1/section1.xml @@ -0,0 +1,188 @@ +
+ 编程元素编程元素 + 编程元素 + + + 编程元素编程元素 + + + 编程元素 + + 编程元素 + + + 编程元素 + + + + + 一个强大的编程语言不仅仅是用于指示计算机执行任务的工具。该语言还作为一个框架,用于组织我们关于过程的想法。因此,当我们描述一种语言时,我们应特别关注该语言提供的将简单想法结合成更复杂想法的方法。每一种强大的语言都有三种实现此目的的机制: +
    +
  • + 原始表达式, + 原始表达式 + 代表语言所关注的最简单的实体, +
  • +
  • + 组合方法, 通过 + 组合方法 + 组合,方法 + 从简单的元素构建复合元素,以及 +
  • +
  • + 抽象方法, + 抽象方法 + 通过它可以命名和操作复合元素作为单位。 +
  • +
+
编程元素 + + + 编程元素 + + + + + 一个强大的编程语言不仅仅是用于指示计算机执行任务的工具。该语言还作为一个框架,用于组织我们关于过程的想法。因此,当我们描述一种语言时,我们应特别关注该语言提供的将简单想法结合成更复杂想法的方法。每一种强大的语言都有三种实现此目的的机制: +
    +
  • + 原始表达式, + 原始表达式 + 代表语言所关注的最简单的实体, +
  • +
  • + 组合方法, 通过 + 组合方法 + 组合,方法 + 从简单的元素构建复合元素,以及 +
  • +
  • + 抽象方法, + 抽象方法 + 通过它可以命名和操作复合元素作为单位。 +
  • +
+
+ + + 在编程中,我们处理两种类型的元素: + + + 过程 + 过程 + + + + 函数 + + + 和 + 数据 + 数据。(稍后我们会发现它们实际上并不那么不同。)非正式地,数据是我们想要操作的东西,而 + + 过程 + 函数 + + 是操作数据规则的描述。因此,任何强大的编程语言都应该能够描述原始数据和原始 + + 过程 + 函数 + + 并且应该拥有组合和抽象 + + 过程 + 函数 + + 和数据的方法。 + The Elements of Programming + + + programming 元素 + + + + + 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: +
    +
  • + 原始表达式, + 原始表达式 + 代表语言所关注的最简单的实体, +
  • +
  • + 组合方法, 通过 + 组合方法 + 组合,方法 + 从简单的元素构建复合元素,以及 +
  • +
  • + 抽象方法, + 抽象方法 + 通过它可以命名和操作复合元素作为单位。 +
  • +
+
+ + + In programming, we deal with two kinds of elements: + + + 过程 + 过程 + + + + 函数 + + + 和 + 数据 + 数据。(稍后我们会发现它们实际上并不那么不同。)非正式地,数据是我们想要操作的东西,而 + + 过程 + 函数 + + 是操作数据规则的描述。因此,任何强大的编程语言都应该能够描述原始数据和原始 + + 过程 + 函数 + + 并且应该拥有组合和抽象 + + 过程 + 函数 + + 和数据的方法。 + + + + In this chapter we will deal only with simple + 数值数据 + 数据数值 + 数值数据,以便我们可以专注于构建 + 过程函数的规则。对于数字的 + + 特征描述为简单数据是一个完全的伪装。实际上,数字处理是任何编程语言中最棘手和最令人困惑的方面之一。涉及到的一些典型问题如下: + 整数 + 实数 + 整数与实数 + 一些计算机系统区分出整数,例如2,与实数,例如2.71。实数2.00与整数2不同吗?用于整数的算术运算与用于实数的运算相同吗?6除以2得3,还是3.0?我们可以表示多大的数字?我们可以表示多少位小数?整数的范围与实数的范围相同吗? + 数值分析 + 舍入误差 + 截断误差 + 当然,除了这些问题之外,还有一系列关于舍入和截断误差的问题即整个数值分析科学。由于本书的重点是大型程序设计,而不是数值技术,我们将忽略这些问题。本章中的数值示例将展示通常在使用保持非整数操作中的有限小数位精度的算术运算时观察到的舍入行为。 + + 在后面的章节中,我们将看到这些相同的规则也允许我们构建 + + 过程 + 函数 + + 来操作复合数据。 + 编程元素 +
\ No newline at end of file diff --git a/xml/cn/chapter1/section1/subsection2.xml b/xml/cn/chapter1/section1/subsection2.xml new file mode 100644 index 000000000..fed21578f --- /dev/null +++ b/xml/cn/chapter1/section1/subsection2.xml @@ -0,0 +1,1036 @@ + + 命名与环境命名与环境 + 命名与环境 + + + + 编程语言的一个关键方面是它提供的使用 + 命名计算对象 + 名称来引用计算 + + 对象。 + 对象,我们的第一种方式是常量。 + + + 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 我们说 + 原始表达式常量名称 + 名称识别一个 + 常量(在JavaScript中) + + 变量 + 常量 + + 其中的 + + + 变量的值 + + + 常量(在JavaScript中)的值 + + + 是对象。 + Naming and the Environment + + + + A critical aspect of a programming language is the means it provides + for using + 命名计算对象 + 名称来引用计算 + + 对象。 + 对象,我们的第一种方式是常量。 + + + 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 我们说 + 原始表达式常量名称 + 名称识别一个 + 常量(在JavaScript中) + + 变量 + 常量 + + 其中的 + + + 变量的值 + + + 常量(在JavaScript中)的值 + + + 是对象。 + + + + + 在Lisp的Scheme方言中,我们用 + define + 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} + define命名。 + + + 在JavaScript中,我们用 + 常量声明 + 声明常量常量(const) + 语法形式常量声明 + const(关键字) + 关键字constconst + 分号(;)语句结束 + 常量声明来命名。 + + + + var_size + +(define size 2) + + +const size = 2; + + + causes the interpreter to associate the value 2 with the + name size. + + + + 本书中,我们没有 + 定义 + 未指定值定义define + 显示解释器对 + 计算定义的响应,因为这是依赖于具体实现的。 + + + 本书中,我们没有显示解释器对 + 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 + + + 一旦名称 size + has been associated with the number 2, we can + refer to the value 2 by name: + + size_use_1 + var_size + 2 + +size + + +2 + + +size; + + +2 + + + + size_use_2 + var_size + 10 + +(* 5 size) + + +10 + + +5 * size; + + +10 + + + Naming and the Environment + + + + A critical aspect of a programming language is the means it provides + for using + 命名计算对象 + 名称来引用计算 + + 对象。 + 对象,我们的第一种方式是常量。 + + + 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 我们说 + 原始表达式常量名称 + 名称识别一个 + 常量(在JavaScript中) + + 变量 + 常量 + + 其中的 + + + 变量的值 + + + 常量(在JavaScript中)的值 + + + 是对象。 + + + + + 在Lisp的Scheme方言中,我们用 + define + 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} + define命名。 + + + 在JavaScript中,我们用 + 常量声明 + 声明常量常量(const) + 语法形式常量声明 + const(关键字) + 关键字constconst + 分号(;)语句结束 + 常量声明来命名。 + + + + var_size + +(define size 2) + + +const size = 2; + + + causes the interpreter to associate the value 2 with the + name size. + + + + 本书中,我们没有 + 定义 + 未指定值定义define + 显示解释器对 + 计算定义的响应,因为这是依赖于具体实现的。 + + + 本书中,我们没有显示解释器对 + 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 + + + 一旦名称 size + has been associated with the number 2, we can + refer to the value 2 by name: + + size_use_1 + var_size + 2 + +size + + +2 + + +size; + + +2 + + + + size_use_2 + var_size + 10 + +(* 5 size) + + +10 + + +5 * size; + + +10 + + + + + + + + JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 + + var_size + +5 * size; + + + 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: + + 10 + +const size = 2; +5 * size; + + + + + + Naming and the Environment + + + + A critical aspect of a programming language is the means it provides + for using + 命名计算对象 + 名称来引用计算 + + 对象。 + 对象,我们的第一种方式是常量。 + + + 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 我们说 + 原始表达式常量名称 + 名称识别一个 + 常量(在JavaScript中) + + 变量 + 常量 + + 其中的 + + + 变量的值 + + + 常量(在JavaScript中)的值 + + + 是对象。 + + + + + 在Lisp的Scheme方言中,我们用 + define + 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} + define命名。 + + + 在JavaScript中,我们用 + 常量声明 + 声明常量常量(const) + 语法形式常量声明 + const(关键字) + 关键字constconst + 分号(;)语句结束 + 常量声明来命名。 + + + + var_size + +(define size 2) + + +const size = 2; + + + causes the interpreter to associate the value 2 with the + name size. + + + + 本书中,我们没有 + 定义 + 未指定值定义define + 显示解释器对 + 计算定义的响应,因为这是依赖于具体实现的。 + + + 本书中,我们没有显示解释器对 + 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 + + + 一旦名称 size + has been associated with the number 2, we can + refer to the value 2 by name: + + size_use_1 + var_size + 2 + +size + + +2 + + +size; + + +2 + + + + size_use_2 + var_size + 10 + +(* 5 size) + + +10 + + +5 * size; + + +10 + + + + + + + + JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 + + var_size + +5 * size; + + + 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: + + 10 + +const size = 2; +5 * size; + + + + + + + + Here are further examples of the use of + + + define: + + + const: + + + + + pi + +(define pi 3.14159) + + +const pi = 3.14159; + + + + radius + +(define radius 10) + + +const radius = 10; + + + + pi_radius_radius + 314.159 + pi + radius + +(* pi (* radius radius)) + + +314.159 + + +pi * radius * radius; + + +314.159 + + + + circumference_definition + pi + radius + +(define circumference (* 2 pi radius)) + + +const circumference = 2 * pi * radius; + + + + 62.8318 + circumference_use + circumference_definition + +circumference + + +62.8318 + + +circumference; + + +62.8318 + + + Naming and the Environment + + + + A critical aspect of a programming language is the means it provides + for using + 命名计算对象 + 名称来引用计算 + + 对象。 + 对象,我们的第一种方式是常量。 + + + 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 我们说 + 原始表达式常量名称 + 名称识别一个 + 常量(在JavaScript中) + + 变量 + 常量 + + 其中的 + + + 变量的值 + + + 常量(在JavaScript中)的值 + + + 是对象。 + + + + + 在Lisp的Scheme方言中,我们用 + define + 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} + define命名。 + + + 在JavaScript中,我们用 + 常量声明 + 声明常量常量(const) + 语法形式常量声明 + const(关键字) + 关键字constconst + 分号(;)语句结束 + 常量声明来命名。 + + + + var_size + +(define size 2) + + +const size = 2; + + + causes the interpreter to associate the value 2 with the + name size. + + + + 本书中,我们没有 + 定义 + 未指定值定义define + 显示解释器对 + 计算定义的响应,因为这是依赖于具体实现的。 + + + 本书中,我们没有显示解释器对 + 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 + + + 一旦名称 size + has been associated with the number 2, we can + refer to the value 2 by name: + + size_use_1 + var_size + 2 + +size + + +2 + + +size; + + +2 + + + + size_use_2 + var_size + 10 + +(* 5 size) + + +10 + + +5 * size; + + +10 + + + + + + + + JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 + + var_size + +5 * size; + + + 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: + + 10 + +const size = 2; +5 * size; + + + + + + + + Here are further examples of the use of + + + define: + + + const: + + + + + pi + +(define pi 3.14159) + + +const pi = 3.14159; + + + + radius + +(define radius 10) + + +const radius = 10; + + + + pi_radius_radius + 314.159 + pi + radius + +(* pi (* radius radius)) + + +314.159 + + +pi * radius * radius; + + +314.159 + + + + circumference_definition + pi + radius + +(define circumference (* 2 pi radius)) + + +const circumference = 2 * pi * radius; + + + + 62.8318 + circumference_use + circumference_definition + +circumference + + +62.8318 + + +circumference; + + +62.8318 + + + + + + + 抽象方式definedefine + Define + + + 常量 + 抽象方式常量声明为 + 声明 + + + 是我们语言 + 最简单的抽象方式,因为它允许我们使用简单的名字来引用复合操作的结果,例如 + circumference computed above. + In general, computational objects may have very complex + structures, and it would be extremely inconvenient to have to remember + and repeat their details each time we want to use them. Indeed, + complex programs are constructed by building, step by step, + computational objects of increasing complexity. The + interpreter makes this step-by-step program construction particularly + convenient because name-object associations can be created + incrementally in successive interactions. This feature encourages the + 程序的增量开发 + 程序增量开发 + 程序的增量开发和测试在很大程度上 + 负责 + 程序结构 + + + Lisp + + + JavaScript + + + 程序通常由大量相对简单的 + + + 过程。 + + + 函数。 + + + Naming and the Environment + + + + A critical aspect of a programming language is the means it provides + for using + 命名计算对象 + 名称来引用计算 + + 对象。 + 对象,我们的第一种方式是常量。 + + + 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 我们说 + 原始表达式常量名称 + 名称识别一个 + 常量(在JavaScript中) + + 变量 + 常量 + + 其中的 + + + 变量的值 + + + 常量(在JavaScript中)的值 + + + 是对象。 + + + + + 在Lisp的Scheme方言中,我们用 + define + 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} + define命名。 + + + 在JavaScript中,我们用 + 常量声明 + 声明常量常量(const) + 语法形式常量声明 + const(关键字) + 关键字constconst + 分号(;)语句结束 + 常量声明来命名。 + + + + var_size + +(define size 2) + + +const size = 2; + + + causes the interpreter to associate the value 2 with the + name size. + + + + 本书中,我们没有 + 定义 + 未指定值定义define + 显示解释器对 + 计算定义的响应,因为这是依赖于具体实现的。 + + + 本书中,我们没有显示解释器对 + 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 + + + 一旦名称 size + has been associated with the number 2, we can + refer to the value 2 by name: + + size_use_1 + var_size + 2 + +size + + +2 + + +size; + + +2 + + + + size_use_2 + var_size + 10 + +(* 5 size) + + +10 + + +5 * size; + + +10 + + + + + + + + JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 + + var_size + +5 * size; + + + 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: + + 10 + +const size = 2; +5 * size; + + + + + + + + Here are further examples of the use of + + + define: + + + const: + + + + + pi + +(define pi 3.14159) + + +const pi = 3.14159; + + + + radius + +(define radius 10) + + +const radius = 10; + + + + pi_radius_radius + 314.159 + pi + radius + +(* pi (* radius radius)) + + +314.159 + + +pi * radius * radius; + + +314.159 + + + + circumference_definition + pi + radius + +(define circumference (* 2 pi radius)) + + +const circumference = 2 * pi * radius; + + + + 62.8318 + circumference_use + circumference_definition + +circumference + + +62.8318 + + +circumference; + + +62.8318 + + + + + + + 抽象方式definedefine + Define + + + 常量 + 抽象方式常量声明为 + 声明 + + + 是我们语言 + 最简单的抽象方式,因为它允许我们使用简单的名字来引用复合操作的结果,例如 + circumference computed above. + In general, computational objects may have very complex + structures, and it would be extremely inconvenient to have to remember + and repeat their details each time we want to use them. Indeed, + complex programs are constructed by building, step by step, + computational objects of increasing complexity. The + interpreter makes this step-by-step program construction particularly + convenient because name-object associations can be created + incrementally in successive interactions. This feature encourages the + 程序的增量开发 + 程序增量开发 + 程序的增量开发和测试在很大程度上 + 负责 + 程序结构 + + + Lisp + + + JavaScript + + + 程序通常由大量相对简单的 + + + 过程。 + + + 函数。 + + + + + It should be clear that the possibility of associating values with + names and later retrieving them means that the interpreter must + maintain some sort of memory that keeps track of the name-object + pairs. This memory is called the + 环境 + 环境 + (更准确地说是 + + + 全局环境 + 全局环境, + + + 程序环境 + 程序环境, + + + 因为我们稍后会看到,一个计算可能涉及多个不同的环境)。Chapter将展示这个环境概念对于理解解释器如何工作至关重要。Chapter将使用环境来实现解释器。 + \ No newline at end of file diff --git a/xml/cn/chapter1/section1/subsection3.xml b/xml/cn/chapter1/section1/subsection3.xml new file mode 100644 index 000000000..be8788e08 --- /dev/null +++ b/xml/cn/chapter1/section1/subsection3.xml @@ -0,0 +1,460 @@ + + + + + 组合的评估 + 评估组合的 + + + 操作符组合的评估 + 评估操作符组合的 + + + + + 组合的评估 + 评估组合的 + + + 操作符组合的评估 + 评估操作符组合的 + + + + 评估 + + 组合 + 操作符组合 + + + + + 组合的评估 + 评估组合的 + + + 操作符组合的评估 + 评估操作符组合的 + + + + 评估 + + 组合 + 操作符组合 + + + + + + + 组合的评估 + 评估组合的 + + + 操作符组合的评估 + 评估操作符组合的 + + + + Evaluating + + 组合 + 操作符组合 + + + + + + One of our goals in this chapter is to isolate issues about thinking + procedurally. As a case in point, let us consider that, in evaluating + + + 操作符 + + + 组合, 解释器本身在遵循一个过程。 + + 由于关键字 + function很重要, 我们通常将对"过程/过程式"的引用替换为 + 对"函数/函数式"的引用。上述句子是个例外; + 术语"过程性思维"和"过程"或许在此JavaScript版中仍旧适用。 + +
    +
  • + 要评估 + + + 一个组合, + + + 一个操作符组合, + + + 请执行以下操作: +
      +
    1. 评估 + + + 子表达式 + + + 操作数表达式 + + + 组合的。
    2. +
    3. + + + 应用 + 该过程 + 就是左边第一个 + 子表达式(操作符)的值,并应用于其它子表达式(操作数)的值。 + + + 应用该函数,该函数由操作符表示,并应用于 + 操作数的值。 + + +
    4. +
    +
  • +
+ + Scheme版本不区分操作符和应用组合。然而,由于中缀表示法, + JavaScript版本需要为这两者描述稍有不同的规则。本节包含了操作符 + 组合的规则,而1.1.5节为函数应用引入了新的规则。 + + 即使是这个简单的规则也说明了一些关于 + 流程的重要点。首先可以看到,第一步规定 + 为实现对组合的评估过程,我们必须首先对组合的 + 每个操作数执行评估过程。因此,评估规则是 + 递归 + 递归性质的; + 也就是说,其步骤之一包括需要调用规则 + 本身。可能看起来奇怪的是评估 + 规则在第一步中说,应该评估组合的最左边 + 元素,因为此时该元素只能是一个操作符 + 例如+* + 代表内置基本过程如加法或 + 乘法。我们稍后会看到可以使用 + 其操作符本身是复合表达式的组合这点很有用。 + +
+ + + 组合的评估 + 评估组合的 + + + 操作符组合的评估 + 评估操作符组合的 + + + + Evaluating + + 组合 + 操作符组合 + + + + + + One of our goals in this chapter is to isolate issues about thinking + procedurally. As a case in point, let us consider that, in evaluating + + + 操作符 + + + 组合,解释器本身在遵循一个过程。 + + 由于关键字 + function的重要性,我们通常将对"过程/过程式"的引用替换为 + 对"函数/函数式"的引用。上述句子是一个例外; + 术语"过程性思考"和"过程"可能在这里的JavaScript版本中仍然适用。 + +
    +
  • + 要评估 + + + 一个组合, + + + 一个操作符组合, + + + 请执行以下操作: +
      +
    1. 评估 + + + 子表达式 + + + 操作数表达式 + + + 组合的。
    2. +
    3. + + + 应用 + 该过程 + 就是最左边 + 子表达式(操作符)的值,应用于其他子表达式(操作数)的值。 + + + 应用由 + 操作符表示的函数,将其应用于 + 操作数的值。 + + +
    4. +
    +
  • +
+ + Scheme版本不区分操作符和 + 应用组合。然而,由于中缀表示法, + JavaScript版本需要为这两者描述稍微不同的 + 规则。本节包含操作符 + 组合的规则,而1.1.5节引入了函数 + 应用的新规则。 + + 即使是这个简单的规则也说明了一些关于 + 过程的重要点。首先可以观察到,第一步规定 + 为了完成组合的评估过程,我们必须首先对 + 组合的每个操作数进行评估过程。因此,评估规则是 + 递归 + 递归的性质; + 也就是说,它包括的其中一个步骤需要调用规则 + 本身。评估规则可能看起来很奇怪, + 因为在第一步中,它说我们应该评估一个组合的最左边 + 元素,因为在这一点上那只能是一个操作符 + 例如+* + 代表内建基本过程如加法或 + 乘法。稍后我们将看到可以与 + 操作符本身是复合表达式的组合进行工作是有用的。 + +
+ + Notice how succinctly the idea of recursion can be used to express + 递归表示复杂过程 + 在深入嵌套的组合情况下,否则将被视为相当复杂的过程。例如,评估 + + +(* (+ 2 (* 4 6)) + (+ 3 5 7)) + + +(2 + 4 * 6) * (3 + 12); + + + requires that the evaluation rule be applied to four different + combinations. We can obtain a picture of this process by + representing the combination in the form of a + 操作符组合作为树 + 被视为组合 + 树,如图 + + + 图. + + + 图. + + + 每个组合由一个 + 树的节点 + 节点表示,其 + 树的分支 + 分支对应于操作符和 + 从中分支出的组合的操作数。 + 该 + 树的终端节点 + 终端节点(即没有 + 从中分支出的节点)表示操作符或数字。 + 从树的角度来看评估,我们可以想象操作数的 + 值从终端节点开始向上流动,然后在更高的层级进行组合。一般来说,我们 + 将看到递归是一种处理 + 层次结构、树状对象的非常有力的技术。事实上,向上渗透值形式的评估规则是 + 一种被称为 + 树积累 + 树积累的一般过程的示例。 + + + +
+ + 树表示法,显示每个子组合的值。 + +
+
+ +
+
+ + 树表示法,显示每个子表达式的值。 + +
+
+
+
+ + + 组合的评估 + 评估组合的 + + + 操作符组合的评估 + 评估操作符组合的 + + + + Evaluating + + 组合 + 操作符组合 + + + + + + One of our goals in this chapter is to isolate issues about thinking + procedurally. As a case in point, let us consider that, in evaluating + + + + Notice how succinctly the idea of recursion can be used to express + + + +(* (+ 2 (* 4 6)) + (+ 3 5 7)) + + +(2 + 4 * 6) * (3 + 12); + + + requires that the evaluation rule be applied to four different + combinations. We can obtain a picture of this process by + representing the combination in the form of a + + + + + + Next, observe that the repeated application of the first step brings + us to the point where we need to evaluate, not combinations, but + primitive expressions such as + + + + + + + Evaluating + + + + + + One of our goals in this chapter is to isolate issues about thinking + procedurally. As a case in point, let us consider that, in evaluating + + + + Notice how succinctly the idea of recursion can be used to express + + + +(* (+ 2 (* 4 6)) + (+ 3 5 7)) + + +(2 + 4 * 6) * (3 + 12); + + + requires that the evaluation rule be applied to four different + combinations. We can obtain a picture of this process by + representing the combination in the form of a + + + + + + Next, observe that the repeated application of the first step brings + us to the point where we need to evaluate, not combinations, but + primitive expressions such as + + + + + Notice that the + evaluation rule given above does not handle + +x and the other of which is + 3, since the purpose of the + +x with a value. + (That is, + + + + + + Evaluating + + + + + + One of our goals in this chapter is to isolate issues about thinking + procedurally. As a case in point, let us consider that, in evaluating + + + + Notice how succinctly the idea of recursion can be used to express + + + +(* (+ 2 (* 4 6)) + (+ 3 5 7)) + + +(2 + 4 * 6) * (3 + 12); + + + requires that the evaluation rule be applied to four different + combinations. We can obtain a picture of this process by + representing the combination in the form of a + + + + + + Next, observe that the repeated application of the first step brings + us to the point where we need to evaluate, not combinations, but + primitive expressions such as + + + + + Notice that the + evaluation rule given above does not handle + +x and the other of which is + 3, since the purpose of the + +x with a value. + (That is, + + + + + + +
\ No newline at end of file diff --git a/xml/cn/chapter1/section1/subsection4.xml b/xml/cn/chapter1/section1/subsection4.xml new file mode 100644 index 000000000..2b878ac14 --- /dev/null +++ b/xml/cn/chapter1/section1/subsection4.xml @@ -0,0 +1,2423 @@ + + + + 复合 过程 + 函数 + + + 复合 过程 + 函数 + + + + 我们在 + + Lisp + JavaScript + + 中识别了一些任何强大编程语言中必须出现的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + 复合 过程 + 函数 + + + + 我们在 + + Lisp + JavaScript + + 中识别了一些任何强大编程语言中必须出现的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + 我们首先研究如何表达 + 平方 + 的概念。我们可能会说: + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + + 这在我们的语言中表示为 + + square + function (关键字) + 关键字functionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + 复合 过程 + 函数 + + + + 我们在 + + Lisp + JavaScript + + 中识别了一些任何强大编程语言中必须出现的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + 我们首先研究如何表达 + 平方 + 的概念。我们可能会说: + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + + 这在我们的语言中表示为 + + square + function (关键字) + 关键字functionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + Compound 过程 + 函数 + + + + We have identified in + + Lisp + JavaScript + + 一些必须出现在任何强大编程语言中的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + We begin by examining how to express the idea of + 平方。 + 我们可能会说, + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,为了更好地匹配JavaScript中的中缀表示法,Scheme和JavaScript的表达略有不同。 + + 这在我们的语言中表示为 + + square + function (keyword) + keywordsfunctionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + We can understand this in the following way: + + + +(define (square x) (* x x)) +;; ^ ^ ^ ^ ^ ^ +;; 要 平方 某个东西, 将其 乘以 自身。 + + +function square( x ) { return x * x; } +// ^ ^ ^ ^ ^ ^ ^ +// 要 平方 某个东西, 将其 乘以 自身。 + + + + + + + \begin{flushleft}\normalcodesize + \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} + \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ + \end{tabular} + \end{flushleft} + + + 我们这里有一个 + + + 复合过程 + 过程复合 + 复合过程, + + + 复合函数 + 复合 + 复合函数, + + + 它被赋予了一个名称 square. The + + 过程 + 函数 + + 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, + which plays the same role that a pronoun plays in natural language. + + + 过程命名使用 define 进行命名 + 过程define 创建 + 过程命名 + + + 函数命名of functions + 使用函数声明命名 + 使用函数声明创建 + + + 评估 + + + 定义 + + + 声明 + + + 创建这个复合 + + + 过程 + + + 函数 + + + 并将其与名称关联 + 语法形式函数声明 + + 函数声明 + 声明函数函数的 (function) + square. 注意,这里结合了两种不同的操作:我们正在创建 + + + 过程, + + + 函数, + + + 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 + + + 过程 + + + 函数 + + + 而不命名它们,以及给已创建的 + + + 过程 + + + 函数 + + + 命名。在节中我们将看到如何做到这一点。 + + + Compound 过程 + 函数 + + + + We have identified in + + Lisp + JavaScript + + 一些必须出现在任何强大编程语言中的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + We begin by examining how to express the idea of + 平方。 + 我们可能会说, + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + + 这在我们的语言中表示为 + + square + function (keyword) + keywordsfunctionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + We can understand this in the following way: + + + +(define (square x) (* x x)) +;; ^ ^ ^ ^ ^ ^ +;; 要 平方 某个东西, 将其 乘以 自身。 + + +function square( x ) { return x * x; } +// ^ ^ ^ ^ ^ ^ ^ +// 要 平方 某个东西, 将其 乘以 自身。 + + + + + + + \begin{flushleft}\normalcodesize + \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} + \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ + \end{tabular} + \end{flushleft} + + + 我们这里有一个 + + + 复合过程 + 过程复合 + 复合过程, + + + 复合函数 + 复合 + 复合函数, + + + 它被赋予了一个名称 square. The + + 过程 + 函数 + + 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, + which plays the same role that a pronoun plays in natural language. + + + 过程命名使用 define 进行命名 + 过程define 创建 + 过程命名 + + + 函数命名of functions + 使用函数声明命名 + 使用函数声明创建 + + + 评估 + + + 定义 + + + 声明 + + + 创建这个复合 + + + 过程 + + + 函数 + + + 并将其与名称关联 + 语法形式函数声明 + + 函数声明 + 声明函数函数的 (function) + square. 注意,这里结合了两种不同的操作:我们正在创建 + + + 过程, + + + 函数, + + + 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 + + + 过程 + + + 函数 + + + 而不命名它们,以及给已创建的 + + + 过程 + + + 函数 + + + 命名。在节中我们将看到如何做到这一点。 + + + + + 过程定义的一般形式 + + + 函数声明的最简单形式 + + + 是 + + +(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) + + +function name(parameters) { return expression; } + + + The + + + + 过程名称过程名称 + 过程的名称 + $\langle \textit{name}\rangle$ + + + 函数名称函数名称 + 的名称 + 名称 + + + 是与环境中的 + + + 过程 + + + 函数 + + + 定义关联的符号。在整本书中,我们将 + 本书中的符号表达式语法中的斜体符号 + 语法表达式的描述 + 通过使用斜体符号来描述表达式的一般语法 以尖括号 + 包围例如, + + + $\langle \textit{name}\rangle$表示 + + + 名称表示 + + + 表达式中需要被填入的,在实际使用这样的表达式时。 + The + + + 形式参数过程的 + 形式参数 + + + 的参数 + 参数 + + + + + $\langle \textit{形式参数}\rangle$ + + + 参数 + + + 是在 + + + 过程 + + + 函数 + + + 的主体中用于引用所对应论据的名称。 + + + 过程。 + + + 函数。 + + + + + 过程的 + 主体过程的 + 过程主体 + $\langle \textit{body} \rangle$ + 是一个表达式 + 当形式参数被实际参数替换,即过程被应用到的实际参数时,该表达式将产生过程应用的值。更 + 表达式序列过程在过程主体中 + 一般来说,过程的主体可以是一个表达式序列。在这种情况下,解释器依次评估序列中的每个表达式,并将最后一个表达式的值作为过程应用的值返回。 + $\langle \textit{name} \rangle$ + 和 + $\langle \textit{formal parameters} \rangle$ + 被括在一起 + 括号过程在过程定义中 + 过程定义的 + 括号内,就像在过程的实际调用中一样。 + + + 参数 + 被括在一起 + 括号函数在函数声明中 + 括号函数在函数声明中 + 括号内并用逗号分隔,就像在函数声明的应用中一样。 + 返回语句 + 返回值 + return (关键字) + 语法结构返回语句 + 关键字returnreturn + 在最简单的形式中, + 的主体 + 函数的主体 + 主体是一个单一的函数声明 + 返回语句更 + 语句序列函数在函数主体中 + 一般来说,函数的主体可以是一系列语句。在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 + 它由关键字 + return + 后跟返回表达式组成 + 当形式参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句一样, + 返回语句 + 分号 (;)结束语句 + 声明 + 以分号结束。 + + + + + Compound 过程 + 函数 + + + + We have identified in + + Lisp + JavaScript + + 一些必须出现在任何强大编程语言中的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + We begin by examining how to express the idea of + 平方。 + 我们可能会说, + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + + 这在我们的语言中表示为 + + square + function (keyword) + keywordsfunctionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + We can understand this in the following way: + + + +(define (square x) (* x x)) +;; ^ ^ ^ ^ ^ ^ +;; 要 平方 某个东西, 将其 乘以 自身。 + + +function square( x ) { return x * x; } +// ^ ^ ^ ^ ^ ^ ^ +// 要 平方 某个东西, 将其 乘以 自身。 + + + + + + + \begin{flushleft}\normalcodesize + \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} + \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ + \end{tabular} + \end{flushleft} + + + 我们这里有一个 + + + 复合过程 + 过程复合 + 复合过程, + + + 复合函数 + 复合 + 复合函数, + + + 它被赋予了一个名称 square. The + + 过程 + 函数 + + 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, + which plays the same role that a pronoun plays in natural language. + + + 过程命名的命名 + 过程使用define命名 + 过程使用define创建 + + + 函数命名的命名 + 使用函数声明命名 + 使用函数声明创建 + + + 评估 + + + 定义 + + + 声明 + + + 创建这个复合 + + + 过程 + + + 函数 + + + 并将其与名称关联 + 语法形式函数声明 + + 函数声明 + 声明函数函数的声明 (function) + square. 注意,这里结合了两种不同的操作:我们正在创建 + + + 过程, + + + 函数, + + + 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 + + + 过程 + + + 函数 + + + 而不命名它们,以及给已创建的 + + + 过程 + + + 函数 + + + 命名。在节中我们将看到如何做到这一点。 + + + + + 过程定义的一般形式 + + + 函数声明的最简单形式 + + + 是 + + +(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) + + +function name(parameters) { return expression; } + + + The + + + + 过程名称过程的名称 + 过程的名称 + $\langle \textit{name}\rangle$ + + + 函数名称函数的名称 + 的名称 + 名称 + + + 是要与环境中的 + + + 过程 + + + 函数 + + + 定义相关联的符号。在整本书中,我们将 + 本书中的符号表达式语法中的斜体符号 + 语法表达式的语法描述 + 通过使用斜体符号来描述表达式的一般语法 用尖括号 + 分隔例如, + + + $\langle \textit{name}\rangle$表示 + + + 名称表示 + + + 表达式中需要被填入的,在实际使用这样的表达式时。 + + + 过程形式参数 + 形式参数 + + + 的参数 + 参数 + + + + + $\langle \textit{formal parameters}\rangle$ + + + 参数 + + + 是过程的主体中用来引用 + 对应的实参的名称。 + + + 过程。 + + + 函数。 + + + + + 过程的 + 主体的过程 + 过程主体 + $\langle \textit{body} \rangle$ + 是一种表达式 + 当形式参数被 + 过程 + 应用的实际参数替换时,将产生 + 的值。更 + 表达式序列过程在过程主体中 + 通常,过程的主体可以是一个表达式序列。 + 在这种情况下,解释器依次评估序列中的每个表达式,并将最终表达式的值作为过程应用的值返回。 + $\langle \textit{name} \rangle$ + 和 + $\langle \textit{formal parameters} \rangle$ + 在 + 括号过程在过程定义中 + 过程定义的 + 括号内分组,就像在实际调用要定义的过程时一样。 + + + 参数 + 在 + 括号函数在函数声明中 + 括号函数在函数声明中 + 括号内分组并用逗号分隔,就像在函数声明的应用中一样。 + 返回语句 + 返回值 + return (关键字) + 语法形式返回语句 + 关键字returnreturn + 在最简单的形式中, + 主体的 + 函数主体 + 主体 是一个单一的函数声明 + 返回语句更 + 语句序列函数在函数主体中 + 一般来说,函数的主体可以是一系列语句。 + 在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 + 它由关键字 + return + 后跟返回表达式组成 + 当 + + 形式 + + 参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句, + 返回语句 + 分号 (;)结束语句 + 声明 + 以分号结束。 + + + + + + + 定义了square之后, + 我们现在可以使用它: + + + 声明了square之后, + 我们现在可以在 + 函数应用表达式中使用它,并通过使用分号将其转化为一个语句: + + + + square_definition + +(square 21) + + +441 + + +square(21); + + +441 + + + + + + + + + 由于运算符组合在句法上与函数应用不同,JavaScript版本需要在这里明确说明函数应用的评估规则。这为下一小节的替换模型奠定了基础。 + + 函数应用是继运算符 + 组合之后的我们遇到的第二种将 + 表达式组合成更大表达式的方法。 + 函数应用的一般形式是 + + +函数表达式(参数表达式) + + + 其中应用的 + 函数表达式 + 函数表达式 + 指定了要应用于以逗号分隔的 + 参数 + 参数表达式的函数。 + 为了评估函数应用,解释器遵循 + 评估函数应用 + 函数应用的评估 + 一个过程,与 + 在节中描述的运算符组合过程非常相似。 +
    +
  • 评估函数应用时,进行以下操作: +
      +
    1. + 评估应用的子表达式,即 + 函数表达式和参数表达式。 +
    2. +
    3. + 将函数表达式的值所代表的函数 + 应用到参数表达式的值上。 +
    4. +
    +
  • +
+
+
+ + square_definition + +(square (+ 2 5)) + + +49 + + +square(2 + 5); + + +49 + + + + + 这里,参数表达式本身是一个复合表达式, + 运算符组合 2 + 5。 + + + + square_square + 81 + square_definition + +(square (square 3)) + + +81 + + +square(square(3)); + + +81 + + + + + 当然,函数应用表达式也可以作为参数表达式。 + + +
+ + Compound 过程 + 函数 + + + + We have identified in + + Lisp + JavaScript + + 一些必须出现在任何强大编程语言中的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + We begin by examining how to express the idea of + 平方。 + 我们可能会说, + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + + 这在我们的语言中表示为 + + square + function (keyword) + keywordsfunctionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + We can understand this in the following way: + + + +(define (square x) (* x x)) +;; ^ ^ ^ ^ ^ ^ +;; 要 平方 某个东西, 将其 乘以 自身。 + + +function square( x ) { return x * x; } +// ^ ^ ^ ^ ^ ^ ^ +// 要 平方 某个东西, 将其 乘以 自身。 + + + + + + + \begin{flushleft}\normalcodesize + \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} + \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ + \end{tabular} + \end{flushleft} + + + 我们这里有一个 + + + 复合过程 + 过程复合 + 复合过程, + + + 复合函数 + 复合 + 复合函数, + + + 它被赋予了一个名称 square. The + + 过程 + 函数 + + 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, + which plays the same role that a pronoun plays in natural language. + + + 过程命名的命名 + 过程使用define命名 + 过程使用define创建 + + + 函数命名的命名 + 使用函数声明命名 + 使用函数声明创建 + + + 评估 + + + 定义 + + + 声明 + + + 创建这个复合 + + + 过程 + + + 函数 + + + 并将其与名称关联 + 语法形式函数声明 + + 函数声明 + 声明函数函数的 (function) + square. 注意,这里结合了两种不同的操作:我们正在创建 + + + 过程, + + + 函数, + + + 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 + + + 过程 + + + 函数 + + + 而不命名它们,以及给已创建的 + + + 过程 + + + 函数 + + + 命名。在节中我们将看到如何做到这一点。 + + + + + 过程定义的一般形式 + + + 函数声明的最简单形式 + + + 是 + + +(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) + + +function name(parameters) { return expression; } + + + The + + + 过程名称过程名称 + 过程的名称 + $\langle \textit{name}\rangle$ + + + 函数名称函数名称 + 的名称 + 名称 + + + 是要与环境中的 + + + 过程 + + + 函数 + + + 定义相关联的符号。在整个书中,我们将 + 本书中的符号表达式语法中的斜体符号 + 语法表达式描述表达式的语法 + 通过使用斜体符号来描述表达式的一般语法 以尖括号 + 分隔例如, + + + $\langle \textit{name}\rangle$表示 + + + 名称表示 + + + 实际使用表达式时需要填入的 + + + 过程形式参数 + 形式参数 + + + 参数 + 参数 + + + + + $\langle \textit{formal parameters}\rangle$ + + + 参数 + + + 是在 + + + 过程 + + + 函数 + + + 的主体中用于引用 + + + 过程 + + + 函数 + + + 对应参数的名称。 + + + 过程。 + + + 函数。 + + + + + The + 过程的主体 + 过程的主体 + $\langle \textit{body} \rangle$ + 是一个表达式 + 当形式参数被 + 实际参数替换,过程 + 被应用时,将产生过程应用的值。更 + 表达式序列过程在过程主体中 + 通常,过程的主体可以是一系列表达式。 + 在这种情况下,解释器依次评估序列中的每个表达式,并将最终表达式的值作为过程应用的值返回。 + $\langle \textit{name} \rangle$ + 和 + $\langle \textit{formal parameters} \rangle$ + 在 + 括号过程在过程定义中 + 过程的定义 + 括号内分组,就像在实际调用要定义的过程时一样。 + + + 参数 + 在 + 括号函数在函数声明中 + 括号函数在函数声明中 + 括号内分组并用逗号分隔,就像在函数声明的应用中一样。 + 返回语句 + 返回值 + return (关键字) + 语法形式返回语句 + 关键字returnreturn + 在最简单的形式中, + 主体的 + 函数的主体 + 主体 是一个单一的函数声明 + 返回语句更 + 语句序列函数在函数主体中 + 一般来说,函数的主体可以是一系列语句。 + 在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 + 它由关键字 + return + 后跟返回表达式组成 + 当 + + 形式 + + 参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句, + 返回语句 + 分号 (;)结束语句 + 声明 + 以分号结束。 + + + + + + + 定义了square之后, + 我们现在可以使用它: + + + 声明了square之后, + 我们现在可以在 + 函数应用表达式中使用它,并通过使用分号将其转化为一个语句: + + + + square_definition + +(square 21) + + +441 + + +square(21); + + +441 + + + + + + + + + 由于运算符组合在句法上与函数应用不同,JavaScript版本需要在这里明确说明函数应用的评估规则。这为下一小节的替换模型奠定了基础。 + + 函数应用是继运算符组合之后的我们遇到的第二种将表达式组合成更大表达式的方法。 + 函数应用的一般形式是 + + +函数表达式(参数表达式) + + + 其中应用的 + 函数表达式 + 函数表达式 + 指定应用于以逗号分隔的 + 参数 + 参数表达式的函数。 + 为了评估函数应用,解释器遵循 + 评估函数应用的 + 函数应用的评估 + 一个过程, + 与节中描述的运算符组合的过程非常相似。 +
    +
  • 评估函数应用时,执行以下操作: +
      +
    1. + 评估应用的子表达式,即函数表达式和参数表达式。 +
    2. +
    3. + 将函数表达式之值代表的函数应用于参数表达式之值。 +
    4. +
    +
  • +
+
+
+ + square_definition + +(square (+ 2 5)) + + +49 + + +square(2 + 5); + + +49 + + + + + 这里,参数表达式本身是一个复合表达式, + 运算符组合 2 + 5。 + + + + square_square + 81 + square_definition + +(square (square 3)) + + +81 + + +square(square(3)); + + +81 + + + + + 当然,函数应用表达式也可以作为参数表达式。 + + +
+ + + We can also use square + as a building block in defining other + + + 过程。 + + + 函数。 + + + 例如, $x^2 +y^2$ can be expressed as + + +(+ (square x) (square y)) + + +square(x) + square(y) + + + We can easily + + 定义 + 声明 + + 一个 + + + 过程 + sum-of-squares + + + 函数 + sum_of_squares多部分名称的书写方式,例如 + sum_of_squares,影响程序的可读性,不同的编程社区对此有不同的看法。 + 骆驼拼写 + 根据常见的JavaScript惯例,称为骆驼拼写, + 名称将是 + sumOfSquares。本书中使用的惯例称为蛇形拼写, + 命名惯例蛇形拼写 + 蛇形拼写 + 在本书的Scheme版本中使用的惯例更为相似,其中连字符承担了我们下划线的角色。 + + + 给定任意两个数字作为参数,产生它们平方和: + + sum_of_squares + sum_of_squares + 25 + sum_of_squares_example + square_definition + +(define (sum-of-squares x y) + (+ (square x) (square y))) + +(sum-of-squares 3 4) + + +function sum_of_squares(x, y) { + return square(x) + square(y); +} + + + + sum_of_squares_example + 25 + sum_of_squares + +(sum-of-squares 3 4) + + +25 + + +sum_of_squares(3, 4); + + +25 + + + Now we can use + + + sum-of-squares + + + sum_of_squares + + + 作为构建进一步 + + + 过程的构件: + + + 函数的构件: + + + + f + f_example + 136 + sum_of_squares + +(define (f a) + (sum-of-squares (+ a 1) (* a 2))) + + +function f(a) { + return sum_of_squares(a + 1, a * 2); +} + + + + f_example + f + +(f 5) + + +136 + + +f(5); + + +136 + + + + + Compound 过程 + 函数 + + + + We have identified in + + Lisp + JavaScript + + 一些必须出现在任何强大编程语言中的元素: +
    +
  • + 数字和算术运算是原始数据和 + + 过程。 + 函数。 + +
  • +
  • + 组合的嵌套提供了组合运算的方法。 +
  • +
  • + 将名称与值关联的常量声明提供了一种有限的抽象方式。 +
  • +
+ 现在我们将学习 + + + 过程定义 + + + 声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 +
+ + We begin by examining how to express the idea of + 平方。 + 我们可能会说, + + + 要平方某个东西,将其乘以自身。 + + + 要平方某个东西,将其乘以自身。 + + + + 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + + 这在我们的语言中表示为 + + square + function (keyword) + keywordsfunctionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + We can understand this in the following way: + + + +(define (square x) (* x x)) +;; ^ ^ ^ ^ ^ ^ +;; 要 平方 某个东西, 将其 乘以 自身。 + + +function square( x ) { return x * x; } +// ^ ^ ^ ^ ^ ^ ^ +// 要 平方 某个东西, 将其 乘以 自身。 + + + + + + + \begin{flushleft}\normalcodesize + \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} + \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ + \end{tabular} + \end{flushleft} + + + 我们这里有一个 + + + 复合过程 + 过程复合 + 复合过程, + + + 复合函数 + 复合 + 复合函数, + + + 它被赋予了一个名称 square. The + + 过程 + 函数 + + 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, + which plays the same role that a pronoun plays in natural language. + + + 过程命名的命名 + 过程使用define命名 + 过程使用define创建 + + + 函数命名的命名 + 使用函数声明命名 + 使用函数声明创建 + + + 评估 + + + 定义 + + + 声明 + + + 创建这个复合 + + + 过程 + + + 函数 + + + 并将其与名称关联 + 语法形式函数声明 + + 函数声明 + 声明函数的函数 (function) + square. 注意,这里结合了两种不同的操作:我们正在创建 + + + 过程, + + + 函数, + + + 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 + + + 过程 + + + 函数 + + + 而不命名它们,以及给已创建的 + + + 过程 + + + 函数 + + + 命名。在节中我们将看到如何做到这一点。 + + + + + 过程定义的一般形式 + + + 函数声明的最简单形式 + + + 是 + + +(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) + + +function name(parameters) { return expression; } + + + The + + + 过程名称过程名称 + 过程名称 + $\langle \textit{name}\rangle$ + + + 函数名称函数名称 + 名称 + 名称 + + + 是一个与环境中的 + + + 过程 + + + 函数 + + + 定义关联的符号。在整本书中,我们将 + 本书中的符号表达式语法中的斜体符号 + 语法表达式表达式的描述 + 使用斜体符号来描述表达式的一般语法 用尖括号 + 分隔例如, + + + $\langle \textit{name}\rangle$表示 + + + 名称表示 + + + 表达式中需要填入的,实际使用表达式时。 + + + 过程形式参数 + 形式参数 + + + 参数 + 参数 + + + + + $\langle \textit{formal parameters}\rangle$ + + + 参数 + + + 是在过程的主体中用于引用 + + + 过程 + + + 函数 + + + 对应参数的名称。 + + + 过程。 + + + 函数。 + + + + + 参数 + 在 + 括号函数在函数声明中 + 括号函数在函数声明中 + 括号内分组并用逗号分隔,就像在函数声明的应用中一样。 + 返回语句 + 返回值 + return (关键字) + 语法结构返回语句 + 关键字returnreturn + 在最简单的形式中, + 主体的 + 函数主体 + 主体 是一个单一的函数声明 + 返回语句更 + 语句序列函数在函数主体中 + 通常来说,函数的主体可以是一系列语句。 + 在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 + 它由关键字 + return + 后跟返回表达式组成 + 当 + + 形式 + + 参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句, + 返回语句 + 分号 (;)结束语句 + 声明 + 以分号结束。 + + + + + + + 定义了square之后, + 我们现在可以使用它: + + + 声明了square之后, + 我们现在可以在 + 函数应用表达式中使用它,并通过使用分号将其转化为一个语句: + + + + square_definition + +(square 21) + + +441 + + +square(21); + + +441 + + + + + + + + + 由于运算符组合在句法上与函数应用不同,JavaScript版本需要在这里明确说明函数应用的评估规则。这为下一小节的替换模型奠定了基础。 + + 函数应用是继运算符组合之后的我们遇到的第二种将表达式组合成更大表达式的方法。 + 函数应用的一般形式是 + + +函数表达式(参数表达式) + + + 其中应用的 + 函数表达式 + 函数表达式 + 指定应用于以逗号分隔的 + 参数 + 参数表达式的函数。 + 为了评估函数应用,解释器遵循 + 评估函数应用的 + 函数应用的评估 + 一个过程,与节中描述的运算符组合的过程非常相似。 +
    +
  • 评估函数应用时,执行以下操作: +
      +
    1. + 评估应用的子表达式,即函数表达式和参数表达式。 +
    2. +
    3. + 将函数表达式之值代表的函数应用于参数表达式之值。 +
    4. +
    +
  • +
+
+
+ + square_definition + +(square (+ 2 5)) + + +49 + + +square(2 + 5); + + +49 + + + + + 这里,参数表达式本身是一个复合表达式, + 运算符组合 2 + 5。 + + + + square_square + 81 + square_definition + +(square (square 3)) + + +81 + + +square(square(3)); + + +81 + + + + + 当然,函数应用表达式也可以作为参数表达式。 + + +
+ + + We can also use square + as a building block in defining other + + + 过程。 + + + 函数。 + + + 例如, $x^2 +y^2$ can be expressed as + + +(+ (square x) (square y)) + + +square(x) + square(y) + + + We can easily + + 定义 + 声明 + + 一个 + + + 过程 + sum-of-squares + + + 函数 + sum_of_squares多部分名称的书写方式,例如 + sum_of_squares,影响程序的可读性,不同的编程社区对此有不同的看法。 + 驼峰式拼写 + 根据常见的JavaScript惯例,称为驼峰式拼写, + 名称将是 + sumOfSquares。本书中使用的惯例称为蛇形拼写, + 命名惯例蛇形拼写 + 蛇形拼写 + 在本书的Scheme版本中使用的惯例更为相似,其中连字符承担了我们下划线的角色。 + + + 给定任意两个数字作为参数,产生它们平方和: + + sum_of_squares + sum_of_squares + 25 + sum_of_squares_example + square_definition + +(define (sum-of-squares x y) + (+ (square x) (square y))) + +(sum-of-squares 3 4) + + +function sum_of_squares(x, y) { + return square(x) + square(y); +} + + + + sum_of_squares_example + 25 + sum_of_squares + +(sum-of-squares 3 4) + + +25 + + +sum_of_squares(3, 4); + + +25 + + + Now we can use + + + sum-of-squares + + + sum_of_squares + + + 作为构建进一步 + + + 过程的构件: + + + 函数的构件: + + + + f + f_example + 136 + sum_of_squares + +(define (f a) + (sum-of-squares (+ a 1) (* a 2))) + + +function f(a) { + return sum_of_squares(a + 1, a * 2); +} + + + + f_example + f + +(f 5) + + +136 + + +f(5); + + +136 + + + + + + + 复合 + 复合过程像原始过程一样使用 + 过程的使用方式与原始过程完全相同。事实上,仅通过查看上面给出的sum-of-squares定义,我们无法判断square是内置于解释器中的,像+*一样,还是定义为复合过程。 + + + 除了复合函数之外,任何JavaScript环境还提供 + 原始 + 原始函数 + 内置在解释器中或从库中加载的原始函数。 + 本书中使用的JavaScript环境 + 除了运算符提供的原始函数外, + 本书使用的JavaScript环境包括额外的原始函数,例如函数 + math_log (原始函数) + math_logMath.log + math_log, + 它计算参数的自然对数。我们的 + JavaScript环境包括ECMAScript的所有函数和常量 + Math对象, + 以math_$\ldots$命名。 + ECMAScriptMathMath对象 + 例如,ECMAScript的Math.log + 可以作为math_log使用。 + MIT出版社 + 为本书提供的网页包括JavaScript包 + sicp JavaScript包 + JavaScript包 sicp + sicp,提供这些及书中认为是原始的所有其他JavaScript函数。 + + 这些附加的原始函数的使用方式与 + 复合函数像原始函数一样使用 + 复合函数完全相同;评估应用 + math_log(1) 的结果为数字0。 + 事实上,仅通过查看上面给出的 + sum_of_squares定义,我们无法判断 + square 是内置于解释器中的,还是从库中加载,或定义为复合函数。 + + +
\ No newline at end of file diff --git a/xml/cn/chapter1/section1/subsection5.xml b/xml/cn/chapter1/section1/subsection5.xml new file mode 100644 index 000000000..70e4745b0 --- /dev/null +++ b/xml/cn/chapter1/section1/subsection5.xml @@ -0,0 +1,3394 @@ + + + + + 过程应用的替代模型 + + + 函数应用的替代模型 + + + + + 过程应用的替代模型 + + + 函数应用的替代模型 + + + + 过程及 + + + 函数 + + + 函数 + + + 应用的替代模型 + + + + 过程应用的替代模型 + + + 函数应用的替代模型 + + + + 过程及 + + + 函数 + + + 函数 + + + 应用的替代模型 + + + + + + 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程与其操作符命名原始过程的组合大致相同,我们在 + 中描述了该过程。 + + + 为了评估一个函数应用,解释器遵循在中描述的过程。 + + + 也就是说,解释器评估 + + + 组合 + + + 应用 + + + 的元素,并应用 + + + 过程 + + + 函数 + + + (它是 + + + 组合的操作员 + + + 应用的函数表达式 + + + 的值)到参数(它们是 + + + 组合的操作数 + + + 应用的参数表达式 + + + 的值)。 + + + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 + + + 为了评估一个函数应用,解释器遵循在中描述的过程。 + + + 也就是说,解释器评估 + + + 组合的元素 + + + 应用的元素 + + + 并应用 + + + 过程 + + + 函数 + + + (它是 + + + 组合的操作员的值) + + + 应用的函数表达式的值) + + + 到参数(它们是 + + + 组合的操作数的值)。 + + + 应用的参数表达式的值)。 + + + + + + + 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 + + + 我们可以假设基本函数的应用由解释器或库处理。 + + + 对于复合 + + + 过程, + + + 函数, + + + 应用过程如下: +
    +
  • + 要应用一个复合 + + + 过程 + + + 函数 + + + 于参数, + + + 评估过程的主体 + + + 评估函数的返回表达式 + + + 将每个 + + 形式 + + 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 +
  • +
+ 为了说明这个过程,让我们评估 + + + 组合 + + + 应用 + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + 定义的过程 + + + 声明的函数 + + + 在章节中。 + 我们首先检索 + + + 主体 + + + 返回表达式 + + + 的 +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + 定义 + + + 声明 + + + 的 +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 + + + 为了评估一个函数应用,解释器遵循在中描述的过程。 + + + 也就是说,解释器评估 + + + 组合的元素 + + + 应用的元素 + + + 并应用 + + + 过程 + + + 函数 + + + (它是 + + + 组合的操作员的值) + + + 应用的函数表达式的值) + + + 到参数(它们是 + + + 组合的操作数的值)。 + + + 应用的参数表达式的值)。 + + + + + + + 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 + + + 我们可以假设基本函数的应用由解释器或库处理。 + + + 对于复合 + + + 过程, + + + 函数, + + + 应用过程如下: +
    +
  • + 要应用一个复合 + + + 过程 + + + 函数 + + + 于参数, + + + 评估过程的主体 + + + 评估函数的返回表达式 + + + 将每个 + + 形式 + + 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 +
  • +
+ 为了说明这个过程,让我们评估 + + + 组合 + + + 应用 + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + 定义的过程 + + + 声明的函数 + + + 在章节中。 + 我们首先检索 + + + 主体 + + + 返回表达式 + + + 的 +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + definition + + + declaration + + + 的 +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 + + + 为了评估一个函数应用,解释器遵循在中描述的过程。 + + + 也就是说,解释器评估 + + + 组合的元素 + + + 应用的元素 + + + 并应用 + + + 过程 + + + 函数 + + + (它是 + + + 组合的操作员的值) + + + 应用的函数表达式的值) + + + 到参数(它们是 + + + 组合的操作数的值)。 + + + 应用的参数表达式的值)。 + + + + + + + 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 + + + 我们可以假设基本函数的应用由解释器或库处理。 + + + 对于复合 + + + 过程, + + + 函数, + + + 应用过程如下: +
    +
  • + 要应用一个复合 + + + 过程 + + + 函数 + + + 于参数, + + + 评估过程的主体 + + + 评估函数的返回表达式 + + + 将每个 + + 形式 + + 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 +
  • +
+ 为了说明这个过程,让我们评估 + + + 组合 + + + 应用 + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + 定义的过程 + + + 声明的函数 + + + 在章节中。 + 我们首先检索 + + + 主体 + + + 返回表达式 + + + 的 +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + definition + + + declaration + + + 的 +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + The process we have just described is called the substitution + model for + + procedure + function + + application. It can be taken as a model that + determines the meaning of + + procedure + function + + application, insofar as the + + procedures + functions + + in this chapter are concerned. However, there are two + points that should be stressed: +
    +
  • + 替换的目的是帮助我们思考 + + 过程 + 函数 + + 应用,而不是提供解释器实际工作方式的描述。典型的解释器不会通过操作 + + 过程 + 函数 + + 的文本来替换形式参数的值来评估应用程序。实际上,"替换"是通过为 + + 形式 + + + + + 参数使用局部环境来完成的。我们将在第3章和第4章中更加充分地讨论这个问题,当我们详细检查解释器的实现时。 +
  • +
  • + 在这本书中,我们将展示一系列不断复杂的解释器工作模型,最终在第章获得一个完整的解释器和编译器实现。替换模型只是这些模型中的第一个——通过正式考虑评估过程来入门的一种方式。一般来说,当 + 建模在科学和工程中 + 科学和工程中建模现象时,我们从简化的、不完整的模型开始。当我们更详细地研究事物时,这些简单模型变得不够,需要用更精细的模型来代替。替换模型也不例外。特别是,当我们在第章讨论使用 + + 过程 + 函数 + + 与“可变数据”时,我们将看到替换模型会崩溃,必须用更复杂的 + + 过程 + 函数 + + 应用模型来替代。尽管替换思想很简单,但给出替换过程的严格数学定义却出奇地复杂。问题源于可能存在对 + + 过程的形式参数 + 函数的参数 + + 的名称和可能用于应用的表达式中(可能相同)名称之间的混淆。实际上,在逻辑和编程语义的文献中,存在着对替换的错误定义的悠久历史。 + Stoy, Joseph E. + 详细讨论了替换,请参见Stoy 1977 +
  • +
+
+ + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 + + + 为了评估一个函数应用,解释器遵循在中描述的过程。 + + + 也就是说,解释器评估 + + + 组合的元素 + + + 应用的元素 + + + 并应用 + + + 过程 + + + 函数 + + + (它是 + + + 组合的操作员的值) + + + 应用的函数表达式的值) + + + 到参数(它们是 + + + 组合的操作数的值)。 + + + 应用的参数表达式的值)。 + + + + + + + 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 + + + 我们可以假设基本函数的应用由解释器或库处理。 + + + 对于复合 + + + 过程, + + + 函数, + + + 应用过程如下: +
    +
  • + 要应用一个复合 + + + 过程 + + + 函数 + + + 于参数, + + + 评估过程的主体 + + + 评估函数的返回表达式 + + + 将每个 + + 形式 + + 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 +
  • +
+ 为了说明这个过程,让我们评估 + + + 组合 + + + 应用 + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + 定义的过程 + + + 声明的函数 + + + 在章节中。 + 我们首先检索 + + + 主体 + + + 返回表达式 + + + 的 +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + definition + + + declaration + + + 的 +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + The process we have just described is called the substitution + model for + + procedure + function + + application. It can be taken as a model that + determines the meaning of + + procedure + function + + application, insofar as the + + procedures + functions + + in this chapter are concerned. However, there are two + points that should be stressed: +
    +
  • + 替换的目的是帮助我们思考 + + 过程 + 函数 + + 应用,而不是提供解释器实际工作方式的描述。典型的解释器不会通过操作 + + 过程 + 函数 + + 的文本来替换形式参数的值来评估应用程序。实际上,"替换"是通过为 + + 形式 + + + + + 参数使用局部环境来完成的。我们将在第3章和第4章中更加充分地讨论这个问题,当我们详细检查解释器的实现时。 +
  • +
  • + 在这本书中,我们将展示一系列不断复杂的解释器工作模型,最终在第章获得一个完整的解释器和编译器实现。替换模型只是这些模型中的第一个——通过正式考虑评估过程来入门的一种方式。一般来说,当 + 建模在科学和工程中 + 科学和工程中建模现象时,我们从简化的、不完整的模型开始。当我们更详细地研究事物时,这些简单模型变得不够,需要用更精细的模型来代替。替换模型也不例外。特别是,当我们在第章讨论使用 + + 过程 + 函数 + + 与“可变数据”时,我们将看到替换模型会崩溃,必须用更复杂的 + + 过程 + 函数 + + 应用模型来替代。尽管替换思想很简单,但给出替换过程的严格数学定义却出奇地复杂。问题源于可能存在对 + + 过程的形式参数 + 函数的参数 + + 的名称和可能用于应用的表达式中(可能相同)名称之间的混淆。实际上,在逻辑和编程语义的文献中,存在着对替换的错误定义的悠久历史。 + Stoy, Joseph E. + 详细讨论了替换,请参见Stoy 1977 +
  • +
+
+ + + Applicative order versus normal order + + + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + To evaluate a combination whose operator names a compound procedure, the + interpreter follows much the same process as for combinations whose + operators name primitive procedures, which we described in + section. + + + To evaluate a function application, the interpreter follows the process + described in section. + + + That is, the interpreter evaluates the elements of the + + + combination + + + application + + + and applies the + + + procedure + + + function + + + (which is the value of the + + + operator of the combination) + + + function expression of the application) + + + to the arguments (which are the values of the + + + operands of the combination). + + + argument expressions of the application). + + + + + + + We can assume that the mechanism for applying primitive + procedures + to arguments is built into the interpreter. + + + We can assume that the application of primitive + functions is handled by the interpreter or libraries. + + + For compound + + + procedures, + + + functions, + + + the application process is as follows: +
    +
  • + To apply a compound + + + procedure + + + function + + + to arguments, + + + evaluate the body of the procedure + + + evaluate the return expression of the function + + + with each + + formal + + parameter replaced by the corresponding argument.If the + body of the function is a sequence of statements, the + body is evaluated with the parameters replaced, and the value of the + application is the value of the return expression of the first + return statement encountered. +
  • +
+ To illustrate this process, lets evaluate the + + + combination + + + application + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + procedure defined + + + function declared + + + in section. + We begin by retrieving the + + + body + + + return expression + + + of +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + definition + + + declaration + + + of +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + The process we have just described is called the substitution + model for + + procedure + function + + application. It can be taken as a model that + determines the meaning of + + procedure + function + + application, insofar as the + + procedures + functions + + in this chapter are concerned. However, there are two + points that should be stressed: +
    +
  • + The purpose of the substitution is to help us think about + + procedure + function + + application, not to provide a description of how the interpreter + really works. Typical interpreters do not evaluate + + procedure + function + + applications by manipulating the text of a + + procedure to substitute values for the formal + function to substitute values for the + + parameters. In practice, the substitution is + accomplished by using a local environment for the + + formal + + + + + parameters. We will discuss this more fully in chapters 3 and + 4 when we examine the implementation of an interpreter in detail. +
  • +
  • + Over the course of this book, we will present a sequence of + increasingly elaborate models of how interpreters work, culminating + with a complete implementation of an interpreter and compiler in + chapter. The substitution model is only the first of + these modelsa way to get started thinking formally + about the evaluation process. In general, when + modelingin science and engineering + modeling phenomena in science and engineering, we begin with + simplified, incomplete models. As we examine things in greater detail, + these simple models become inadequate and must be replaced by more + refined models. The substitution model is no exception. In particular, + when we address in chapter the use of + + procedures + functions + + with mutable data, we will see that the substitution + model breaks down and must be replaced by a more complicated model of + + procedure + function + + application.Despite the + simplicity of the substitution idea, it turns out to be + surprisingly complicated to give a rigorous mathematical + definition of the substitution process. The problem arises + from the possibility of confusion between the names used for the + + formal parameters of a procedure + parameters of a function + + and the (possibly identical) names used in the expressions + to which the + + procedure + function + + may be applied. Indeed, there is a long + history of erroneous definitions of substitution in the + literature of logic and programming semantics. + Stoy, Joseph E. + See Stoy 1977 for a + careful discussion of substitution. +
  • +
+
+ + + Applicative order versus normal order + + + + According to the description of evaluation given in + + + section, + + + section, + + + the interpreter first evaluates the + + + operator + + + function + + + and + + + operands + + + argument expressions + + + and then applies the resulting + + + procedure + + + function + + + to the resulting arguments. This is not the only way to perform evaluation. + An alternative evaluation model would not evaluate the + + + operands + + + arguments + + + until their values were needed. Instead it would first substitute + + + operand + + + argument + + + expressions for parameters until it obtained an expression involving + only + + primitive operators, + operators and primitive functions, + + and would then perform the evaluation. If we + used this method, the evaluation of + + +(f 5) + + +f(5) + + + would proceed according to the sequence of expansions + + +(sum-of-squares (+ 5 1) (* 5 2)) + +(+ (square (+ 5 1)) (square (* 5 2)) ) + +(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2))) + + +sum_of_squares(5 + 1, 5 * 2) + +square(5 + 1) + square(5 * 2) + +(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2) + + + followed by the reductions + + +(+ (* 6 6) (* 10 10)) + +(+ 36 100) + + 136 + + +6 * 6 + 10 * 10 + + 36 + 100 + + 136 + + + This gives the same answer as our previous evaluation model, but the + process is different. In particular, the evaluations of + + + (+ 5 1) + + + 5 + 1 + + + and + + + (* 5 2) + + + 5 * 2 + + + are each performed twice here, corresponding to the reduction of the + expression + + +(* x x) + + +x * x + + + with x replaced respectively by + + + (+ 5 1) + + + 5 + 1 + + + and + + + (* 5 2). + + + 5 * 2. + + + + + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + To evaluate a combination whose operator names a compound procedure, the + interpreter follows much the same process as for combinations whose + operators name primitive procedures, which we described in + section. + + + To evaluate a function application, the interpreter follows the process + described in section. + + + That is, the interpreter evaluates the elements of the + + + combination + + + application + + + and applies the + + + procedure + + + function + + + (which is the value of the + + + operator of the combination) + + + function expression of the application) + + + to the arguments (which are the values of the + + + operands of the combination). + + + argument expressions of the application). + + + + + + + We can assume that the mechanism for applying primitive + procedures + to arguments is built into the interpreter. + + + We can assume that the application of primitive + functions is handled by the interpreter or libraries. + + + For compound + + + procedures, + + + functions, + + + the application process is as follows: +
    +
  • + To apply a compound + + + procedure + + + function + + + to arguments, + + + evaluate the body of the procedure + + + evaluate the return expression of the function + + + with each + + formal + + parameter replaced by the corresponding argument.If the + body of the function is a sequence of statements, the + body is evaluated with the parameters replaced, and the value of the + application is the value of the return expression of the first + return statement encountered. +
  • +
+ To illustrate this process, lets evaluate the + + + combination + + + application + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + procedure defined + + + function declared + + + in section. + We begin by retrieving the + + + body + + + return expression + + + of +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + definition + + + declaration + + + of +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + The process we have just described is called the substitution + model for + + procedure + function + + application. It can be taken as a model that + determines the meaning of + + procedure + function + + application, insofar as the + + procedures + functions + + in this chapter are concerned. However, there are two + points that should be stressed: +
    +
  • + The purpose of the substitution is to help us think about + + procedure + function + + application, not to provide a description of how the interpreter + really works. Typical interpreters do not evaluate + + procedure + function + + applications by manipulating the text of a + + procedure to substitute values for the formal + function to substitute values for the + + parameters. In practice, the substitution is + accomplished by using a local environment for the + + formal + + + + + parameters. We will discuss this more fully in chapters 3 and + 4 when we examine the implementation of an interpreter in detail. +
  • +
  • + Over the course of this book, we will present a sequence of + increasingly elaborate models of how interpreters work, culminating + with a complete implementation of an interpreter and compiler in + chapter. The substitution model is only the first of + these modelsa way to get started thinking formally + about the evaluation process. In general, when + modelingin science and engineering + modeling phenomena in science and engineering, we begin with + simplified, incomplete models. As we examine things in greater detail, + these simple models become inadequate and must be replaced by more + refined models. The substitution model is no exception. In particular, + when we address in chapter the use of + + procedures + functions + + with mutable data, we will see that the substitution + model breaks down and must be replaced by a more complicated model of + + procedure + function + + application.Despite the + simplicity of the substitution idea, it turns out to be + surprisingly complicated to give a rigorous mathematical + definition of the substitution process. The problem arises + from the possibility of confusion between the names used for the + + formal parameters of a procedure + parameters of a function + + and the (possibly identical) names used in the expressions + to which the + + procedure + function + + may be applied. Indeed, there is a long + history of erroneous definitions of substitution in the + literature of logic and programming semantics. + Stoy, Joseph E. + See Stoy 1977 for a + careful discussion of substitution. +
  • +
+
+ + + Applicative order versus normal order + + + + According to the description of evaluation given in + + + section, + + + section, + + + the interpreter first evaluates the + + + operator + + + function + + + and + + + operands + + + argument expressions + + + and then applies the resulting + + + procedure + + + function + + + to the resulting arguments. This is not the only way to perform evaluation. + An alternative evaluation model would not evaluate the + + + operands + + + arguments + + + until their values were needed. Instead it would first substitute + + + operand + + + argument + + + expressions for parameters until it obtained an expression involving + only + + primitive operators, + operators and primitive functions, + + and would then perform the evaluation. If we + used this method, the evaluation of + + +(f 5) + + +f(5) + + + would proceed according to the sequence of expansions + + +(sum-of-squares (+ 5 1) (* 5 2)) + +(+ (square (+ 5 1)) (square (* 5 2)) ) + +(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2))) + + +sum_of_squares(5 + 1, 5 * 2) + +square(5 + 1) + square(5 * 2) + +(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2) + + + followed by the reductions + + +(+ (* 6 6) (* 10 10)) + +(+ 36 100) + + 136 + + +6 * 6 + 10 * 10 + + 36 + 100 + + 136 + + + This gives the same answer as our previous evaluation model, but the + process is different. In particular, the evaluations of + + + (+ 5 1) + + + 5 + 1 + + + and + + + (* 5 2) + + + 5 * 2 + + + are each performed twice here, corresponding to the reduction of the + expression + + +(* x x) + + +x * x + + + with x replaced respectively by + + + (+ 5 1) + + + 5 + 1 + + + and + + + (* 5 2). + + + 5 * 2. + + + + + This alternative fully expand and then reduce + evaluation method is known as + normal-order evaluation + normal-order evaluation, in contrast to the evaluate + the arguments and then apply method that the interpreter actually + uses, which is called + applicative-order evaluation + applicative-order evaluation. It can be shown that, for + + procedure + function + + applications that can be modeled using substitution (including all the + + procedures + functions + + in the first two chapters of this book) and that yield legitimate values, + normal-order and applicative-order evaluation produce the same value. + (See exercise + for an instance of an illegitimate value where normal-order + and applicative-order evaluation do not give the same result.) + + + + substitution model of procedure application + + + substitution model of function application + + + + The Substitution Model for + + + Procedure + + + Function + + + Application + + + + + + To evaluate a combination whose operator names a compound procedure, the + interpreter follows much the same process as for combinations whose + operators name primitive procedures, which we described in + section. + + + To evaluate a function application, the interpreter follows the process + described in section. + + + That is, the interpreter evaluates the elements of the + + + combination + + + application + + + and applies the + + + procedure + + + function + + + (which is the value of the + + + operator of the combination) + + + function expression of the application) + + + to the arguments (which are the values of the + + + operands of the combination). + + + argument expressions of the application). + + + + + + + We can assume that the mechanism for applying primitive + procedures + to arguments is built into the interpreter. + + + We can assume that the application of primitive + functions is handled by the interpreter or libraries. + + + For compound + + + procedures, + + + functions, + + + the application process is as follows: +
    +
  • + To apply a compound + + + procedure + + + function + + + to arguments, + + + evaluate the body of the procedure + + + evaluate the return expression of the function + + + with each + + formal + + parameter replaced by the corresponding argument.If the + body of the function is a sequence of statements, the + body is evaluated with the parameters replaced, and the value of the + application is the value of the return expression of the first + return statement encountered. +
  • +
+ To illustrate this process, lets evaluate the + + + combination + + + application + + + + f_of_five + f + 136 + +(f 5) + + +f(5) + + +f(5); + + + where f is the + + + procedure defined + + + function declared + + + in section. + We begin by retrieving the + + + body + + + return expression + + + of +f: + + f + +(sum-of-squares (+ a 1) (* a 2)) + + +sum_of_squares(a + 1, a * 2) + + + Then we replace the parameter a + by the argument 5: + + +(sum-of-squares (+ 5 1) (* 5 2)) + + +sum_of_squares(5 + 1, 5 * 2) + + + Thus the problem reduces to the evaluation of + + + 一个组合 + + + 一个应用 + + + 具有两个 + + + 操作数 + + + 参数 + + + 和 + + + 一个运算符 sum-of-squares。 + + + 一个函数表达式 sum_of_squares。 + + + 评估这个 + + + 组合 + + + 应用 + + + 涉及三个子问题。我们必须评估 + + 运算符 + 函数表达式 + + 以获得要应用的 + + + 过程 + + + 函数 + + + ,并且我们必须评估 + + + 操作数 + + + 参数表达式 + + + 以获得参数。现在 + + + (+ 5 1) + + + 5 + 1 + + + 产生 6 而 + + + (* 5 2) + + + 5 * 2 + + + 产生 10,因此我们必须将 + + + sum-of-squares 过程 + + + sum_of_squares 函数 + + + 应用于 6 和 10。这些值被替代为 + 形式 + 参数 +x and + y in the body of + + + sum-of-squares, + + + sum_of_squares, + + + reducing the expression to + + +(+ (square 6) (square 10)) + + +square(6) + square(10) + + + If we use the + + + definition + + + declaration + + + of +square, this reduces to + + +(+ (* 6 6) (* 10 10)) + + +(6 * 6) + (10 * 10) + + + which reduces by multiplication to + + +(+ 36 100) + + +36 + 100 + + + and finally to + + +136 + + +136 + + +
+ + + The process we have just described is called the substitution + model for + + procedure + function + + application. It can be taken as a model that + determines the meaning of + + procedure + function + + application, insofar as the + + procedures + functions + + in this chapter are concerned. However, there are two + points that should be stressed: +
    +
  • + The purpose of the substitution is to help us think about + + procedure + function + + application, not to provide a description of how the interpreter + really works. Typical interpreters do not evaluate + + procedure + function + + applications by manipulating the text of a + + procedure to substitute values for the formal + function to substitute values for the + + parameters. In practice, the substitution is + accomplished by using a local environment for the + + formal + + + + + parameters. We will discuss this more fully in chapters 3 and + 4 when we examine the implementation of an interpreter in detail. +
  • +
  • + Over the course of this book, we will present a sequence of + increasingly elaborate models of how interpreters work, culminating + with a complete implementation of an interpreter and compiler in + chapter. The substitution model is only the first of + these modelsa way to get started thinking formally + about the evaluation process. In general, when + modelingin science and engineering + modeling phenomena in science and engineering, we begin with + simplified, incomplete models. As we examine things in greater detail, + these simple models become inadequate and must be replaced by more + refined models. The substitution model is no exception. In particular, + when we address in chapter the use of + + procedures + functions + + with mutable data, we will see that the substitution + model breaks down and must be replaced by a more complicated model of + + procedure + function + + application.Despite the + simplicity of the substitution idea, it turns out to be + surprisingly complicated to give a rigorous mathematical + definition of the substitution process. The problem arises + from the possibility of confusion between the names used for the + + formal parameters of a procedure + parameters of a function + + and the (possibly identical) names used in the expressions + to which the + + procedure + function + + may be applied. Indeed, there is a long + history of erroneous definitions of substitution in the + literature of logic and programming semantics. + Stoy, Joseph E. + See Stoy 1977 for a + careful discussion of substitution. +
  • +
+
+ + + Applicative order versus normal order + + + + According to the description of evaluation given in + + + section, + + + section, + + + the interpreter first evaluates the + + + operator + + + function + + + and + + + operands + + + argument expressions + + + and then applies the resulting + + + procedure + + + function + + + to the resulting arguments. This is not the only way to perform evaluation. + An alternative evaluation model would not evaluate the + + + operands + + + arguments + + + until their values were needed. Instead it would first substitute + + + operand + + + argument + + + expressions for parameters until it obtained an expression involving + only + + primitive operators, + operators and primitive functions, + + and would then perform the evaluation. If we + used this method, the evaluation of + + +(f 5) + + +f(5) + + + would proceed according to the sequence of expansions + + +(sum-of-squares (+ 5 1) (* 5 2)) + +(+ (square (+ 5 1)) (square (* 5 2)) ) + +(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2))) + + +sum_of_squares(5 + 1, 5 * 2) + +square(5 + 1) + square(5 * 2) + +(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2) + + + followed by the reductions + + +(+ (* 6 6) (* 10 10)) + +(+ 36 100) + + 136 + + +6 * 6 + 10 * 10 + + 36 + 100 + + 136 + + + This gives the same answer as our previous evaluation model, but the + process is different. In particular, the evaluations of + + + (+ 5 1) + + + 5 + 1 + + + 和 + + + (* 5 2) + + + 5 * 2 + + + 在此处执行两次,对应于表达式的简化 + + +(* x x) + + +x * x + + + with x replaced respectively by + + + (+ 5 1) + + + 5 + 1 + + + 和 + + + (* 5 2). + + + 5 * 2. + + + + + This alternative fully expand and then reduce + evaluation method is known as + normal-order evaluation + normal-order evaluation, in contrast to the evaluate + the arguments and then apply method that the interpreter actually + uses, which is called + applicative-order evaluation + applicative-order evaluation. It can be shown that, for + + procedure + function + + applications that can be modeled using substitution (including all the + + procedures + functions + + in the first two chapters of this book) and that yield legitimate values, + normal-order and applicative-order evaluation produce the same value. + (See exercise + for an instance of an illegitimate value where normal-order + and applicative-order evaluation do not give the same result.) + + + + + Lisp + applicative-order evaluationLispin Lisp + Lispapplicative-order evaluation in + + + JavaScript + applicative-order evaluationJavaScriptin JavaScript + JavaScriptapplicative-order evaluation in + + + uses applicative-order evaluation, partly because of the + additional efficiency obtained from avoiding multiple evaluations of + expressions such as those illustrated with + + + (+ 5 1) + and (* 5 2) + + + 5 + 1 + and 5 * 2 + + + above and, more significantly, because normal-order evaluation + becomes much more complicated to deal with when we leave the realm of + + procedures + functions + + that can be modeled by substitution. On the other hand, + normal-order evaluation can be an extremely valuable tool, and we will + investigate some of its implications in chapters 3 and 4.In + chapter 3 we will introduce stream processing, which is a + way of handling apparently infinite data structures + by incorporating a limited form of normal-order evaluation. In + section we will modify the + Scheme + JavaScript + + interpreter to produce a normal-order variant of + + Scheme. + JavaScript. + + + + substitution model of procedure application + + + substitution model of function application + + +
\ No newline at end of file diff --git a/xml/cn/chapter1/section1/subsection6.xml b/xml/cn/chapter1/section1/subsection6.xml new file mode 100644 index 000000000..be6de1d3d --- /dev/null +++ b/xml/cn/chapter1/section1/subsection6.xml @@ -0,0 +1,952 @@ + + + 条件表达式和谓词 + + + + + The expressive power of the class of + + + 过程 + + + 函数 + + + + For instance, we cannot define a procedure that computes the + 绝对值 + \[\begin{array}{lll} + |x| & = & \left\{ \begin{array}{rl} + x & \mbox{if $x>0$} \\ + 0 & \mbox{if $x=0$} \\ + -x & \mbox{if $x<0$} + \end{array} + \right. + \end{array}\] + + This construct is called a + 案例分析通用案例分析条件特殊形式(那些标注为 ns 的不在 IEEE Scheme 标准之内)条件条件条件表达式条件条件cond (which stands for + 条件 + abs + abs_definition + abs_example + +(define (abs x) + (cond ((> x 0) x) + ((= x 0) 0) + ((< x 0) (- x)))) + + + + abs_example + +(abs (- 5)) + + + The general form of a conditional expression is + + +(cond ($\langle p_1 \rangle $ $\langle e_1 \rangle$) + ($\langle p_2 \rangle $ $\langle e_2 \rangle$) + $\vdots$ + ($\langle p_n \rangle $ $\langle e_n \rangle$)) + + + consisting of the symbol cond followed by + 括号限定条件限定条件子句($\langle p\ e \rangle$) + called + 子句,条件条件子句子句谓词条件条件的子句条件表达式谓词,结果,备选谓词 + 解释为 + + + \#t + + \#f + + 真或假 + 意思是这样的:在 Scheme 中,有两个特殊的值,由常量 #t#f 表示。 + 当解释器检查一个谓词的值时,它将 #f 解释为假。任何其他值 + 被视为真。(因此,提供 #t + 在逻辑上是不必要的,但很方便。)在本书中,我们将使用名称 true 和 + false, + 分别与值 #t 和 + #f 关联。 + + 我们从条件表达式开始,因为它们更适合替代模型。注意,第 1.1.1 至 1.1.7 节中的所有函数都以单一的返回语句为主体,这强调了条件表达式的作用。条件语句将在 1.3.2 节介绍。 + 绝对值 + \[\begin{array}{lll} + |x| & = & \left\{ \begin{array}{rl} + x & \mbox{if $x \geq 0$} \\ + -x & \mbox{otherwise} + \end{array} + \right. + \end{array}\] + + 我们简化了示例,以便可以只使用一个条件表达式。 + 案例分析案例分析条件表达式 + abs + 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 如果$x$大于或等于零,则返回$x$;否则返回$- x$ + +predicate ? consequent-expression : alternative-expression + + + Conditional + 条件表达式语法形式条件表达式?:;3谓词条件条件表达式的truetrue(关键字)falsefalse(关键字)关键字truetrue关键字falsefalse表达式原始布尔型布尔值(true, false)谓词truefalse布尔truefalse谓词结果表达式备选表达式 + + + + + + 条件表达式是 + 条件的计算 + 计算条件条件 + 按如下方式计算。谓词 + $\langle p_1 \rangle$ 首先被计算。 + 如果其值为假,则 + $\langle p_2 \rangle$ 被计算。 + 如果 $\langle p_2 \rangle$ 的 + 值也为假,则 + $\langle p_3 \rangle$ 被计算。 + 这个过程继续,直到找到一个值为真的谓词,在这种情况下,解释器会返回 + 对应的 + 结果条件条件子句 + 结果表达式 + $\langle e \rangle$ 作为该子句的值作为条件表达式的值。如果所有的 + $\langle p \rangle$ 都不为真,则 条件 的值是未定义的。 + + + + + 要 + 计算条件表达式的 + 条件表达式的计算 + 计算一个条件表达式, + 解释器首先计算该表达式的 + 谓词。 + 如果该谓词的值为真,解释器会计算 + 结果条件表达式的条件表达式 + 结果表达式并以其值作为条件的值返回。 + 如果谓词 + 的值为假,则会计算 + 备选条件表达式的条件表达式 + 备选表达式并以其值作为 + 条件的值返回。 + 条件表达式非布尔值作为谓词 + 条件语句 + + 在完整的 JavaScript 中接受任意值,不仅是布尔型,作为 + 谓词表达式的计算结果(详见脚注 + 在第节)。本书中的程序 + 只使用布尔值作为条件语句的谓词。 + + + + + + + 术语 + 谓词 + 谓词用于返回真或假的过程,以及评估为真或假的表达式。 + 绝对值过程 abs 使用 + >(原始数值比较谓词) + <(原始数值比较谓词) + =(原始数值相等谓词) + 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)>> + 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)<< + 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)== + 数值比较 + 数值相等 + 相等数值数值 + 原始谓词 >、 + < 和 + = + Abs 还使用 + -(原始减法过程)作为取负 + 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)-- + 操作符减号-, + 当与单一操作数一起使用时,如(- x), + 表示取反。 这些以两个数作为参数, + 分别测试第一个数是否大于、小于或等于第二个数,并返回相应的真假值。 + + + + 术语 + 谓词 + 谓词用于返回真或假的运算符和函数,以及评估为真或假的表达式。绝对值函数 + abs 使用 + >=(数值比较运算符) + >=(数值比较) + 数值比较 + 数值相等 + 原始谓词 >=, + 一个比较运算符,需要两个数作为参数并测试第一个数是否大于或等于第二个数,并返回相应的真假值。 + + + + + + 另一种编写绝对值过程的方法是 + + abs + abs_example + +(define (abs x) + (cond ((< x 0) (- x)) + (else x))) + + + 英文表述为 + 如果 $x$ 小于零 + 返回 $- x$;否则返回 + $x$ + else条件中的特殊符号) + Else 是一个特殊符号,可以用在 + $\langle p \rangle$ 在 + 条件的最终子句中。 + 这会使 条件 返回其值 + 当所有前面的子句都被跳过时的对应 + $\langle e \rangle$ 的值。 + 事实上,任何总是评估为真值的表达式都可以用作这里的 + $\langle p \rangle$。 + + + + + + + + + Here is yet another way to write the absolute-value procedure: + + abs + abs_example + +(define (abs x) + (if (< x 0) + (- x) + x)) + + 条件表达式ififif特殊形式(那些标注为ns的不在 IEEE Scheme 标准之内)ififif, a restricted + type of conditional that can be used when there are precisely + 案例分析两种两种情况(if)if + expression is + + +(if $\langle \textit{predicate} \rangle$ $\langle \textit{consequent}\rangle$ $\langle \textit{alternative}\rangle$) + + + To + if的计算计算ififif谓词,结果和备选if expression, + the interpreter starts by evaluating the + 谓词ifif$\langle \textit{predicate}\rangle$ + part of the expression. + If the $\langle \textit{predicate}\rangle$ + evaluates to a true value, the interpreter then evaluates the + 结果ifif$\langle \textit{consequent}\rangle$ + and returns its value. Otherwise it evaluates the + 备选if$\langle \textit{alternative}\rangle$ + and returns its value. 一个微小的 + 区别 + ifcondcond + condifif + 表达式序列cond的结果中 + 在 if 和 + cond 之间是 + $\langle e \rangle$ + 每个cond + 子句的部分可以是表达式的序列。 + 如果相应的 $\langle p \rangle$ + 被发现为真, + 这些表达式 $\langle e \rangle$ + 按顺序计算,序列中最后一个表达式的值作为 + cond 的值返回。 + 然而,在一个 if 表达式中, + $\langle \textit{consequent}\rangle$ 和 + $\langle \textit{alternative}\rangle$ 必须是单一的表达式。 + + + If 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} + |x| &=& \left\{ \begin{array}{rl} + x & \mbox{if $x > 0$} \\ + 0 & \mbox{if $x = 0$} \\ + -x & \mbox{otherwise} + \end{array} + \right. + \end{array}\] + + In JavaScript, we express a case analysis with multiple cases by nesting + conditional expressions as alternative expressions inside other conditional expressions: + + abs + abs_example + +function abs(x) { + return x > 0 + ? x + : x === 0 + ? 0 + : - x; +} + + + Parentheses are not needed around the alternative expression + x === 0 ? 0 : - x条件表达式备选条件表达式的备选结合性条件表达式条件表达式的条件表达式右结合性右结合?: + +p$_1$ +? e$_1$ +: p$_2$ +? e$_2$ +$\vdots$ +: p$_n$ +? e$_n$ +: final-alternative-expression + + + We call a predicate $p_i$ + together with its consequent expression + $e_i$ + a + 案例分析的子句谓词子句子句的子句案例分析序列子句的序列p$_1$. + If its value is false, then p$_2$ + is evaluated. + If p$_2$p$_3$ + is evaluated. This process continues until a predicate is + found whose value is true, in which case the interpreter returns the + value of the corresponding + 结果子句子句的ep + + + + + + In addition to primitive predicates such as + <, + =, and >, + there are logical composition operations, which enable us to construct + compound predicates. The three most frequently used are these: +
  • + and + 特殊形式(那些标注为ns的不在 IEEE Scheme 标准之内)andand + and的计算 + 计算andand + (and + $\langle e_1\rangle \ldots \langle e_n \rangle$ + + )

    + 解释器按从左到右的顺序逐个计算 + $\langle e \rangle$ 表达式。 + 如果任何 + $\langle e \rangle$ 的计算结果为假, + 那么 and 表达式的值为假, + 其余的 + $\langle e \rangle$s + 不再计算。如果所有的 + $\langle e \rangle$s + 计算结果都是真值, 那么 + and + 表达式的值是最后一个表达式的值。 +
  • + or + 特殊形式(那些标注为ns的不在 IEEE Scheme 标准之内)oror + or的计算 + 计算oror + (or $\langle e_1 \rangle\ldots \langle e_n \rangle$)

    + 解释器按从左到右的顺序逐个计算表达式 + $\langle e \rangle$。 + 如果任何 + $\langle e \rangle$ + 的计算结果为真值,这个值将作为 + or 表达式的值返回,其余的 + $\langle e \rangle$s 不再计算。 + 如果所有的 + $\langle e \rangle$s + 的计算结果为假, 那么 + or 表达式的值为假。 +
  • + not + 原始过程(那些标注为ns的不在 IEEE Scheme 标准之内)notnot + (not + $\langle e \rangle$) +

    + not 表达式的值为真 + 当表达式 $\langle e \rangle$ + 的计算结果为假,否则为假。 +
+ and为什么是特殊形式or为什么是特殊形式and and + or are special forms, not procedures, + because the subexpressions are not necessarily all evaluated. + Not is an ordinary procedure. +
+
+ + In addition to primitive predicates such as + >(数值比较运算符)>=0<(数值比较运算符)>=1<=(数值比较运算符)===数值作为数值相等运算符"!==;4作为数值比较运算符数值>(数值比较)===1<(数值比较)===2<=(数值比较)===(用于数值比较)"!==(用于数值比较);4相等数值数值的相等>=><<====!==目前,我们将这些运算符限制在数值参数内。在第节中,我们将泛化等价和不等谓词 ===!==
  • + expression$_1$ && + expression$_2$

    + 此操作表示 + 语法糖;1&& 和 {\tt "|"|} 作为 + &&(逻辑合取);1 + &&(逻辑合取)的计算;1 + 语法形式逻辑合取(&&) + 逻辑合取 + 合取 + 计算&&的 ;1 + 逻辑合取,大致等同于英文单词和。 + 我们假设这个假设由脚注中提到的限制证明是合理的。完整的 JavaScript + 需要考虑 expression$_1$ 计算结果既不是真也不是假的情况。 这个语法形式是语法糖简单为方便替代表面结构而设计的语法形式有时被称为语法糖,使用这个术语是彼得·兰丁 + Landin, Peter + 语法糖彼得·兰丁创造的。 + 对于

    + expression$_1$ ? + expression$_2$ : + false。 +
  • + expression$_1$ + || + expression$_2$

    + 此操作表示 + {\tt "|"|}(逻辑析取);2 + {\tt "|"|}(逻辑析取);2的计算 + 语法形式逻辑析取 ({\tt "|"|}) + 逻辑析取 + 析取 + 计算的{\tt "|"|}的 ;2 + 逻辑析取,大致等同于英文单词或。 + 我们假设这个语法形式是语法糖对

    + expression$_1$ ? + true : + expression$_2$。 +
  • + ! + expression

    + 此操作表示 + "!(逻辑否定运算符);399 + ;399"!(逻辑否定) + 否定逻辑 ("!) + 逻辑否定,大致等同于英文单词不是。 + 表达式的值为真,当 + expression + 的计算结果为假,而当 + expression + 的计算结果为真时,值为假。 +
+ Notice that &&||&&(逻辑合取);1为什么是语法形式{\tt "|"|}(逻辑析取);2为什么是语法形式!一元二元!-作为数值否定运算符-(数值否定运算符)否定数值 (-)二元运算符一元运算符前缀运算符前缀运算符abs- x
+
+
+ + + + + 作为如何使用这些谓词的一个例子,条件一个数 $x$ 在范围 + $5 < x < 10$ 可能表示为 + + +(and (> x 5) (< x 10)) + + + + + + + 作为如何使用这些谓词的一个例子,条件一个数 $x$ 在范围 + $5 < x < 10$ 可能表示为 + + +x > 5 && x < 10 + + + 语法形式 + && + 比较运算符的优先级低于 + > + 和<,以及 + 条件表达式语法形式 + $\cdots$?$\cdots$:$\cdots$ + 优先级低于我们目前遇到的任何其他运算符, + 这是我们在上述 abs 函数中使用的一个特性。 + + + + + 作为另一个例子,我们可以 + + + 定义 + + + 声明 + + + 一个谓词来测试一个数字是否 + + + >= + + + 大于或等于另一个数字,如 + + geq_example + +(define (>= x y) + (or (> x y) (= x y))) + + +function greater_or_equal(x, y) { + return x > y || x === y; +} + + + 或者作为 + + + >= + + + + geq_example + +(define (>= x y) + (not (< x y))) + + +function greater_or_equal(x, y) { + return ! (x < y); +} + + + + geq_example + +(>= 7 4) + + +greater_or_equal(7, 4); + + + + + + + 函数 greater_or_equal, + 应用于两个数字时,表现与运算符 + >= 相同。一元运算符具有 + 优先级一元一元运算符的优先级 + 比二元运算符的优先级高,使得该示例中的括号是必要的。 + + + + + + + Below is a sequence of + + + 表达式。 + + + 语句。 + + + + 表达式时打印的结果是什么? + + + 语句时打印的结果是什么? + + + ten + 10 + +10 + + +10; + + + + five_plus_three_plus_four + 12 + +(+ 5 3 4) + + +5 + 3 + 4; + + + + nine_minus_one + 8 + +(- 9 1) + + +9 - 1; + + + + six_over_two + 3 + +(/ 6 2) + + +6 / 2; + + + + two_times_four_etc + 6 + +(+ (* 2 4) (- 4 6)) + + +2 * 4 + (4 - 6); + + + + definea + undefined + +(define a 3) + + +const a = 3; + + + + defineb + definea + undefined + +(define b (+ a 1)) + + +const b = a + 1; + + + + a_plus_b_etc + defineb + 19 + +(+ a b (* a b)) + + +a + b + a * b; + + + + a_equal_b + defineb + false + +(= a b) + + +a === b; + + + + b_gt_a_etc + defineb + 4 + +(if (and (> b a) (< b (* a b))) + b + a) + + +b > a && b < a * b ? b : a; + + + + a_equal_four_etc + defineb + 16 + +(cond ((= a 4) 6) + ((= b 4) (+ 6 7 a)) + (else 25)) + + +a === 4 +? 6 +: b === 4 +? 6 + 7 + a +: 25; + + + + two_plus_etc + defineb + 6 + +(+ 2 (if (> b a) b a)) + + +2 + (b > a ? b : a); + + + + a_greater_b_etc + defineb + 16 + +(* (cond ((> a b) a) + ((< a b) b) + (else -1)) + (+ a 1)) + + +(a > b + ? a + : a < b + ? b + : -1) +* +(a + 1); + + + + 最后两个语句中条件表达式周围的括号是必要的,因为 + 条件表达式操作数运算符组合的操作数 + 条件表达式语法形式的 + 优先级条件表达式条件表达式的 + 条件表达式的优先级 + 优先级低于算术运算符 + + 和 + *。 + + + 解答由 GitHub 用户 Emekk 提供 +
    +
  1. 10
  2. +
  3. 12
  4. +
  5. 8
  6. +
  7. 3
  8. +
  9. 6
  10. +
  11. 3
  12. +
  13. 4
  14. +
  15. 19
  16. +
  17. false
  18. +
  19. 4
  20. +
  21. 16
  22. +
  23. 6
  24. +
  25. 16
  26. +
+
+ + + 将以下表达式翻译成 + + + 前缀形式 + + + JavaScript + + + + \par\medskip + + + $\begin{array}{l} + \quad~~\dfrac{5+4+\left(2-\left(3-(6+\frac{4}{5})\right)\right)}{3 (6-2) (2-7)} + \end{array}$ + + + + +(5 + 4 + (2 - (3 - (6 + 4 / 5)))) +/ +(3 * (6 - 2) * (2 - 7)); + + + + + + + + + 定义一个过程 + + + 声明一个函数 + + + 接受三个数字作为参数,并返回两个较大数字的平方和。 + + + larger_two_square_example + square_definition + +function f(x, y, z) { + return square(x) + square(y) + square(z) - + // 减去最小的平方 + square(x > y ? (y > z ? z : y) : (x > z ? z : x)); +} + + +(define (f x y z) + (let ((smallest (if (> x y) (if (> y z) z y) (if (> x z) z x)))) + (- (+ (square x) (square y) (square z)) (square smallest)))) + + + + + larger_two_square_example + +(f 4 7 2) + + +f(4, 7, 2); + + + + + + + 注意我们的评估模型允许 + 组合复合表达式作为运算符的 + 复合表达式运算符作为组合的运算符 + 组合的运算符复合表达式作为 + 运算符是复合表达式的组合。利用这一观察来描述以下过程的行为: + + +(define (a-plus-abs-b a b) + ((if (> b 0) + -) a b)) + + + + Observe that our model of evaluation allows for + 函数应用复合表达式作为函数表达式的复合表达式函数表达式作为应用的函数表达式函数表达式复合表达式作为a_plus_abs_b + plusminusexample + +function plus(a, b) { return a + b; } + +function minus(a, b) { return a - b; } + +function a_plus_abs_b(a, b) { + return (b >= 0 ? plus : minus)(a, b); +} + + + + plusminusexample + +a_plus_abs_b(5, -4); + + + 注意,在 JavaScript 的条件表达式中,我们不能像在 Scheme 中那样直接使用运算符 +-,而是必须使用名称 plusminus,因为在中缀表示法中,中间只允许运算符符号,而不是复合表达式。 + + 根据第节,应用的计算过程如下: +
    +
  1. 计算应用的子表达式。 +
  2. +
  3. 计算函数表达式结果的返回表达式,其中每个参数被相应的参数表达式的结果替换。 +
  4. +
+ 因此,应用 a_plus_abs_b(5, -4) 的计算 (1) 计算 a_plus_abs_b,结果是上面给出的函数,并且参数已经是值。所以我们需要计算 (2) 函数的返回表达式,其中参数被参数替换,因此:(-4 >= 0 ? plus : minus)(5, -4)。 + 使用相同的规则,我们需要 (1) 计算函数表达式,在这种情况下是条件表达式 -4 >= 0 ? plus : minus。由于谓词的计算结果为假,函数表达式计算结果为 minus。参数 (1) 再次已经是值。因此,我们最终计算 (2) minus 的主体,参数 ab 分别被 5 和 -4 替换,结果是 5 - (-4),最终将计算为 9。 +
+
+
+ + 正常顺序计算应用顺序与应用顺序计算正常顺序与 + + 定义了以下两个过程: + + + 声明了以下两个函数: + + + ptest + +(define (p) (p)) + +(define (test x y) + (if (= x 0) + 0 + y)) + + +function p() { return p(); } + +function test(x, y) { + return x === 0 ? 0 : y; +} + + + 表达式 + 语句 + + ptest + +(test 0 (p)) + + +test(0, p()); + + 正常顺序计算条件条件表达式的条件表达式正常顺序计算的 + + 特殊形式if + + + 条件表达式 + + + + + 在应用顺序计算中, + test(0, p()), + 我们需要在计算函数 + test + 的返回表达式之前计算参数表达式。 + 然而,参数表达式 + p() + 的计算将不会终止:它将不断计算形式为 + p()的应用表达式,因此 + test(0, p()) 的计算将不会生成有效的值。在正常顺序计算中,另一方面, + 函数应用 + test(0, p()) + 将立即计算函数 + test + 的返回表达式, + x === 0 ? 0 : y + 在将参数 + x替换为 + 0和 + y替换为 + p()之后。 + 替换的结果将是 + 0 === 0 ? 0 : p()。 + 谓词 + 0 === 0 + 的计算结果为 true,因此条件 + 表达式的计算结果为 0,无需 + 计算 p()。 + + + +
diff --git a/xml/cn/chapter1/section1/subsection7.xml b/xml/cn/chapter1/section1/subsection7.xml new file mode 100644 index 000000000..88ef8a63d --- /dev/null +++ b/xml/cn/chapter1/section1/subsection7.xml @@ -0,0 +1,659 @@ + + 示例: 牛顿方法求平方根 + + + + + + 过程数学函数 vs. + 函数(数学)过程 vs. + + + 数学函数 vs. + 函数(数学)JavaScript 函数 vs. + + + + 过程, + 函数, + + 上面介绍的,与普通数学函数非常相似。它们指定一个由一个或多个参数确定的值。但是数学函数和计算机 + + 过程 + 函数 + + 之间有一个重要的区别。 + + + 过程 + + + 计算机函数 + + + 必须是有效的。 + + + 举个例子,考虑计算平方根的问题。我们可以将平方根函数定义为 + + \[ + \sqrt{x}\ =\text{ the }y\text{ such that }y \geq 0\text{ and } + y^2\ =\ x + \] + + 这描述了一个完全合法的数学函数。我们可以用它来识别一个数字是否是另一个数字的平方根,或者推导有关平方根的一般事实。另一方面,这个定义并没有描述 + + 过程。 + 计算机函数。 + + 实际上,它几乎没有告诉我们如何实际找到一个给定数的平方根。用 + + 伪Lisp: + 伪JavaScript: + + 来重新表述这个定义也无济于事。 + + +(define (sqrt x) + (the y (and (>= y 0) + (= (square y) x)))) + + +function sqrt(x) { + return the y $\texttt{with}$ y >= 0 && square(y) === x; +} + + + 这只不过是回到了原点。 + + + + The contrast between + + + 函数和过程 + + + 数学函数和计算机函数 + + 声明性与命令性知识命令性与声明性知识数学计算机科学 vs.计算机科学数学 vs.声明性和命令性描述密切相关,正如数学和计算机科学密切相关一样。例如,说程序产生的答案是 + 程序的正确性 + 正确的是对程序做出的声明性陈述。大量研究旨在建立 + 证明程序正确性 + 证明程序正确性的技术,而该学科的许多技术困难与在命令性陈述(程序由此构建)和声明性陈述(可用于推断事物)之间的过渡有关。 + + + 与此相关,编程语言设计的一个重要当前领域是探索所谓的 + 编程语言非常高级 + 非常高级语言 + 非常高级语言,在这种语言中,人们实际上以声明性陈述进行编程。 + + + 与此相关,编程语言设计者已经探索了所谓的 + 编程语言非常高级 + 非常高级语言 + 非常高级语言,其中人们实际上以声明性陈述进行编程。 + + + 这个想法是让解释器足够聪明,以便在给定程序员指定的是什么的知识后,它们可以自动生成如何做的知识。一般来说,这是不可能的,但在一些重要领域已经取得了进展。我们将在中重新探讨这一想法。 + + 过程数学函数 vs. + 函数(数学)过程 vs. + + + 数学函数 vs. + 函数(数学)JavaScript 函数 vs. + + + + 如何计算 + 平方根 + 牛顿方法平方用于平方根 + 平方根?最常见的方法是使用牛顿的逐次近似方法,该方法指出,只要我们有一个猜测 $y$ 作为数字 $x$ 的平方根的值,我们就可以通过将 $y$$x/y$ 平均来进行简单操作以获得更好的猜测(更接近实际平方根)。此平方根算法实际上是牛顿法则的一个特例,牛顿法则是一种用于求解方程根的一般技术。平方根算法本身是由亚历山大的希伦在公元一世纪开发的。我们将在中看到如何将一般的牛顿法表示为 + + + Lisp 过程 + + + JavaScript 函数 + + + 例如,我们可以如下计算 2 的平方根。假设我们的初始猜测是1: + + \[ + \begin{array}{lll} + \textrm{猜测} & \textrm{商} & \textrm{平均}\\[1em] + 1 & {\displaystyle \frac{2}{1} = 2} & {\displaystyle \frac{(2+1)}{2} = 1.5} \\[1em] + 1.5 & {\displaystyle \frac{2}{1.5} = 1.3333} & {\displaystyle \frac{(1.3333+1.5)}{2} = 1.4167} \\[1em] + 1.4167 & {\displaystyle \frac{2}{1.4167} = 1.4118} & {\displaystyle \frac{(1.4167+1.4118)}{2} = 1.4142} \\[1em] + 1.4142 & \ldots & \ldots + \end{array} + \] + + 继续这个过程,我们将获得越来越接近平方根的近似值。 + + + Now let被开方数 + + 过程: + + + 函数: + + + sqrt_iter + is_good_enough + improve + sqrt_iter_example + +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) + + +function sqrt_iter(guess, x) { + return is_good_enough(guess, x) + ? guess + : sqrt_iter(improve(guess, x), x); +} + + + + sqrt_iter_example + +(sqrt-iter 3 25) + + +sqrt_iter(3, 25); + + + A guess is improved by averaging it with the quotient of the radicand and + the old guess: + + improve + average_definition + improve_example + +(define (improve guess x) + (average guess (/ x guess))) + + +function improve(guess, x) { + return average(guess, x / guess); +} + + + + improve_example + +(improve 3 25) + + +improve(3, 25); + + + where + + average + average_definition + average_example + +(define (average x y) + (/ (+ x y) 2)) + + +function average(x, y) { + return (x + y) / 2; +} + + + + average_example + +(average 3 6) + + +average(3, 6); + + + We also have to say what we mean by 足够好。我们通常会给 + 谓词命名约定 + 命名约定用于谓词 + 问号,用于谓词名称 + ,用于谓词名称 + 谓词名称以问号结尾,帮助我们记住它们是谓词。这只是一个风格上的约定。对于解释器来说,问号只是一个普通字符。我们通常会给 + 谓词命名约定 + 命名约定is_用于谓词 + is_,在谓词名称中 + 谓词名称以is_开头,帮助我们记住它们是谓词。 + is_good_enough + abs_definition + square_definition + is_good_enough_example + +(define (good-enough? guess x) + (< (abs (- (square guess) x)) 0.001)) + + +function is_good_enough(guess, x) { + return abs(square(guess) - x) < 0.001; +} + + + + is_good_enough_example + +(good-enough? 1.41 2) + + +is_good_enough(1.41, 2); + + + Finally, we need a way to get started. For instance, we can always guess + that the square root of any number + is注意,我们将初始猜测表示为1.0而不是1。这在许多Lisp实现中没有区别。 + 有理数MIT在MIT Scheme中 + 精确整数 + 整数精确 + 整数除法 + 整数除法 + 有理数 + 整数,精确 + 小数点 + 数中的小数点 + MIT Scheme + 实现依赖 + 实现依赖 + 然而,MIT Scheme区分精确整数和小数值,两个整数相除得到一个有理数而不是小数。例如,10除以6得到5/3,而10.0除以6.0得到1.6666666666666667。(我们将在中学习如何实现对有理数的算术运算。)如果我们在平方根程序中以1作为初始猜测,而$x$是一个精确整数,那么平方根计算中产生的所有后续值将是有理数而非小数。对有理数和小数进行混合运算始终会得到小数,因此将初始猜测设为1.0会使所有后续值都变为小数。脚注已移除:它是特定于Scheme的(甚至更具体来说:特定于MIT Scheme) + sqrt + sqrt + sqrt_iter + sqrt_example_2 + 2.2360688956433634 + +(define (sqrt x) + (sqrt-iter 1.0 x)) + + +function sqrt(x) { + return sqrt_iter(1, x); +} + + + If we type these + + + 定义 + + + 声明 + + sqrt + just as we can use any + + + 过程: + + + 函数: + + + sqrt_example + sqrt + +(sqrt 9) + + +3.00009155413138 + + +sqrt(9); + + +3.00009155413138 + + + + sqrt_example_2 + +(sqrt 5) + + +sqrt(5); + + + + + sqrt_example_3 + sqrt + +(sqrt (+ 100 37)) + + +11.704699917758145 + + +sqrt(100 + 37); + + +11.704699917758145 + + + + sqrt_example_4 + 1.7739279023207892 + sqrt + +(sqrt (+ (sqrt 2) (sqrt 3))) + + +1.7739279023207892 + + +sqrt(sqrt(2) + sqrt(3)); + + +1.7739279023207892 + + + + sqrt_example_5 + sqrt + +(square (sqrt 1000)) + + +1000.000369924366 + + +square(sqrt(1000)); + + +1000.000369924366 + + square root + Newtons methodsquarefor square roots + + + + sqrt 程序还说明了我们迄今为止介绍的简单 + + + 迭代过程通过过程调用实现 + 过程式 + + + 迭代过程通过函数调用实现 + 函数式 + + + 语言足以编写可以在 C 或 Pascal 中编写的任何纯数值程序。这似乎令人惊讶,因为我们在语言中没有包含任何指示计算机反复执行某些操作的迭代循环结构 + (循环)结构。 + + + Sqrt-iter, + + + 函数 sqrt_iter, + + + 另一方面,演示了如何使用普通的过程调用而无需任何特殊结构来完成迭代。 + 过程。函数。对使用 + + + 过程 + + + 函数 + + + 调用来实现迭代的效率问题感到担忧的读者请注意尾递归中的说明。 + + + 迭代过程通过过程调用实现 + + + 迭代过程通过函数调用实现 + + + + + + + Alyssa P. Hacker 不明白为什么if需要作为 + if为什么是特殊形式 + 特殊形式需要 + 特殊形式提供。为什么我不能只是用cond来定义它作为一个普通过程呢?她问。 + Alyssa的朋友Eva Lu Ator声称这确实可以做到,她定义了一个if的新版本: + + new_if + +(define (new-if predicate then-clause else-clause) + (cond (predicate then-clause) + (else else-clause))) + + + Eva 为 Alyssa 演示了程序: + + new_if + +(new-if (= 2 3) 0 5) + + +5 + + + + new_if + +(new-if (= 1 1) 0 5) + + +0 + + + 兴奋的 Alyssa 使用 new-if 重写了平方根程序: + + new_if + is_good_enough + improve + sqrt_iter_example + +(define (sqrt-iter guess x) + (new-if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) + x))) + + + 当 Alyssa 尝试使用此方法计算平方根时会发生什么?解释。 + + + Alyssa P. Hacker 不喜欢语法形式需要 + 条件表达式为什么是语法形式 + 条件表达式的语法,包括字符?:。 + 为什么我不能简单地声明一个普通的条件函数,其应用方式与条件表达式相同呢?她问道。 + 作为最初的计算机程序的构造与解释中的Lisp黑客,Alyssa更喜欢一种更简单、更统一的语法。 + Alyssa 的朋友 Eva Lu Ator 声称这确实可以做到,并且她声明了一个conditional + 函数,如下所示: + + conditional + +function conditional(predicate, then_clause, else_clause) { + return predicate ? then_clause : else_clause; +} + + + Eva 为 Alyssa 演示了程序: + + conditional + +conditional(2 === 3, 0, 5); + + +5 + + + + conditional + +conditional(1 === 1, 0, 5); + + +0 + + + 高兴的Alyssa使用conditional重写了平方根程序: + + delighted + conditional + is_good_enough + improve + sqrt_iter_example + +function sqrt_iter(guess, x) { + return conditional(is_good_enough(guess, x), + guess, + sqrt_iter(improve(guess, x), + x)); +} + + + 当 Alyssa 尝试使用此方法计算平方根时会发生什么?解释。 + + + 任何调用sqrt_iter都会立即导致一个无限循环。原因是我们的应用序求值。sqrt_iter的返回表达式的求值需要首先求值其参数,包括递归调用sqrt_iter,无论谓词是求值为true还是false。当然,递归调用也是如此,因此conditional函数实际上从未真正被应用。 + + + + + + + + + good-enough? + + + is_good_enough + + + 测试用于计算平方根对于非常小的数字将不是很有效。此外,在实际计算机中,算术运算几乎总是在有限精度下进行。这使得我们的测试对非常大的数字不够充分。用例子解释这些语句,显示测试对于小数和大数如何失败的。实现 + + + good-enough? + + + is_good_enough + + + 的另一种策略是观察guess如何从一次迭代变化到下一次,并在变化是猜测的一小部分时停止。设计一个使用这种终止测试的平方根 + + + 过程 + + + 函数 + + + 这对于小数和大数效果更好吗? + + 绝对容差0.001在计算一个小值的平方根时太大。例如, + sqrt(0.0001) + 的结果是0.03230844833048122,而不是预期的值0.01,误差超过200%。 +

+ 另一方面,对于非常大的值,舍入错误可能导致算法无法接近平方根,在这种情况下它将不会终止。 +

+ 以下程序通过使用相对容差替换绝对容差来缓解这个问题。 + + abs_definition + average_definition + sqrt + improve + sqrt_iter + square_definition + example_1.8 + +const relative_tolerance = 0.0001; +function is_good_enough(guess, x) { + return abs(square(guess) - x) < guess * relative_tolerance; +} + + +
+ + example_1.8 + +display(sqrt(0.0001)); +display(sqrt(4000000000000)); + + + + +
+ + + + 牛顿方法用于 + 立方根牛顿通过牛顿的方法 + 牛顿方法立方用于立方根 + 立方根基于这样一个事实:如果 $y$$x$ 的立方根的一个近似值,那么更好的近似值由以下公式给出: + + \[ + \begin{array}{lll} + \dfrac{x/y^{2}+2y} {3} + \end{array} + \] + + 使用此公式实现一个立方根 + + + 过程 + + + 函数 + + + 类似于平方根 + + 过程。 + 函数。 + + (在部分,我们将看到如何实现牛顿方法作为这些平方根和立方根的 + + 过程的 + 函数的抽象。) + + + + example_1.9 + abs_definition + cube_definition + +function is_good_enough(guess, x) { + return abs(cube(guess) - x) < 0.001; +} +function div3(x, y) { + return (x + y) / 3; +} +function improve(guess, x) { + return div3(x / (guess * guess), 2 * guess); +} +function cube_root(guess, x) { + return is_good_enough(guess, x) + ? guess + : cube_root(improve(guess, x), x); +} + + + + + + + example_1.9 + + cube_root(3, 27); + + + +
diff --git a/xml/cn/chapter1/section1/subsection8.xml b/xml/cn/chapter1/section1/subsection8.xml new file mode 100644 index 000000000..104f85035 --- /dev/null +++ b/xml/cn/chapter1/section1/subsection8.xml @@ -0,0 +1,1038 @@ + + + + + + 程序【8:0†try.txt】 + + + 函数【8:0†try.txt】 + + + 作为黑匣子抽象【8:0†try.txt】 + + + + + + 平方根 + + 函数sqrt + + + 是通过一组相互定义的 + + 程序 + 函数 + + 定义的过程的第一个例子。注意到 + + sqrt-iter的定义 + + sqrt_iter的声明 + + 是 + + + 递归程序递归程序定义 + + + 递归函数递归函数声明 + + + 递归的;也就是说, + + 程序 + 函数 + + 是基于自身定义的。能够基于自身定义一个 + + 程序 + 函数 + + 的概念可能令人不安;可能显得不清楚这种 + 循环定义如何能够有意义,更不用说由计算机执行的过程进行良好定义。这将在中更为详细地讨论。但首先,让我们考虑 + sqrt例子所展示的一些其他重要点。 + + + 注意到计算平方根的问题自然地分解成多个子问题: + 程序的结构 + 如何判断一个猜测是否足够好,如何改进一个猜测,等等。每项任务由一个单独的 + + 程序。 + 函数。 + + 整个平方根程序可以看作是一组 + + + 程序 + (如图所示) + + + 函数 + (如图所示) + + + 这种程序集反映问题的分解为子问题。 + + + +
+ + 程序的过程分解 + 平方根程序。 + +
+
+ +
+
+ + 程序的函数分解 + sqrt程序。 + +
+
+
+ + The importance of this + 程序分解成部分 + + 程序 + + + 函数 + + + 程序。 + 函数。 + + + 程序作为黑箱 + + + 作为黑箱 + + + + 够好吗?程序 + + + is_good_enough函数 + + square, we are able to + regard the square + + 程序 + + + 函数 + + 黑箱黑箱。如何 + + 程序 + + + 函数 + + 确实 + + 够好吗?程序 + + + is_good_enough函数 + + square is not quite a + + + 程序 + + + 函数 + + + 程序, + 函数, + + + 程序抽象 + 抽象过程 + 过程抽象. + + + 功能抽象 + 抽象功能 + 功能抽象. + + + + 程序 + + + 函数 + + + + 因此,仅考虑它们返回的值,以下两个 + + + 程序 + + + 函数 + + + 对一个数进行平方计算应该是无法区分的。每个都接受一个数值参数并产生该数的平方作为值。甚至不清楚这些 + + + 程序 + + + 函数 + + + 中哪个是更高效的实现。这取决于可用的硬件。有些机器对于明显的 + 实现反而效率较低。考虑一台机器,存储了大量以非常高效方式存储的对数和反对数表。 + + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + +(define (square x) + (exp (double (log x)))) + +(define (double x) (+ x x)) + + +function square(x) { + return math_exp(double(math_log(x))); +} +function double(x) { + return x + x; +} + + + + + 因此,一个 + + + 程序 + + + 函数 + + + 应能够隐藏细节。 + + + 程序 + + + 函数 + + + 的用户可能并未编写此 + + + 程序 + + + 函数 + + + ,而是从另一位程序员那里获得的一个黑箱。用户不需要知道该 + + + 程序 + + + 函数 + + + 是如何实现的,才能使用它。 + + + 程序作为黑箱 + + + 作为黑箱 + + + + + + 局部名称 + + + 局部名称 + + + One detail of a + + 程序s + 函数s + + 程序 + 函数 + + 程序s形式参数的名称。 + 函数s参数的名称。 + + 程序 + 函数 + + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + +(define (square y) (* y y)) + + +function square(y) { + return y * y; +} + + + This principle + 程序 + 函数 + + 程序 + 函数 + + 程序 + 函数 + square + + 在定义 + 够好吗? + + + 在声明 + is_good_enough + + + + 程序: + + + 函数: + + + is_good_enough_example + abs_definition + square_definition + +(define (good-enough? guess x) + (< (abs (- (square guess) x)) 0.001)) + + +function is_good_enough(guess, x) { + return abs(square(guess) - x) < 0.001; +} + + + The intention of the author of + + + 够好吗? + + + is_good_enough + + + + 够好吗? + + + is_good_enough + + guess to refer to the + first argument and x to refer to the + second argument. The argument of square + is guess. If the author of + square used x + (as above) to refer to that argument, we see that the + x in + + + 够好吗? + + + is_@good_@enough + + x than the one + in square. Running the + + + 程序 + + + 函数 + + square must not affect the value + of x that is used by + + + 够好吗?, + + + is_good_enough, + + x may be needed by + + + 够好吗? + + + is_good_enough + + square is done computing. + + + 如果参数不是各自 + + 程序 + 函数 + + 主体的局部变量,那么在平方中的参数x可能会与在 + + + 够好吗? + + + is_@good_@enough + + + 中的参数x混淆,并且 + + + 够好吗? + + + is_good_enough + + + 的行为将取决于我们使用的是哪个版本的平方。因此,平方就不会是我们所期望的黑箱。 + + + A + 参数名称名称参数参数的名称 + + 程序的形式参数 + + + 函数的参数 + + + + 程序定义中具有很特殊的角色, + + + 函数声明中具有很特殊的角色, + + + + 形式 + + + + + + 绑定变量 + 变量绑定 + 一个绑定变量,我们称这个程序定义为 + + + 绑定名称 + 名称绑定 + 绑定,我们称这个函数声明为 + + 绑定绑定 + + 形式参数。 + + + 参数。 + + + + 程序定义的意义不会改变,如果一个绑定变量 + + + 函数声明的意义不会改变,如果一个绑定名称 + + 定义声明一致重新命名的概念实际上是微妙且难以形式化定义的。有名的逻辑学家在这里犯过尴尬的错误。 + 变量 + 名称 + + + 自由变量 + 变量自由 + + + 自由名称 + 名称自由 + + 自由的 + 定义 + 声明 + + + 变量的作用域 + 变量作用域 + + + 名称的作用域 + 名称作用域 + + 作用域 + + 程序定义中,绑定变量 + + + 函数声明中,绑定名称 + + + + 形式参数的作用域 + 程序形式参数的作用域 + 变量的作用域程序的形式参数 + + + 参数的作用域 + 参数的作用域 + 名称的作用域函数的参数 + + + + 程序的形式参数 + + + 函数的参数 + + + + 程序 + + + 函数 + + + + In the + + + 够好吗?的定义 + + + is_good_enough的声明 + + guess and + x are + bound + + + 变量 + + + 名称 + + + + <, + -, + + abs + and square are free. + The meaning of + + + 够好吗? + + + is_good_enough + + guess and + x so long as they are distinct and + different from + + + <, + -, + + abs + and square. (If we renamed + guess to + abs we would have introduced a bug by + + + 捕获一个自由变量 + 错误捕获一个自由变量 + 自由变量捕获 + + + 捕获一个自由名称 + 错误捕获一个自由名称 + 自由名称捕获 + + 捕获 + + 变量 + + + 名称 + + abs. + It would have changed from free to bound.) The meaning of + + + 够好吗? + + + is_good_enough + + + + 其自由变量的名称无关, + + + 其自由名称的选择无关, + + + + (定义外部) + + + (声明外部) + + + + 符号abs命名一个计算绝对值的程序 + + + 名称abs指的是一个用于计算绝对值的函数 + + + + 够好吗? + + + 函数is_good_enough + + + + cos + + + math_cos + (原始的余弦函数) + + abs in its + + + 定义。 + + + 声明。 + + 局部名称 + + + 内部 + + 定义 + 声明 + + 和块结构 + + + + + + 到目前为止,我们有一种名称隔离的方法: + + + 程序的形式参数 + + + 函数的参数 + + + 是 + + 程序 + 函数 + + 主体的局部变量。平方根程序展示了我们希望控制名称使用的另一种方式。 + 程序结构 + 现有的程序由几个独立的 + + + 程序: + + + 函数: + + + + + sqrt_example_2 + abs_definition + square_definition + average_definition + +(define (sqrt x) + (sqrt-iter 1.0 x)) + +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) + +(define (good-enough? guess x) + (< (abs (- (square guess) x)) 0.001)) + +(define (improve guess x) + (average guess (/ x guess))) + + +function sqrt(x) { + return sqrt_iter(1, x); +} +function sqrt_iter(guess, x) { + return is_good_enough(guess, x) + ? guess + : sqrt_iter(improve(guess, x), x); +} +function is_good_enough(guess, x) { + return abs(square(guess) - x) < 0.001; +} +function improve(guess, x) { + return average(guess, x / guess); +} + + + + + The problem with this program is that the only + + + 程序 + + + 函数 + + sqrt is + sqrt. The other + + + 程序 + + + 函数 + + + + (sqrt-iter, + 够好吗?, + + + (sqrt_iter, + is_good_enough, + + improve) only clutter up their minds. + They may not + + + 定义任何其他程序 + + + 声明任何其他函数 + + + + 够好吗? + + + is_good_enough + + sqrt + needs it. The problem is especially severe in the construction of large + systems by many separate programmers. For example, in the construction + of a large library of numerical + + 程序, + 函数, + + + 程序 + + + 函数 + + + + 够好吗? + + + is_good_enough + + improve as auxiliary + + 程序。 + 函数。 + + 子程序, + 子函数, + sqrt so that + sqrt could coexist with other + successive approximations, each having its own private + + 够好吗?程序。 + + is_good_enough函数。 + + + + 程序 + + + 函数 + + 块结构 + + 内部定义 + + + 内部声明 + + + 程序 + 函数 + + sqrt_example_2 + 2.2360688956433634 + abs_definition + square_definition + average_definition + +(define (sqrt x) + (define (good-enough? guess x) + (< (abs (- (square guess) x)) 0.001)) + (define (improve guess x) + (average guess (/ x guess))) + (define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) x))) + (sqrt-iter 1.0 x)) + + +function sqrt(x) { + function is_good_enough(guess, x) { + return abs(square(guess) - x) < 0.001; + } + function improve(guess, x) { + return average(guess, x / guess); + } + function sqrt_iter(guess, x) { + return is_good_enough(guess, x) + ? guess + : sqrt_iter(improve(guess, x), x); + } + return sqrt_iter(1, x); +} + + + + + + + 任何匹配的一对花括号表示一个,并且块内的声明是块的局部变量。 + + 句法形式 + + 块结构 + 辅助程序的定义内化 + 辅助函数的声明内化 + x is bound in the + + 定义 + 声明 + sqrt, the + + + 程序 + + + 函数 + + + 够好吗?, + is_good_enough, + improve, and + + sqrt-iter, + 定义在内部的 + sqrt_iter, + 声明在内部的 + sqrt, are in the scope of + x. Thus, it is not necessary to pass + x explicitly to each of these + + 程序。 + 函数。 + x to be a free + + + 内部定义的自由变量 + 自由变量内部在内部定义中 + + + 内部声明的自由名称 + 自由名称内部在内部声明中 + + + 变量 + 名称 + + 定义中 + 声明中 + x gets its value from + the argument with which the enclosing + + + 程序 + + + 函数 + + sqrt is called. This discipline is called + 词法作用域词法作用域词法作用域决定了一个 + + + 程序中的自由变量 + + + 函数中的自由名称 + + + 被认为是指封闭的 + + + 程序定义所做的绑定; + + + 函数声明所做的绑定; + + + 即,它们在环境 + 环境词法作用域与 + 中被查找,在 + + + 程序被定义的环境中。 + + + 函数被声明的环境中。 + + + 当我们研究环境和解释器的详细行为时,我们将在中详细了解这种机制如何工作。 + sqrtblock structured + sqrt_example_2 + abs_definition + square_definition + average_definition + +(define (sqrt x) + (define (good-enough? guess) + (< (abs (- (square guess) x)) 0.001)) + (define (improve guess) + (average guess (/ x guess))) + (define (sqrt-iter guess) + (if (good-enough? guess) + guess + (sqrt-iter (improve guess)))) + (sqrt-iter 1.0)) + + +function sqrt(x) { + function is_good_enough(guess) { + return abs(square(guess) - x) < 0.001; + } + function improve(guess) { + return average(guess, x / guess); + } + function sqrt_iter(guess) { + return is_good_enough(guess) + ? guess + : sqrt_iter(improve(guess)); + } + return sqrt_iter(1); +} + + + + + 我们将广泛使用块结构来帮助我们将大型程序分解成可处理的部分。嵌入的 + + + 定义必须在程序的主体系中首先出现 + + + 声明必须在函数的主体系中首先出现 + + 。 + + + 内部定义位置 + + + 内部声明位置 + + + 管理层不对运行相互交织的 + + 定义 + 声明 + + 和使用的程序造成的后果负责;另请参见 + 注释 + 和 + 在一节中。 + + 块结构的概念起源于编程语言 + Algol块结构 + Algol60。它出现在大多数高级编程语言中,是帮助组织大型程序构建的重要工具。 + 程序结构 + 块结构 + + + 内部定义 + + + 内部声明 + + + +
diff --git a/xml/cn/chapter2/chapter2.xml b/xml/cn/chapter2/chapter2.xml new file mode 100644 index 000000000..e0de31be5 --- /dev/null +++ b/xml/cn/chapter2/chapter2.xml @@ -0,0 +1,345 @@ + + 用数据建立抽象 + + \addtocontents{toc}{\protect\enlargethispage{-\baselineskip}} + + + % 秋'97 添加 tex '\label' 用于手动引用。 + % 8/26/97 修正第三次印刷的错字 -- p.112, 179, 181 + + % 4/15 修正并入同一页的多个条目 + % 4/13-15 索引 [在章节最终打印后] + % 4/11 基于最终校对和拼写检查的修正和调整 + % 4/9 基于索引之前的对比进行微小修正 + % 4/7-... Julie 索引修正(和4/8 改变'ns') + % 4/6/96 Julie:页码更改;添加 Thatcher 引用;索引修正 + % 4/5/96 Hal 索引 + % 4/5/96 Julie:索引修正;拼写修正 + % 4/4/96 内容修正;页码调整;其余的分页 + % 4/3/96 索引更改 + % 4/2/96 分页至2.2 + % 4/1/96 Julie:分页到 Rogers 脚注 + % 4/1/96 Julie 杂项改进 + % 3/29/96 Julie 索引修正 + % 3/20/96 Julie 索引修正 + % 3/18/96 'atan' 编辑及微小索引修正 + % 3/17/96 Julie 微小索引更改 + % 3/15/96 Julie 微小索引更改 + % 3/10-3/13/96 Hal 索引,Julie 的微小索引修正/添加 + + % 3/9/96 修正一些练习部分的空格问题;在 get/put 规范中分列行; + % 其他微小编辑 + % 3/6/96 细小的措辞和换行改进 + % 3/4/96 重写以'修复'大部分三连字符;杂项其他排版修正 + % 3/3/96 更改 countleaves 为 count-leaves(以适应) + % 3/3/96 修复 poly gcd 脚注中的错误(由 Albert Meyer 指出) + % 2/26-3/2/96 修复一些不良的换行 + % 2/24/96 在{lisp}之后取消缩进,关闭空隙以排齐 + % 2/24/96 修复松散的脚注 (#49) + % 2/23/96 将8皇后图移入例中以避免额外的例间空格 + % 2/22/96 新的题词规范 + % 2/21/96 修正一个表格的格式 + % 2/19/96 修正引用中的() + + + + 我们现在来到了数学抽象的决定性步骤:我们忘记了符号代表的意义。 [数学家] + 不需要闲着;他可以用这些符号进行许多操作,而不必去看它们代表的事物。 + Weyl, Hermann + + Hermann Weyl + <EM>数学思维的方式</EM> + + + + + + + 复合数据, 需要 + 数据复合 + + 我们在第章集中在计算过程和程序设计中 + + 过程 + 函数 + + 的作用。我们看到了如何使用原始数据(数字)和原始操作(算术操作),如何组合 + + 过程 + 函数 + + 通过组合、条件语句和参数的使用形成复合 + + 过程 + 函数 + + ,以及如何通过使用 + + 定义. + 函数声明。 + + 来抽象 + + 过程 + 进程 + + 。我们看到 + + 过程 + 函数 + + 可以视为一个过程的局部演变的模式,并且我们对一些常见的进程模式进行了分类、推理和简单的算法分析,这些模式体现在 + + 过程。 + 函数。 + + 我们还看到高阶 + + 过程 + 函数 + + 通过使我们能够操纵和推理一般的计算方法来增强了我们语言的能力。这是编程的本质所在。 + + + + 在本章中,我们将研究更复杂的数据。第章中的所有 + + 过程 + 函数 + + 都处理简单的数值数据,而简单的数据不足以解决我们希望用计算解决的许多问题。程序通常设计用于对复杂现象进行建模,往往必须构建具有多个部分的计算对象,以对具有多个方面的现实世界现象进行建模。因此,尽管我们在第章中的重点是通过组合 + + 过程 + 函数 + + 形成复合 + + 过程, + 函数, + + 来构建抽象,而在本章中,我们转向任何编程语言的另一个关键方面:它提供通过组合数据对象形成复合数据的手段。 + + + + 为什么我们需要在编程语言中使用复合数据?因为我们需要复合 + + 过程: + 函数: + + 以提升我们设计程序时的概念层次,增加设计的模块化,并增强语言的表达能力。正如能够 + + 定义过程 + 声明函数 + + 使我们能够在高于语言原始操作的概念层次上处理过程一样,构建复合数据对象的能力使我们能够在高于语言原始数据对象的概念层次上处理数据。 + + + + + 考虑设计一个系统以执行 + 有理数运算复合数据的需求 + 的任务。在简单数据的意义上,有理数可以被认为是两个整数:一个分子和一个分母。因此,我们可以设计一个程序,其中每个有理数都由两个整数(一个分子和一个分母)表示,其中 + + add-rat + add_rat + + 由两个 + + 过程 + 函数 + + (一个产生和的分子,一个产生和的分母)实现。但这将很麻烦,因为我们需要明确地跟踪哪些分子对应于哪些分母。在一个意图在许多有理数上执行多次运算的系统中,这样的数据维护会极大地混淆程序,更不用说对我们的思维造成的负担。 如果我们能够结合到一起一个分子和分母以形成一个对一个复合数据对象,这样程序就可以以一致的方式将有理数视为单一概念单位来操作,那将会好得多。 + + + + 使用复合数据还使我们能够提高程序的模块化。如果我们能够直接将有理数作为对象来操作,那么我们就能将程序中处理有理数本身的部分与如何将有理数表示为整数对的细节分开。将程序中处理数据对象表示方式的部分与处理数据对象使用方式的部分分开的总体技术是一种强大的设计方法,称为 + 数据抽象 + 数据抽象。我们将看到数据抽象如何使程序更容易设计、维护和修改。 + + + + The use of compound data leads to a real increase in the expressive power + of our programming language. Consider the idea of forming a + 线性组合$ax+by$. We + might like to write a + + 过程 + 函数 + $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 + + 定义过程 + 声明函数 + + 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 + + 用过程术语表达,形成 + 描述一个形成的过程 + + 过程 + 函数 + + +(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 + + 过程 + 函数 + + 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 + + a, + b, x, and + y is that the + + 过程 + 函数 + add and mul will + perform the appropriate manipulations. From the perspective of the + + 过程 + 函数 + + linear-combination, + linear_combination, + 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 + + 过程 + 函数 + + linear-combination + linear_combination + + add and + mul without having to know their detailed + structure.直接操作 + + 过程 + 函数 + + 的能力在编程语言的表达能力上提供了类似的提升。例如,在 + section中,我们引入了 + 求和 + + 过程, + 函数, + + 它将 + + 过程 + 函数 + + 作为参数,并计算在某个特定区间上的值的和。 + 为了定义求和,能够独立地将 + + 过程 + 函数 + + 例如作为一个实体是至关重要的,而不考虑如何用更原始的操作表示。实际上,如果我们没有 + 过程,函数,”的概念 + ,我们可能甚至不会想到定义像求和这样的操作。此外,就执行求和而言,如何从更原始的操作构建的细节是无关紧要的。复合数据, 需要数据复合 + + + 我们从实现上述提到的有理数运算系统开始本章。这将成为我们讨论复合数据和数据抽象的背景。与复合 + + 过程, + 函数, + + 一样,主要要解决的问题是如何通过抽象作为一种应对复杂性的技术,我们将看到数据抽象如何使我们能够在程序的不同部分之间建立合适的 + 抽象屏障 + 抽象屏障。 + + + + 我们将看到,构成复合数据的关键在于编程语言应提供某种胶水,以便将数据对象组合成更复杂的数据对象。有许多种可能的胶水。事实上,我们将发现如何在没有任何特殊数据操作的情况下,仅使用 + + 过程。 + 函数。 + + 这样做将进一步模糊 + + 过程 + 函数 + + 和数据之间的区别,这种区别已经在第章末端变得微弱。我们还将探讨一些常规技术,用于表示序列和树。处理复合数据的一个关键概念是 + 闭包 + 闭包我们用来组合数据对象的胶水应该允许我们不仅组合原始数据对象,还可以组合复合数据对象。另一个关键概念是复合数据对象可以作为 + 常规接口 + 常规接口用于混合匹配方式组合程序模块。我们通过介绍一个利用闭包的简单图形语言来说明这些概念。 + + + + 然后,我们将通过引入 + 符号表达式 + 表达式符号 + 符号表达式其基本部分可以是任意符号而不仅仅是数字的数据,来增强我们语言的表达能力。我们探讨用于表示对象集合的各种替代方案。我们将发现,正如一个给定的数值函数可以通过许多不同的计算过程计算一样,一个给定的数据结构可以通过更简单的对象来表示,而且表示的选择可以对操纵数据的过程的时间和空间需求产生重大影响。我们将在符号微分、集合表示和信息编码的背景下探讨这些想法。 + + + + + 接下来,我们将处理程序的不同部分可能以不同方式表示数据的问题。这导致需要实现 + 通用操作 + 操作通用 + 通用操作,它必须处理许多不同类型的数据。在存在通用操作的情况下保持模块化需要比单纯的数据抽象更强大的抽象屏障。特别是,我们引入数据导向编程,作为一种允许独立地设计单个数据表示并然后可加性 + 可加性(即无需修改)组合的方法。为了说明这种系统设计方法的威力,我们在本章末尾应用所学的知识实现一个用于对多项式进行符号运算的包,其中多项式的系数可以是整数、有理数、复数,甚至是其他多项式。 + + + + + + &section2.1; + + + &section2.2; + + + &section2.3; + + + &section2.4; + + + &section2.5; + + diff --git a/xml/Makefile b/xml/en/Makefile similarity index 100% rename from xml/Makefile rename to xml/en/Makefile diff --git a/xml/chapter1/chapter1.xml b/xml/en/chapter1/chapter1.xml similarity index 100% rename from xml/chapter1/chapter1.xml rename to xml/en/chapter1/chapter1.xml diff --git a/xml/chapter1/section1/section1.xml b/xml/en/chapter1/section1/section1.xml similarity index 100% rename from xml/chapter1/section1/section1.xml rename to xml/en/chapter1/section1/section1.xml diff --git a/xml/chapter1/section1/subsection1.xml b/xml/en/chapter1/section1/subsection1.xml similarity index 100% rename from xml/chapter1/section1/subsection1.xml rename to xml/en/chapter1/section1/subsection1.xml diff --git a/xml/chapter1/section1/subsection2.xml b/xml/en/chapter1/section1/subsection2.xml similarity index 100% rename from xml/chapter1/section1/subsection2.xml rename to xml/en/chapter1/section1/subsection2.xml diff --git a/xml/chapter1/section1/subsection3.xml b/xml/en/chapter1/section1/subsection3.xml similarity index 100% rename from xml/chapter1/section1/subsection3.xml rename to xml/en/chapter1/section1/subsection3.xml diff --git a/xml/chapter1/section1/subsection4.xml b/xml/en/chapter1/section1/subsection4.xml similarity index 100% rename from xml/chapter1/section1/subsection4.xml rename to xml/en/chapter1/section1/subsection4.xml diff --git a/xml/chapter1/section1/subsection5.xml b/xml/en/chapter1/section1/subsection5.xml similarity index 100% rename from xml/chapter1/section1/subsection5.xml rename to xml/en/chapter1/section1/subsection5.xml diff --git a/xml/chapter1/section1/subsection6.xml b/xml/en/chapter1/section1/subsection6.xml similarity index 100% rename from xml/chapter1/section1/subsection6.xml rename to xml/en/chapter1/section1/subsection6.xml diff --git a/xml/chapter1/section1/subsection7.xml b/xml/en/chapter1/section1/subsection7.xml similarity index 100% rename from xml/chapter1/section1/subsection7.xml rename to xml/en/chapter1/section1/subsection7.xml diff --git a/xml/chapter1/section1/subsection8.xml b/xml/en/chapter1/section1/subsection8.xml similarity index 100% rename from xml/chapter1/section1/subsection8.xml rename to xml/en/chapter1/section1/subsection8.xml diff --git a/xml/chapter1/section2/section2.xml b/xml/en/chapter1/section2/section2.xml similarity index 100% rename from xml/chapter1/section2/section2.xml rename to xml/en/chapter1/section2/section2.xml diff --git a/xml/chapter1/section2/subsection1.xml b/xml/en/chapter1/section2/subsection1.xml similarity index 100% rename from xml/chapter1/section2/subsection1.xml rename to xml/en/chapter1/section2/subsection1.xml diff --git a/xml/chapter1/section2/subsection2.xml b/xml/en/chapter1/section2/subsection2.xml similarity index 100% rename from xml/chapter1/section2/subsection2.xml rename to xml/en/chapter1/section2/subsection2.xml diff --git a/xml/chapter1/section2/subsection3.xml b/xml/en/chapter1/section2/subsection3.xml similarity index 100% rename from xml/chapter1/section2/subsection3.xml rename to xml/en/chapter1/section2/subsection3.xml diff --git a/xml/chapter1/section2/subsection4.xml b/xml/en/chapter1/section2/subsection4.xml similarity index 100% rename from xml/chapter1/section2/subsection4.xml rename to xml/en/chapter1/section2/subsection4.xml diff --git a/xml/chapter1/section2/subsection5.xml b/xml/en/chapter1/section2/subsection5.xml similarity index 100% rename from xml/chapter1/section2/subsection5.xml rename to xml/en/chapter1/section2/subsection5.xml diff --git a/xml/chapter1/section2/subsection6.xml b/xml/en/chapter1/section2/subsection6.xml similarity index 100% rename from xml/chapter1/section2/subsection6.xml rename to xml/en/chapter1/section2/subsection6.xml diff --git a/xml/chapter1/section3/section3.xml b/xml/en/chapter1/section3/section3.xml similarity index 100% rename from xml/chapter1/section3/section3.xml rename to xml/en/chapter1/section3/section3.xml diff --git a/xml/chapter1/section3/subsection1.xml b/xml/en/chapter1/section3/subsection1.xml similarity index 100% rename from xml/chapter1/section3/subsection1.xml rename to xml/en/chapter1/section3/subsection1.xml diff --git a/xml/chapter1/section3/subsection2.xml b/xml/en/chapter1/section3/subsection2.xml similarity index 100% rename from xml/chapter1/section3/subsection2.xml rename to xml/en/chapter1/section3/subsection2.xml diff --git a/xml/chapter1/section3/subsection3.xml b/xml/en/chapter1/section3/subsection3.xml similarity index 100% rename from xml/chapter1/section3/subsection3.xml rename to xml/en/chapter1/section3/subsection3.xml diff --git a/xml/chapter1/section3/subsection4.xml b/xml/en/chapter1/section3/subsection4.xml similarity index 100% rename from xml/chapter1/section3/subsection4.xml rename to xml/en/chapter1/section3/subsection4.xml diff --git a/xml/chapter2/chapter2.xml b/xml/en/chapter2/chapter2.xml similarity index 100% rename from xml/chapter2/chapter2.xml rename to xml/en/chapter2/chapter2.xml diff --git a/xml/chapter2/section1/section1.xml b/xml/en/chapter2/section1/section1.xml similarity index 100% rename from xml/chapter2/section1/section1.xml rename to xml/en/chapter2/section1/section1.xml diff --git a/xml/chapter2/section1/subsection1.xml b/xml/en/chapter2/section1/subsection1.xml similarity index 100% rename from xml/chapter2/section1/subsection1.xml rename to xml/en/chapter2/section1/subsection1.xml diff --git a/xml/chapter2/section1/subsection2.xml b/xml/en/chapter2/section1/subsection2.xml similarity index 100% rename from xml/chapter2/section1/subsection2.xml rename to xml/en/chapter2/section1/subsection2.xml diff --git a/xml/chapter2/section1/subsection3.xml b/xml/en/chapter2/section1/subsection3.xml similarity index 100% rename from xml/chapter2/section1/subsection3.xml rename to xml/en/chapter2/section1/subsection3.xml diff --git a/xml/chapter2/section1/subsection4.xml b/xml/en/chapter2/section1/subsection4.xml similarity index 100% rename from xml/chapter2/section1/subsection4.xml rename to xml/en/chapter2/section1/subsection4.xml diff --git a/xml/chapter2/section2/section2.xml b/xml/en/chapter2/section2/section2.xml similarity index 100% rename from xml/chapter2/section2/section2.xml rename to xml/en/chapter2/section2/section2.xml diff --git a/xml/chapter2/section2/subsection1.xml b/xml/en/chapter2/section2/subsection1.xml similarity index 100% rename from xml/chapter2/section2/subsection1.xml rename to xml/en/chapter2/section2/subsection1.xml diff --git a/xml/chapter2/section2/subsection2.xml b/xml/en/chapter2/section2/subsection2.xml similarity index 100% rename from xml/chapter2/section2/subsection2.xml rename to xml/en/chapter2/section2/subsection2.xml diff --git a/xml/chapter2/section2/subsection3.xml b/xml/en/chapter2/section2/subsection3.xml similarity index 100% rename from xml/chapter2/section2/subsection3.xml rename to xml/en/chapter2/section2/subsection3.xml diff --git a/xml/chapter2/section2/subsection4.xml b/xml/en/chapter2/section2/subsection4.xml similarity index 100% rename from xml/chapter2/section2/subsection4.xml rename to xml/en/chapter2/section2/subsection4.xml diff --git a/xml/chapter2/section3/section3.xml b/xml/en/chapter2/section3/section3.xml similarity index 100% rename from xml/chapter2/section3/section3.xml rename to xml/en/chapter2/section3/section3.xml diff --git a/xml/chapter2/section3/subsection1.xml b/xml/en/chapter2/section3/subsection1.xml similarity index 100% rename from xml/chapter2/section3/subsection1.xml rename to xml/en/chapter2/section3/subsection1.xml diff --git a/xml/chapter2/section3/subsection2.xml b/xml/en/chapter2/section3/subsection2.xml similarity index 100% rename from xml/chapter2/section3/subsection2.xml rename to xml/en/chapter2/section3/subsection2.xml diff --git a/xml/chapter2/section3/subsection3.xml b/xml/en/chapter2/section3/subsection3.xml similarity index 100% rename from xml/chapter2/section3/subsection3.xml rename to xml/en/chapter2/section3/subsection3.xml diff --git a/xml/chapter2/section3/subsection4.xml b/xml/en/chapter2/section3/subsection4.xml similarity index 100% rename from xml/chapter2/section3/subsection4.xml rename to xml/en/chapter2/section3/subsection4.xml diff --git a/xml/chapter2/section4/section4.xml b/xml/en/chapter2/section4/section4.xml similarity index 100% rename from xml/chapter2/section4/section4.xml rename to xml/en/chapter2/section4/section4.xml diff --git a/xml/chapter2/section4/subsection1.xml b/xml/en/chapter2/section4/subsection1.xml similarity index 100% rename from xml/chapter2/section4/subsection1.xml rename to xml/en/chapter2/section4/subsection1.xml diff --git a/xml/chapter2/section4/subsection2.xml b/xml/en/chapter2/section4/subsection2.xml similarity index 100% rename from xml/chapter2/section4/subsection2.xml rename to xml/en/chapter2/section4/subsection2.xml diff --git a/xml/chapter2/section4/subsection3.xml b/xml/en/chapter2/section4/subsection3.xml similarity index 100% rename from xml/chapter2/section4/subsection3.xml rename to xml/en/chapter2/section4/subsection3.xml diff --git a/xml/chapter2/section5/section5.xml b/xml/en/chapter2/section5/section5.xml similarity index 100% rename from xml/chapter2/section5/section5.xml rename to xml/en/chapter2/section5/section5.xml diff --git a/xml/chapter2/section5/subsection1.xml b/xml/en/chapter2/section5/subsection1.xml similarity index 100% rename from xml/chapter2/section5/subsection1.xml rename to xml/en/chapter2/section5/subsection1.xml diff --git a/xml/chapter2/section5/subsection2.xml b/xml/en/chapter2/section5/subsection2.xml similarity index 100% rename from xml/chapter2/section5/subsection2.xml rename to xml/en/chapter2/section5/subsection2.xml diff --git a/xml/chapter2/section5/subsection3.xml b/xml/en/chapter2/section5/subsection3.xml similarity index 100% rename from xml/chapter2/section5/subsection3.xml rename to xml/en/chapter2/section5/subsection3.xml diff --git a/xml/chapter3/chapter3.xml b/xml/en/chapter3/chapter3.xml similarity index 100% rename from xml/chapter3/chapter3.xml rename to xml/en/chapter3/chapter3.xml diff --git a/xml/chapter3/section1/section1.xml b/xml/en/chapter3/section1/section1.xml similarity index 100% rename from xml/chapter3/section1/section1.xml rename to xml/en/chapter3/section1/section1.xml diff --git a/xml/chapter3/section1/subsection1.xml b/xml/en/chapter3/section1/subsection1.xml similarity index 100% rename from xml/chapter3/section1/subsection1.xml rename to xml/en/chapter3/section1/subsection1.xml diff --git a/xml/chapter3/section1/subsection2.xml b/xml/en/chapter3/section1/subsection2.xml similarity index 100% rename from xml/chapter3/section1/subsection2.xml rename to xml/en/chapter3/section1/subsection2.xml diff --git a/xml/chapter3/section1/subsection3.xml b/xml/en/chapter3/section1/subsection3.xml similarity index 100% rename from xml/chapter3/section1/subsection3.xml rename to xml/en/chapter3/section1/subsection3.xml diff --git a/xml/chapter3/section2/section2.xml b/xml/en/chapter3/section2/section2.xml similarity index 100% rename from xml/chapter3/section2/section2.xml rename to xml/en/chapter3/section2/section2.xml diff --git a/xml/chapter3/section2/subsection1.xml b/xml/en/chapter3/section2/subsection1.xml similarity index 100% rename from xml/chapter3/section2/subsection1.xml rename to xml/en/chapter3/section2/subsection1.xml diff --git a/xml/chapter3/section2/subsection2.xml b/xml/en/chapter3/section2/subsection2.xml similarity index 100% rename from xml/chapter3/section2/subsection2.xml rename to xml/en/chapter3/section2/subsection2.xml diff --git a/xml/chapter3/section2/subsection3.xml b/xml/en/chapter3/section2/subsection3.xml similarity index 100% rename from xml/chapter3/section2/subsection3.xml rename to xml/en/chapter3/section2/subsection3.xml diff --git a/xml/chapter3/section2/subsection4.xml b/xml/en/chapter3/section2/subsection4.xml similarity index 100% rename from xml/chapter3/section2/subsection4.xml rename to xml/en/chapter3/section2/subsection4.xml diff --git a/xml/chapter3/section2/subsection5.xml b/xml/en/chapter3/section2/subsection5.xml similarity index 100% rename from xml/chapter3/section2/subsection5.xml rename to xml/en/chapter3/section2/subsection5.xml diff --git a/xml/chapter3/section3/section3.xml b/xml/en/chapter3/section3/section3.xml similarity index 100% rename from xml/chapter3/section3/section3.xml rename to xml/en/chapter3/section3/section3.xml diff --git a/xml/chapter3/section3/subsection1.xml b/xml/en/chapter3/section3/subsection1.xml similarity index 100% rename from xml/chapter3/section3/subsection1.xml rename to xml/en/chapter3/section3/subsection1.xml diff --git a/xml/chapter3/section3/subsection2.xml b/xml/en/chapter3/section3/subsection2.xml similarity index 100% rename from xml/chapter3/section3/subsection2.xml rename to xml/en/chapter3/section3/subsection2.xml diff --git a/xml/chapter3/section3/subsection3.xml b/xml/en/chapter3/section3/subsection3.xml similarity index 100% rename from xml/chapter3/section3/subsection3.xml rename to xml/en/chapter3/section3/subsection3.xml diff --git a/xml/chapter3/section3/subsection4.xml b/xml/en/chapter3/section3/subsection4.xml similarity index 100% rename from xml/chapter3/section3/subsection4.xml rename to xml/en/chapter3/section3/subsection4.xml diff --git a/xml/chapter3/section3/subsection5.xml b/xml/en/chapter3/section3/subsection5.xml similarity index 100% rename from xml/chapter3/section3/subsection5.xml rename to xml/en/chapter3/section3/subsection5.xml diff --git a/xml/chapter3/section4/section4.xml b/xml/en/chapter3/section4/section4.xml similarity index 100% rename from xml/chapter3/section4/section4.xml rename to xml/en/chapter3/section4/section4.xml diff --git a/xml/chapter3/section4/subsection1.xml b/xml/en/chapter3/section4/subsection1.xml similarity index 100% rename from xml/chapter3/section4/subsection1.xml rename to xml/en/chapter3/section4/subsection1.xml diff --git a/xml/chapter3/section4/subsection2.xml b/xml/en/chapter3/section4/subsection2.xml similarity index 100% rename from xml/chapter3/section4/subsection2.xml rename to xml/en/chapter3/section4/subsection2.xml diff --git a/xml/chapter3/section5/section5.xml b/xml/en/chapter3/section5/section5.xml similarity index 100% rename from xml/chapter3/section5/section5.xml rename to xml/en/chapter3/section5/section5.xml diff --git a/xml/chapter3/section5/subsection1.xml b/xml/en/chapter3/section5/subsection1.xml similarity index 100% rename from xml/chapter3/section5/subsection1.xml rename to xml/en/chapter3/section5/subsection1.xml diff --git a/xml/chapter3/section5/subsection2.xml b/xml/en/chapter3/section5/subsection2.xml similarity index 100% rename from xml/chapter3/section5/subsection2.xml rename to xml/en/chapter3/section5/subsection2.xml diff --git a/xml/chapter3/section5/subsection3.xml b/xml/en/chapter3/section5/subsection3.xml similarity index 100% rename from xml/chapter3/section5/subsection3.xml rename to xml/en/chapter3/section5/subsection3.xml diff --git a/xml/chapter3/section5/subsection4.xml b/xml/en/chapter3/section5/subsection4.xml similarity index 100% rename from xml/chapter3/section5/subsection4.xml rename to xml/en/chapter3/section5/subsection4.xml diff --git a/xml/chapter3/section5/subsection5.xml b/xml/en/chapter3/section5/subsection5.xml similarity index 100% rename from xml/chapter3/section5/subsection5.xml rename to xml/en/chapter3/section5/subsection5.xml diff --git a/xml/chapter4/chapter4.xml b/xml/en/chapter4/chapter4.xml similarity index 100% rename from xml/chapter4/chapter4.xml rename to xml/en/chapter4/chapter4.xml diff --git a/xml/chapter4/section1/section1.xml b/xml/en/chapter4/section1/section1.xml similarity index 100% rename from xml/chapter4/section1/section1.xml rename to xml/en/chapter4/section1/section1.xml diff --git a/xml/chapter4/section1/subsection1.xml b/xml/en/chapter4/section1/subsection1.xml similarity index 100% rename from xml/chapter4/section1/subsection1.xml rename to xml/en/chapter4/section1/subsection1.xml diff --git a/xml/chapter4/section1/subsection2.xml b/xml/en/chapter4/section1/subsection2.xml similarity index 100% rename from xml/chapter4/section1/subsection2.xml rename to xml/en/chapter4/section1/subsection2.xml diff --git a/xml/chapter4/section1/subsection3.xml b/xml/en/chapter4/section1/subsection3.xml similarity index 100% rename from xml/chapter4/section1/subsection3.xml rename to xml/en/chapter4/section1/subsection3.xml diff --git a/xml/chapter4/section1/subsection4.xml b/xml/en/chapter4/section1/subsection4.xml similarity index 100% rename from xml/chapter4/section1/subsection4.xml rename to xml/en/chapter4/section1/subsection4.xml diff --git a/xml/chapter4/section1/subsection5.xml b/xml/en/chapter4/section1/subsection5.xml similarity index 100% rename from xml/chapter4/section1/subsection5.xml rename to xml/en/chapter4/section1/subsection5.xml diff --git a/xml/chapter4/section1/subsection6.xml b/xml/en/chapter4/section1/subsection6.xml similarity index 100% rename from xml/chapter4/section1/subsection6.xml rename to xml/en/chapter4/section1/subsection6.xml diff --git a/xml/chapter4/section1/subsection7.xml b/xml/en/chapter4/section1/subsection7.xml similarity index 100% rename from xml/chapter4/section1/subsection7.xml rename to xml/en/chapter4/section1/subsection7.xml diff --git a/xml/chapter4/section2/section2.xml b/xml/en/chapter4/section2/section2.xml similarity index 100% rename from xml/chapter4/section2/section2.xml rename to xml/en/chapter4/section2/section2.xml diff --git a/xml/chapter4/section2/subsection1.xml b/xml/en/chapter4/section2/subsection1.xml similarity index 100% rename from xml/chapter4/section2/subsection1.xml rename to xml/en/chapter4/section2/subsection1.xml diff --git a/xml/chapter4/section2/subsection2.xml b/xml/en/chapter4/section2/subsection2.xml similarity index 100% rename from xml/chapter4/section2/subsection2.xml rename to xml/en/chapter4/section2/subsection2.xml diff --git a/xml/chapter4/section2/subsection3.xml b/xml/en/chapter4/section2/subsection3.xml similarity index 100% rename from xml/chapter4/section2/subsection3.xml rename to xml/en/chapter4/section2/subsection3.xml diff --git a/xml/chapter4/section3/section3.xml b/xml/en/chapter4/section3/section3.xml similarity index 100% rename from xml/chapter4/section3/section3.xml rename to xml/en/chapter4/section3/section3.xml diff --git a/xml/chapter4/section3/subsection1.xml b/xml/en/chapter4/section3/subsection1.xml similarity index 100% rename from xml/chapter4/section3/subsection1.xml rename to xml/en/chapter4/section3/subsection1.xml diff --git a/xml/chapter4/section3/subsection2.xml b/xml/en/chapter4/section3/subsection2.xml similarity index 100% rename from xml/chapter4/section3/subsection2.xml rename to xml/en/chapter4/section3/subsection2.xml diff --git a/xml/chapter4/section3/subsection3.xml b/xml/en/chapter4/section3/subsection3.xml similarity index 100% rename from xml/chapter4/section3/subsection3.xml rename to xml/en/chapter4/section3/subsection3.xml diff --git a/xml/chapter4/section4/section4.xml b/xml/en/chapter4/section4/section4.xml similarity index 100% rename from xml/chapter4/section4/section4.xml rename to xml/en/chapter4/section4/section4.xml diff --git a/xml/chapter4/section4/subsection1.xml b/xml/en/chapter4/section4/subsection1.xml similarity index 100% rename from xml/chapter4/section4/subsection1.xml rename to xml/en/chapter4/section4/subsection1.xml diff --git a/xml/chapter4/section4/subsection2.xml b/xml/en/chapter4/section4/subsection2.xml similarity index 100% rename from xml/chapter4/section4/subsection2.xml rename to xml/en/chapter4/section4/subsection2.xml diff --git a/xml/chapter4/section4/subsection3.xml b/xml/en/chapter4/section4/subsection3.xml similarity index 100% rename from xml/chapter4/section4/subsection3.xml rename to xml/en/chapter4/section4/subsection3.xml diff --git a/xml/chapter4/section4/subsection4.xml b/xml/en/chapter4/section4/subsection4.xml similarity index 100% rename from xml/chapter4/section4/subsection4.xml rename to xml/en/chapter4/section4/subsection4.xml diff --git a/xml/chapter5/chapter5.xml b/xml/en/chapter5/chapter5.xml similarity index 100% rename from xml/chapter5/chapter5.xml rename to xml/en/chapter5/chapter5.xml diff --git a/xml/chapter5/section1/section1.xml b/xml/en/chapter5/section1/section1.xml similarity index 100% rename from xml/chapter5/section1/section1.xml rename to xml/en/chapter5/section1/section1.xml diff --git a/xml/chapter5/section1/subsection1.xml b/xml/en/chapter5/section1/subsection1.xml similarity index 100% rename from xml/chapter5/section1/subsection1.xml rename to xml/en/chapter5/section1/subsection1.xml diff --git a/xml/chapter5/section1/subsection2.xml b/xml/en/chapter5/section1/subsection2.xml similarity index 100% rename from xml/chapter5/section1/subsection2.xml rename to xml/en/chapter5/section1/subsection2.xml diff --git a/xml/chapter5/section1/subsection3.xml b/xml/en/chapter5/section1/subsection3.xml similarity index 100% rename from xml/chapter5/section1/subsection3.xml rename to xml/en/chapter5/section1/subsection3.xml diff --git a/xml/chapter5/section1/subsection4.xml b/xml/en/chapter5/section1/subsection4.xml similarity index 100% rename from xml/chapter5/section1/subsection4.xml rename to xml/en/chapter5/section1/subsection4.xml diff --git a/xml/chapter5/section1/subsection5.xml b/xml/en/chapter5/section1/subsection5.xml similarity index 100% rename from xml/chapter5/section1/subsection5.xml rename to xml/en/chapter5/section1/subsection5.xml diff --git a/xml/chapter5/section2/section2.xml b/xml/en/chapter5/section2/section2.xml similarity index 100% rename from xml/chapter5/section2/section2.xml rename to xml/en/chapter5/section2/section2.xml diff --git a/xml/chapter5/section2/subsection1.xml b/xml/en/chapter5/section2/subsection1.xml similarity index 100% rename from xml/chapter5/section2/subsection1.xml rename to xml/en/chapter5/section2/subsection1.xml diff --git a/xml/chapter5/section2/subsection2.xml b/xml/en/chapter5/section2/subsection2.xml similarity index 100% rename from xml/chapter5/section2/subsection2.xml rename to xml/en/chapter5/section2/subsection2.xml diff --git a/xml/chapter5/section2/subsection3.xml b/xml/en/chapter5/section2/subsection3.xml similarity index 100% rename from xml/chapter5/section2/subsection3.xml rename to xml/en/chapter5/section2/subsection3.xml diff --git a/xml/chapter5/section2/subsection4.xml b/xml/en/chapter5/section2/subsection4.xml similarity index 100% rename from xml/chapter5/section2/subsection4.xml rename to xml/en/chapter5/section2/subsection4.xml diff --git a/xml/chapter5/section3/section3.xml b/xml/en/chapter5/section3/section3.xml similarity index 100% rename from xml/chapter5/section3/section3.xml rename to xml/en/chapter5/section3/section3.xml diff --git a/xml/chapter5/section3/subsection1.xml b/xml/en/chapter5/section3/subsection1.xml similarity index 100% rename from xml/chapter5/section3/subsection1.xml rename to xml/en/chapter5/section3/subsection1.xml diff --git a/xml/chapter5/section3/subsection2.xml b/xml/en/chapter5/section3/subsection2.xml similarity index 100% rename from xml/chapter5/section3/subsection2.xml rename to xml/en/chapter5/section3/subsection2.xml diff --git a/xml/chapter5/section4/section4.xml b/xml/en/chapter5/section4/section4.xml similarity index 100% rename from xml/chapter5/section4/section4.xml rename to xml/en/chapter5/section4/section4.xml diff --git a/xml/chapter5/section4/subsection1.xml b/xml/en/chapter5/section4/subsection1.xml similarity index 100% rename from xml/chapter5/section4/subsection1.xml rename to xml/en/chapter5/section4/subsection1.xml diff --git a/xml/chapter5/section4/subsection2.xml b/xml/en/chapter5/section4/subsection2.xml similarity index 100% rename from xml/chapter5/section4/subsection2.xml rename to xml/en/chapter5/section4/subsection2.xml diff --git a/xml/chapter5/section4/subsection3.xml b/xml/en/chapter5/section4/subsection3.xml similarity index 100% rename from xml/chapter5/section4/subsection3.xml rename to xml/en/chapter5/section4/subsection3.xml diff --git a/xml/chapter5/section4/subsection4.xml b/xml/en/chapter5/section4/subsection4.xml similarity index 100% rename from xml/chapter5/section4/subsection4.xml rename to xml/en/chapter5/section4/subsection4.xml diff --git a/xml/chapter5/section5/section5.xml b/xml/en/chapter5/section5/section5.xml similarity index 100% rename from xml/chapter5/section5/section5.xml rename to xml/en/chapter5/section5/section5.xml diff --git a/xml/chapter5/section5/subsection1.xml b/xml/en/chapter5/section5/subsection1.xml similarity index 100% rename from xml/chapter5/section5/subsection1.xml rename to xml/en/chapter5/section5/subsection1.xml diff --git a/xml/chapter5/section5/subsection2.xml b/xml/en/chapter5/section5/subsection2.xml similarity index 100% rename from xml/chapter5/section5/subsection2.xml rename to xml/en/chapter5/section5/subsection2.xml diff --git a/xml/chapter5/section5/subsection3.xml b/xml/en/chapter5/section5/subsection3.xml similarity index 100% rename from xml/chapter5/section5/subsection3.xml rename to xml/en/chapter5/section5/subsection3.xml diff --git a/xml/chapter5/section5/subsection4.xml b/xml/en/chapter5/section5/subsection4.xml similarity index 100% rename from xml/chapter5/section5/subsection4.xml rename to xml/en/chapter5/section5/subsection4.xml diff --git a/xml/chapter5/section5/subsection5.xml b/xml/en/chapter5/section5/subsection5.xml similarity index 100% rename from xml/chapter5/section5/subsection5.xml rename to xml/en/chapter5/section5/subsection5.xml diff --git a/xml/chapter5/section5/subsection6.xml b/xml/en/chapter5/section5/subsection6.xml similarity index 100% rename from xml/chapter5/section5/subsection6.xml rename to xml/en/chapter5/section5/subsection6.xml diff --git a/xml/chapter5/section5/subsection7.xml b/xml/en/chapter5/section5/subsection7.xml similarity index 100% rename from xml/chapter5/section5/subsection7.xml rename to xml/en/chapter5/section5/subsection7.xml diff --git a/xml/contents.ent b/xml/en/contents.ent similarity index 100% rename from xml/contents.ent rename to xml/en/contents.ent diff --git a/xml/others/02foreword02.xml b/xml/en/others/02foreword02.xml similarity index 100% rename from xml/others/02foreword02.xml rename to xml/en/others/02foreword02.xml diff --git a/xml/others/02foreword84.xml b/xml/en/others/02foreword84.xml similarity index 100% rename from xml/others/02foreword84.xml rename to xml/en/others/02foreword84.xml diff --git a/xml/others/03prefaces03.xml b/xml/en/others/03prefaces03.xml similarity index 100% rename from xml/others/03prefaces03.xml rename to xml/en/others/03prefaces03.xml diff --git a/xml/others/03prefaces96.xml b/xml/en/others/03prefaces96.xml similarity index 100% rename from xml/others/03prefaces96.xml rename to xml/en/others/03prefaces96.xml diff --git a/xml/others/04acknowledgements04.xml b/xml/en/others/04acknowledgements04.xml similarity index 100% rename from xml/others/04acknowledgements04.xml rename to xml/en/others/04acknowledgements04.xml diff --git a/xml/others/06see06.xml b/xml/en/others/06see06.xml similarity index 100% rename from xml/others/06see06.xml rename to xml/en/others/06see06.xml diff --git a/xml/others/97references97.xml b/xml/en/others/97references97.xml similarity index 100% rename from xml/others/97references97.xml rename to xml/en/others/97references97.xml diff --git a/xml/others/98indexpreface98.xml b/xml/en/others/98indexpreface98.xml similarity index 100% rename from xml/others/98indexpreface98.xml rename to xml/en/others/98indexpreface98.xml diff --git a/xml/others/99making99.xml b/xml/en/others/99making99.xml similarity index 100% rename from xml/others/99making99.xml rename to xml/en/others/99making99.xml diff --git a/yarn.lock b/yarn.lock index 4fbfb697f..376fb1851 100644 --- a/yarn.lock +++ b/yarn.lock @@ -971,40 +971,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/node-fetch@^2.6.4": - version "2.6.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" - integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== - dependencies: - "@types/node" "*" - form-data "^4.0.0" - -"@types/node@*": - version "22.13.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" - integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== - dependencies: - undici-types "~6.20.0" - -"@types/node@^18.11.18": - version "18.19.80" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.80.tgz#6d6008e8920dddcd23f9dd33da24684ef57d487c" - integrity sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ== - dependencies: - undici-types "~5.26.4" - abbrev@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-class-fields@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/acorn-class-fields/-/acorn-class-fields-1.0.0.tgz#b413793e6b3ddfcd17a02f9c7a850f4bbfdc1c7a" @@ -1046,13 +1017,6 @@ agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: dependencies: debug "^4.3.4" -agentkeepalive@^4.2.1: - version "4.6.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" - integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== - dependencies: - humanize-ms "^1.2.1" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1095,11 +1059,6 @@ async@^2.6.2: dependencies: lodash "^4.17.14" -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - babel-plugin-polyfill-corejs2@^0.4.10: version "0.4.10" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" @@ -1227,14 +1186,6 @@ cacache@^18.0.0: tar "^6.1.11" unique-filename "^3.0.0" -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1276,13 +1227,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-progress@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" - integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== - dependencies: - string-width "^4.2.3" - clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -1304,13 +1248,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - commander@^12.0.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -1395,30 +1332,11 @@ define-properties@^1.1.2, define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - detect-libc@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== -dotenv@^16.4.7: - version "16.4.7" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" - integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -1485,33 +1403,6 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -1531,11 +1422,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -1610,29 +1496,6 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" -form-data-encoder@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" - integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== - -form-data@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" - integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - mime-types "^2.1.12" - -formdata-node@^4.3.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2" - integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== - dependencies: - node-domexception "1.0.0" - web-streams-polyfill "4.0.0-beta.3" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -1666,11 +1529,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -1686,30 +1544,6 @@ get-intrinsic@^1.0.2: has-proto "^1.0.1" has-symbols "^1.0.3" -get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -1764,11 +1598,6 @@ glsl-tokenizer@^2.1.5: dependencies: through2 "^0.6.3" -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - gpu-mock.js@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/gpu-mock.js/-/gpu-mock.js-1.3.1.tgz#f7deaa09da3f672762eda944ecf948fd2c4b1490" @@ -1805,18 +1634,6 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1824,13 +1641,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -1899,13 +1709,6 @@ https-proxy-agent@^7.0.1: agent-base "^7.0.2" debug "4" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - husky@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" @@ -2178,11 +1981,6 @@ make-fetch-happen@^13.0.0: promise-retry "^2.0.1" ssri "^10.0.0" -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -2196,18 +1994,6 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -2323,11 +2109,6 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - nan@^2.18.0: version "2.20.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" @@ -2350,11 +2131,6 @@ node-abi@^3.3.0, node-abi@^3.56.0: dependencies: semver "^7.3.5" -node-domexception@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" - integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== - node-environment-flags@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -2363,13 +2139,6 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-gyp@^10.0.1: version "10.2.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-10.2.0.tgz#80101c4aa4f7ab225f13fcc8daaaac4eb1a8dd86" @@ -2438,19 +2207,6 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -openai@^4.81.0: - version "4.87.3" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.87.3.tgz#82679f09d91f0e8e9da94b9ee0369c44733577da" - integrity sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ== - dependencies: - "@types/node" "^18.11.18" - "@types/node-fetch" "^2.6.4" - abort-controller "^3.0.0" - agentkeepalive "^4.2.1" - form-data-encoder "1.7.2" - formdata-node "^4.3.2" - node-fetch "^2.6.7" - opener@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" @@ -2770,11 +2526,6 @@ safe-buffer@^5.0.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" - integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== - secure-compare@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" @@ -2921,7 +2672,7 @@ ssri@^10.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.3: +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -3066,11 +2817,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - tslib@^1.9.2: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -3083,16 +2829,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== - unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -3172,21 +2908,11 @@ v8flags@^3.1.1: dependencies: homedir-polyfill "^1.0.1" -web-streams-polyfill@4.0.0-beta.3: - version "4.0.0-beta.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" - integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== - webgpu@^0.1.16: version "0.1.16" resolved "https://registry.yarnpkg.com/webgpu/-/webgpu-0.1.16.tgz#dec416373e308181b28864b58c8a914461d7ceee" integrity sha512-KAXn/f8lnL8o4B718zzdfi1l0nEWQpuoWlC1L5WM/svAbeHjShCEI0l5ZcZBEEUm9FF3ZTgRjWk8iwbJfnGKTA== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" @@ -3194,14 +2920,6 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From d1104298445043921e9bf732c5b910b270cae79e Mon Sep 17 00:00:00 2001 From: yihao Date: Fri, 11 Apr 2025 00:00:09 +0800 Subject: [PATCH 17/55] Fix file path resolution in translate function and update package manager version in package.json --- i18n/controllers/recurTranslate.ts | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 1421d542d..3aeeeb702 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -29,7 +29,7 @@ async function translate(language: string, filePath: string): Promise { try { // Pipe the XML file into the parser. const input_dir = fileURLToPath( - import.meta.resolve("../../xml" + filePath) + import.meta.resolve("../../xml/en" + filePath) ); const translated: string = await recursivelyTranslate(language, input_dir); diff --git a/package.json b/package.json index 032f42b40..2f6aaff40 100644 --- a/package.json +++ b/package.json @@ -64,5 +64,6 @@ "hooks": { "pre-push": "yarn lint" } - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } From a115312b1b04af00b8838953f85c94b9dcbb87c6 Mon Sep 17 00:00:00 2001 From: yihao Date: Fri, 11 Apr 2025 13:03:46 +0800 Subject: [PATCH 18/55] Refactor translation logic to simplify segment handling and improve readability --- i18n/controllers/recurTranslate.ts | 125 ++++++++++++++--------------- 1 file changed, 59 insertions(+), 66 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 3aeeeb702..2f76c4bfc 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -6,6 +6,7 @@ import dotenv from "dotenv"; import sax from "sax"; import { Readable } from "stream"; import { fileURLToPath } from "url"; +import { isGeneratorObject } from "util/types"; dotenv.config(); @@ -19,6 +20,8 @@ const ai = new OpenAI({ baseURL: process.env.AI_BASEURL }); +const ignoredTags = ["LATEXINLINE", "LATEX", "SNIPPET", "SCHEMEINLINE", "SCHEME", "LONG_PAGE", "LABEL"]; + const MAXLEN = Number(process.env.MAX_LEN) || 3000; const createParser = () => @@ -58,8 +61,8 @@ async function recursivelyTranslate( path: string ): Promise { // Recursive function to split and translate - async function helper(ori: string, force: boolean): Promise { - if (ori.length < MAXLEN && !force) { + async function helper(ori: string): Promise { + if (ori.length < MAXLEN) { return await translateChunk(ori); // translate the chunk } @@ -69,21 +72,19 @@ async function recursivelyTranslate( await new Promise((resolve, reject) => { const subParser = createParser(); - let subCurrentDepth = 1; + let subCurrentDepth = 0; let subCurrentSegment = ""; const subSegments: [boolean, string][] = []; let subIsRecording = false; subParser.on("opentag", node => { - if (node.name === "WRAPPER") { - return; - } - + if (node.name === "WRAPPER") return; + subCurrentDepth++; - // If we're at depth 2, this is the start of a new segment. - if (subCurrentDepth === 2 || subIsRecording) { - subIsRecording = true; + if (subCurrentDepth === 2) subIsRecording = true; + + if (subIsRecording) { subCurrentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; } else { subSegments.push([ @@ -97,21 +98,19 @@ async function recursivelyTranslate( text = strongEscapeXML(text); if (subIsRecording) { subCurrentSegment += text; + } else if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] + ) { + subSegments[subSegments.length - 1][1] += text; + } else if ( + text.trim() === "" || + text.trim() === "," || + text.trim() === "." + ) { + subSegments.push([false, text]); } else { - if ( - subSegments.length > 0 && - subSegments[subSegments.length - 1][0] - ) { - subSegments[subSegments.length - 1][1] += text; - } else if ( - text.trim() !== "" || - text.trim() === "," || - text.trim() === "." - ) { - subSegments.push([false, text]); - } else { - subSegments.push([true, text]); - } + subSegments.push([true, text]); } }); @@ -125,41 +124,35 @@ async function recursivelyTranslate( if (tagName === "WRAPPER") { return; } - - if (subIsRecording) { - subCurrentSegment += ``; - } + + subCurrentSegment += ``; if (subCurrentDepth === 2) { // We are closing a segment element. if ( - tagName === "LATEXINLINE" || - tagName === "LATEX" || - tagName === "SNIPPET" || - tagName === "SCHEMEINLINE" + ignoredTags.includes(tagName) ) { subSegments.push([false, subCurrentSegment]); + } else if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] && + subSegments[subSegments.length - 1][1].length + + subCurrentSegment.length < + MAXLEN + ) { + subSegments[subSegments.length - 1][1] += subCurrentSegment; } else { - if ( - subSegments.length > 0 && - subSegments[subSegments.length - 1][0] && - (subSegments[subSegments.length - 1][1].length + - subCurrentSegment.length) < - MAXLEN - ) { - subSegments[subSegments.length - 1][1] += subCurrentSegment; - } else { subSegments.push([true, subCurrentSegment]); - } } subCurrentSegment = ""; subIsRecording = false; } - + if (subCurrentDepth === 1) { - // We are closing the root element. - subSegments.push([false, ``]); + subSegments.push([false, ``]) + subCurrentSegment = ""; } + subCurrentDepth--; }); @@ -174,7 +167,7 @@ async function recursivelyTranslate( subParser.on("end", async () => { for (const segment of subSegments) { if (segment[0]) { - subTranslated.push(await helper(segment[1], false)); + subTranslated.push(await helper(segment[1])); } else { subTranslated.push(segment[1]); } @@ -248,28 +241,23 @@ async function recursivelyTranslate( } if (currentDepth === 2) { + isRecording = false; // We are closing a segment element. - if ( - tagName === "LATEXINLINE" || - tagName === "LATEX" || - tagName === "SNIPPET" || - tagName === "SCHEMEINLINE" || - tagName === "SCHEME" - ) { + if (ignoredTags.includes(tagName)) { segments.push([false, currentSegment]); } else { if ( segments.length > 0 && segments[segments.length - 1][0] && - (segments[segments.length - 1][1].length + - currentSegment.length) < + segments[segments.length - 1][1].length + currentSegment.length < MAXLEN ) { segments[segments.length - 1][1] += currentSegment; } else { - segments.push([true, currentSegment]); + segments.push([true, currentSegment]); } } + currentSegment = ""; } if (currentDepth === 1) { @@ -291,7 +279,7 @@ async function recursivelyTranslate( parser.on("end", async () => { for (const segment of segments) { if (segment[0]) { - translated.push(await helper(segment[1], false)); + translated.push(await helper(segment[1])); } else { translated.push(segment[1]); } @@ -314,12 +302,12 @@ async function recursivelyTranslate( if (chunk.trim() === "" || chunk.trim() === "," || chunk.trim() === ".") { return chunk; } - + // console.log("Translating chunk of length: " + chunk.length); - if (chunk.length < 100) { - console.log("\nchunk: " + chunk) - } - + // if (chunk.length < 100) { + // console.log("\nchunk: " + chunk); + // } + let translatedChunk = ""; try { @@ -332,7 +320,7 @@ async function recursivelyTranslate( Content to translate: ${chunk} ` }); - + const run = await ai.beta.threads.runs.createAndPoll(thread.id, { assistant_id: assistant_id }); @@ -353,7 +341,7 @@ async function recursivelyTranslate( const text = messageContent.text; const safeText = escapeXML(text.value); - console.log(safeText); + // const safeText = chunk; const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { @@ -394,13 +382,18 @@ async function recursivelyTranslate( clean.on("error", error => { console.log( "error encountered when validating XML: " + - error + "\nfile: " + path + + error + + "\nfile: " + + path + "\n section: " + - (safeText.length > 50 ? safeText.substring(0, 100) + "..." : safeText ) + safeText + + "\n original text: " + + chunk ); // Attempt to recover using the internal parser try { + clean._parser.error = null; clean._parser.resume(); } catch (e) { console.log("Failed to resume parser:", e); From 89841d664312df809ef73d5ab63a345f618fa872 Mon Sep 17 00:00:00 2001 From: yihao Date: Fri, 11 Apr 2025 20:03:02 +0800 Subject: [PATCH 19/55] Enhance error handling and logging in translation process, streamline file path usage --- i18n/controllers/recurTranslate.ts | 188 ++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 60 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 2f76c4bfc..63795d014 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -7,6 +7,7 @@ import sax from "sax"; import { Readable } from "stream"; import { fileURLToPath } from "url"; import { isGeneratorObject } from "util/types"; +import { AssistantStream } from "openai/lib/AssistantStream.mjs"; dotenv.config(); @@ -20,25 +21,57 @@ const ai = new OpenAI({ baseURL: process.env.AI_BASEURL }); -const ignoredTags = ["LATEXINLINE", "LATEX", "SNIPPET", "SCHEMEINLINE", "SCHEME", "LONG_PAGE", "LABEL"]; +const ignoredTags = [ + "LATEXINLINE", + "LATEX", + "SNIPPET", + "SCHEMEINLINE", + "SCHEME", + "LONG_PAGE", + "LABEL" +]; const MAXLEN = Number(process.env.MAX_LEN) || 3000; +// Centralized logging to prevent duplicate messages +const errorMessages = new Set(); +function logError(message: string, error?: any) { + // Create a unique key for this error message + const errorKey = message + (error ? error.toString() : ""); + // Only log if we haven't seen this exact message before + if (!errorMessages.has(errorKey)) { + errorMessages.add(errorKey); + if (error) { + console.error(message, error); + } else { + console.error(message); + } + } +} + const createParser = () => (sax as any).createStream(true, { trim: false }, { strictEntities: true }); async function translate(language: string, filePath: string): Promise { const startTime = new Date().getTime(); + let assistant; + try { - // Pipe the XML file into the parser. - const input_dir = fileURLToPath( - import.meta.resolve("../../xml/en" + filePath) - ); + // Use the provided file path directly without modification + const input_path = filePath; + + assistant = await createAssistant(language, ai as any); - const translated: string = await recursivelyTranslate(language, input_dir); + // Generate output path by replacing "/en/" with "/cn/" in the path + const output_path = filePath.replace( + path.sep + "en" + path.sep, + path.sep + "cn" + path.sep + ); - const output_path = fileURLToPath( - import.meta.resolve("../../xml/cn" + filePath) + const translated: string = await recursivelyTranslate( + language, + input_path, + assistant.id ); // Ensure directory exists @@ -48,17 +81,23 @@ async function translate(language: string, filePath: string): Promise { fs.writeFileSync(output_path, translated); console.log(`Translation saved to ${output_path}`); } catch (parseErr) { - console.error("Error parsing XML:", parseErr); + logError(`Error translating file ${filePath}:`, parseErr); } finally { + if (assistant) { + await ai.beta.assistants.del(assistant.id).catch(err => { + logError(`Error deleting assistant:`, err); + }); + } const elapsed = new Date().getTime() - startTime; console.log(filePath + " took " + elapsed / 1000.0 + " seconds"); } } -// TODO: change the toTranslate to a file path, read the file and translate the content +// Function to translate the content of a file async function recursivelyTranslate( language: string, - path: string + filePath: string, + assistant_id: string ): Promise { // Recursive function to split and translate async function helper(ori: string): Promise { @@ -124,14 +163,12 @@ async function recursivelyTranslate( if (tagName === "WRAPPER") { return; } - + subCurrentSegment += ``; if (subCurrentDepth === 2) { // We are closing a segment element. - if ( - ignoredTags.includes(tagName) - ) { + if (ignoredTags.includes(tagName)) { subSegments.push([false, subCurrentSegment]); } else if ( subSegments.length > 0 && @@ -147,12 +184,12 @@ async function recursivelyTranslate( subCurrentSegment = ""; subIsRecording = false; } - + if (subCurrentDepth === 1) { - subSegments.push([false, ``]) + subSegments.push([false, ``]); subCurrentSegment = ""; } - + subCurrentDepth--; }); @@ -166,16 +203,34 @@ async function recursivelyTranslate( subParser.on("end", async () => { for (const segment of subSegments) { - if (segment[0]) { - subTranslated.push(await helper(segment[1])); - } else { - subTranslated.push(segment[1]); + try { + if (segment[0]) { + subTranslated.push(await helper(segment[1])); + } else { + subTranslated.push(segment[1]); + } + } catch (error) { + logError(`Error translating segment in ${filePath}:`, error); + // Add error comment and continue with next segment + subTranslated.push( + segment[1] + `` + ); } } resolve(); }); - subParser.on("error", reject); + subParser.on("error", err => { + logError(`Error in subParser for ${filePath}:`, err); + // Try to recover and continue + try { + subParser._parser.error = null; + subParser._parser.resume(); + } catch (resumeErr) { + logError(`Could not recover from parser error:`, resumeErr); + reject(err); + } + }); Readable.from("" + ori + "").pipe(subParser); }); @@ -186,14 +241,12 @@ async function recursivelyTranslate( // Create a SAX parser in strict mode to split source into chunks. const parser = createParser(); - // const assistant = await createAssistant(language, ai as any); - const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; const thread = await ai.beta.threads.create(); let translated: String[] = []; try { await new Promise((resolve, reject) => { - console.log("Translating " + path + " at " + thread.id); + console.log("Translating " + filePath + " at " + thread.id); // Variables to track current depth and segments. let currentDepth = 0; let currentSegment = ""; @@ -278,24 +331,44 @@ async function recursivelyTranslate( parser.on("end", async () => { for (const segment of segments) { - if (segment[0]) { - translated.push(await helper(segment[1])); - } else { - translated.push(segment[1]); + try { + if (segment[0]) { + translated.push(await helper(segment[1])); + } else { + translated.push(segment[1]); + } + } catch (error) { + logError(`Error translating segment in ${filePath}:`, error); + // Add error comment and continue with next segment + translated.push( + segment[1] + `` + ); } } resolve(); }); - parser.on("error", reject); + parser.on("error", err => { + logError(`Parser error in ${filePath}:`, err); + // Try to recover and continue + try { + parser._parser.error = null; + parser._parser.resume(); + } catch (resumeErr) { + logError(`Could not recover from parser error:`, resumeErr); + reject(err); + } + }); - fs.createReadStream(path).pipe(parser); + // Use the file path directly without modification + fs.createReadStream(filePath).pipe(parser); }); return translated.join(""); } catch (parseErr) { - console.error("Error parsing XML:", parseErr); - return translated.join("") + ""; + logError(`Error parsing XML in ${filePath}:`, parseErr); + // Return what we have so far plus error comment + return translated.join("") + ``; } async function translateChunk(chunk: string): Promise { @@ -303,11 +376,6 @@ async function recursivelyTranslate( return chunk; } - // console.log("Translating chunk of length: " + chunk.length); - // if (chunk.length < 100) { - // console.log("\nchunk: " + chunk); - // } - let translatedChunk = ""; try { @@ -330,7 +398,7 @@ async function recursivelyTranslate( }); const message = messages.data.pop()!; - const messageContent = message.content[0]; + const messageContent = message?.content[0]; if (messageContent.type !== "text") { throw new Error( @@ -341,7 +409,6 @@ async function recursivelyTranslate( const text = messageContent.text; const safeText = escapeXML(text.value); - // const safeText = chunk; const textStream = Readable.from("" + safeText + ""); await new Promise((resolve, reject) => { @@ -359,13 +426,21 @@ async function recursivelyTranslate( clean.on("opentag", node => { currDepth++; - if (node.name != "WRAPPER" && node.name != "TRANSLATE") { + if ( + node.name != "WRAPPER" && + node.name != "TRANSLATE" && + !ignoredTags.includes(node.name) + ) { translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; } }); clean.on("closetag", tagName => { - if (tagName != "WRAPPER" && tagName != "TRANSLATE") { + if ( + tagName != "WRAPPER" && + tagName != "TRANSLATE" && + !ignoredTags.includes(tagName) + ) { translatedChunk += ``; } currDepth--; @@ -380,24 +455,19 @@ async function recursivelyTranslate( }); clean.on("error", error => { - console.log( - "error encountered when validating XML: " + - error + - "\nfile: " + - path + - "\n section: " + - safeText + - "\n original text: " + - chunk - ); + // Log only once with abbreviated content + logError(`XML validation error in ${filePath}`, error); // Attempt to recover using the internal parser try { clean._parser.error = null; clean._parser.resume(); + // Continue processing despite the error + resolve(); } catch (e) { - console.log("Failed to resume parser:", e); - reject(e); + // Add error comment and resolve instead of rejecting + translatedChunk += ``; + resolve(); } }); @@ -408,11 +478,9 @@ async function recursivelyTranslate( return translatedChunk; } catch (err) { - console.log(`Error occured while translating ${path}:\n ` + err); - return ( - translatedChunk + - `\n` - ); + logError(`Error occurred while translating chunk in ${filePath}:`, err); + // Return the original chunk with error comment rather than throwing + return chunk + ``; } } } From b37b50352c315eb77cec0fe7b15b838d20ff626b Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 14:36:21 +0800 Subject: [PATCH 20/55] added proper error logging and reporting. added ability to skip translations inside SCHEME and SCHEMEINLINE tags --- i18n/controllers/recurTranslate.ts | 86 ++++++-- i18n/index.ts | 323 +++++++++++++++++++++++++++-- i18n/initializers/initialize.ts | 8 +- 3 files changed, 377 insertions(+), 40 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 63795d014..073771784 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -5,14 +5,11 @@ import createAssistant from "../initializers/initialize"; import dotenv from "dotenv"; import sax from "sax"; import { Readable } from "stream"; -import { fileURLToPath } from "url"; -import { isGeneratorObject } from "util/types"; -import { AssistantStream } from "openai/lib/AssistantStream.mjs"; dotenv.config(); if (process.env.AI_MODEL === undefined || process.env.API_KEY === undefined) { - throw Error("Please specify AI_MODEL and API_KEY!"); + throw Error("Please specify AI_MODEL and API_KEY"); } // initialize OpenAI API @@ -35,7 +32,10 @@ const MAXLEN = Number(process.env.MAX_LEN) || 3000; // Centralized logging to prevent duplicate messages const errorMessages = new Set(); -function logError(message: string, error?: any) { +// Track errors by file for summary reporting +const fileErrors: Record = {}; + +function logError(message: string, error?: any, filePath?: string) { // Create a unique key for this error message const errorKey = message + (error ? error.toString() : ""); // Only log if we haven't seen this exact message before @@ -46,11 +46,27 @@ function logError(message: string, error?: any) { } else { console.error(message); } + + // Store error for the summary log if filePath is provided + if (filePath) { + if (!fileErrors[filePath]) { + fileErrors[filePath] = []; + } + fileErrors[filePath].push({ message, error }); + } } } +// Function to get all logged errors for summary +export function getFileErrors(): Record< + string, + { message: string; error?: any }[] +> { + return fileErrors; +} + const createParser = () => - (sax as any).createStream(true, { trim: false }, { strictEntities: true }); + (sax as any).createStream(false, { trim: false }, { strictEntities: true }); async function translate(language: string, filePath: string): Promise { const startTime = new Date().getTime(); @@ -81,11 +97,13 @@ async function translate(language: string, filePath: string): Promise { fs.writeFileSync(output_path, translated); console.log(`Translation saved to ${output_path}`); } catch (parseErr) { - logError(`Error translating file ${filePath}:`, parseErr); + logError(`Error translating file ${filePath}:`, parseErr, filePath); + // Re-throw the error to propagate it to the caller + throw parseErr; } finally { if (assistant) { await ai.beta.assistants.del(assistant.id).catch(err => { - logError(`Error deleting assistant:`, err); + logError(`Error deleting assistant:`, err, filePath); }); } const elapsed = new Date().getTime() - startTime; @@ -210,7 +228,11 @@ async function recursivelyTranslate( subTranslated.push(segment[1]); } } catch (error) { - logError(`Error translating segment in ${filePath}:`, error); + logError( + `Error translating segment in ${filePath}:`, + error, + filePath + ); // Add error comment and continue with next segment subTranslated.push( segment[1] + `` @@ -221,13 +243,13 @@ async function recursivelyTranslate( }); subParser.on("error", err => { - logError(`Error in subParser for ${filePath}:`, err); + logError(`Error in subParser for ${filePath}:`, err, filePath); // Try to recover and continue try { subParser._parser.error = null; subParser._parser.resume(); } catch (resumeErr) { - logError(`Could not recover from parser error:`, resumeErr); + logError(`Could not recover from parser error:`, resumeErr, filePath); reject(err); } }); @@ -262,6 +284,8 @@ async function recursivelyTranslate( currentDepth++; // If we're at depth 2, this is the start of a new segment. + if (node.name == "SCHEME" || node.name == "SCHEMEINLINE") return; + if (currentDepth === 2 || isRecording) { isRecording = true; currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; @@ -275,6 +299,10 @@ async function recursivelyTranslate( parser.on("text", text => { text = strongEscapeXML(text); + + // ignore all scheme contents + if(parser._parser.tag == "SCHEME" || parser._parser.tag == "SCHEMEINLINE") return; + if (isRecording) { currentSegment += text; } else { @@ -289,7 +317,7 @@ async function recursivelyTranslate( }); parser.on("closetag", tagName => { - if (isRecording) { + if (tagName !== "SCHEME" && tagName !== "SCHEMEINLINE" && isRecording) { currentSegment += ``; } @@ -338,7 +366,11 @@ async function recursivelyTranslate( translated.push(segment[1]); } } catch (error) { - logError(`Error translating segment in ${filePath}:`, error); + logError( + `Error translating segment in ${filePath}:`, + error, + filePath + ); // Add error comment and continue with next segment translated.push( segment[1] + `` @@ -349,13 +381,13 @@ async function recursivelyTranslate( }); parser.on("error", err => { - logError(`Parser error in ${filePath}:`, err); + logError(`Parser error in ${filePath}:`, err, filePath); // Try to recover and continue try { parser._parser.error = null; parser._parser.resume(); } catch (resumeErr) { - logError(`Could not recover from parser error:`, resumeErr); + logError(`Could not recover from parser error:`, resumeErr, filePath); reject(err); } }); @@ -366,12 +398,13 @@ async function recursivelyTranslate( return translated.join(""); } catch (parseErr) { - logError(`Error parsing XML in ${filePath}:`, parseErr); + logError(`Error parsing XML in ${filePath}:`, parseErr, filePath); // Return what we have so far plus error comment return translated.join("") + ``; } async function translateChunk(chunk: string): Promise { + return chunk; if (chunk.trim() === "" || chunk.trim() === "," || chunk.trim() === ".") { return chunk; } @@ -400,6 +433,7 @@ async function recursivelyTranslate( const message = messages.data.pop()!; const messageContent = message?.content[0]; + if (!messageContent) throw new Error(`undefined AI response`); if (messageContent.type !== "text") { throw new Error( `Unexpected message content type: ${messageContent.type}` @@ -428,8 +462,7 @@ async function recursivelyTranslate( currDepth++; if ( node.name != "WRAPPER" && - node.name != "TRANSLATE" && - !ignoredTags.includes(node.name) + node.name != "TRANSLATE" ) { translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; } @@ -438,8 +471,7 @@ async function recursivelyTranslate( clean.on("closetag", tagName => { if ( tagName != "WRAPPER" && - tagName != "TRANSLATE" && - !ignoredTags.includes(tagName) + tagName != "TRANSLATE" ) { translatedChunk += ``; } @@ -456,7 +488,11 @@ async function recursivelyTranslate( clean.on("error", error => { // Log only once with abbreviated content - logError(`XML validation error in ${filePath}`, error); + logError( + `Error validating AI response for ${filePath}`, + error, + filePath + ); // Attempt to recover using the internal parser try { @@ -478,7 +514,11 @@ async function recursivelyTranslate( return translatedChunk; } catch (err) { - logError(`Error occurred while translating chunk in ${filePath}:`, err); + logError( + `Error occurred while translating chunk in ${filePath}:`, + err, + filePath + ); // Return the original chunk with error comment rather than throwing return chunk + ``; } @@ -488,7 +528,7 @@ async function recursivelyTranslate( export default translate; // Helper function to format attributes into a string. -function formatAttributes(attrs) { +function formatAttributes(attrs: string) { const attrStr = Object.entries(attrs) .map(([key, val]) => `${key}="${val}"`) .join(" "); diff --git a/i18n/index.ts b/i18n/index.ts index bce72edbf..ce72a159f 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,28 +1,319 @@ -import { getSource } from "./controllers/gitComm.ts"; import PathGenerator from "./controllers/path.ts"; -import translate from "./controllers/recurTranslate.ts"; +import translate, { getFileErrors } from "./controllers/recurTranslate.ts"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; +import OpenAI from "openai"; -export default async function fancyName(path: string) { +// Get the directory name of the current module +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Global variables for tracking translation state +// These need to be accessible by signal handlers +let xmlFiles: string[] = []; +let filesToTranslate: string[] = []; +let successCount = 0; +let failureCount = 0; +let processedCount = 0; +let failures: { file: string; error: any }[] = []; + +const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; + +// Function to save summary log - can be called from signal handlers +async function saveSummaryLog() { + try { + const ai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.AI_BASEURL + }); + + // list and delete all assistants + const assistants = await ai.beta.assistants.list({ limit: 100 }); + const failedDel: string[] = []; + await Promise.all( + assistants.data.map(async assistant => { + try { + await ai.beta.assistants.del(assistant.id); + } catch (error) { + failedDel.push(assistant.id); + } + }) + ).then(() => console.log("successfully removed all assistants")); + + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + let summaryLog = ` +Translation Summary (${timestamp}) +================================ +Total files scanned: ${xmlFiles.length} +Files needing translation: ${filesToTranslate.length} +Successfully translated: ${successCount} +Failed translations: ${failureCount} +Success rate: ${filesToTranslate.length > 0 ? ((successCount / filesToTranslate.length) * 100).toFixed(2) : 0}% +`; + + if (failedDel.length > 0) { + summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; + failedDel.forEach( + (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) + ); + } + + // Add failed translations to the log + if (failures.length > 0) { + summaryLog += `\nFailed Translations (High-level errors):\n`; + failures.forEach((failure, index) => { + summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; + }); + } + + // Add detailed errors captured during translation process + const fileErrors = getFileErrors(); + if (Object.keys(fileErrors).length > 0) { + failureCount = Object.keys(fileErrors).length; + summaryLog += `\nDetailed Translation Errors:\n`; + summaryLog += `============================\n`; + + for (const [filePath, errors] of Object.entries(fileErrors)) { + summaryLog += `\nFile: ${filePath}\n`; + errors.forEach((error, index) => { + summaryLog += ` ${index + 1}. ${error.error}\n`; + if (error.error) { + // Format the error object/message for better readability + const errorStr = + typeof error.error === "object" + ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors + : String(error.error); + summaryLog += ` Details: ${errorStr}\n`; + } + }); + } + } + + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const logPath = path.join(logDir, `translation-summary-${timestamp}.log`); + fs.writeFileSync(logPath, summaryLog); + console.log( + `Summary log saved to logs/translation-summary-${timestamp}.log` + ); + } catch (logError) { + console.error("Failed to save log file:", logError); + } +} + +// Register handlers for various termination signals +async function setupCleanupHandlers() { + // Handle normal exit + process.on("exit", () => { + console.log("Process is exiting, saving summary..."); + // Only synchronous operations work in 'exit' handlers + // We can't await here, as exit handlers must be synchronous + try { + // Use synchronous operations for final cleanup if needed + // Note: This won't actually call the async parts of saveSummaryLog properly + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summaryContent = `Translation interrupted during exit at ${timestamp}`; + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + fs.writeFileSync( + path.join(logDir, `emergency-log-${timestamp}.log`), + summaryContent + ); + } catch (error) { + console.error("Failed to save emergency log during exit:", error); + } + }); + + // Handle Ctrl+C + process.on("SIGINT", async () => { + console.log("\nReceived SIGINT (Ctrl+C). Saving summary before exit..."); + await saveSummaryLog(); + process.exit(1); + }); + + // Handle SIGTERM (kill command) + process.on("SIGTERM", async () => { + console.log("\nReceived SIGTERM. Saving summary before exit..."); + await saveSummaryLog(); + process.exit(1); + }); + + // Handle uncaught exceptions + process.on("uncaughtException", async error => { + console.error("\nUncaught exception:", error); + console.log("Saving summary before exit..."); + await saveSummaryLog(); + process.exit(1); + }); +} + +// Function to recursively find all XML files in a directory +async function findAllXmlFiles(directory: string): Promise { + const files = await fs.promises.readdir(directory); + const xmlFiles: string[] = []; + + for (const file of files) { + const fullPath = path.join(directory, file); + const stat = await fs.promises.stat(fullPath); + + if (stat.isDirectory()) { + // Recursively search subdirectories + const subDirFiles = await findAllXmlFiles(fullPath); + xmlFiles.push(...subDirFiles); + } else if (path.extname(file).toLowerCase() === ".xml") { + // Add XML files to the list + xmlFiles.push(fullPath); + } + } + + return xmlFiles; +} + +// Function to check if a file needs translation +async function needsTranslation(enFilePath: string): Promise { + // Generate the corresponding cn file path + const cnFilePath = enFilePath.replace( + path.sep + "en" + path.sep, + path.sep + "cn" + path.sep + ); + + try { + // Check if CN file exists + const cnStats = await fs.promises.stat(cnFilePath); + if (!cnStats.isFile()) { + return true; // CN path exists but is not a file (unusual case) + } + + // Compare modification times + const enStats = await fs.promises.stat(enFilePath); + return enStats.mtime > cnStats.mtime; // Return true if EN file is newer + } catch (error) { + // If we get an error, it's likely because the CN file doesn't exist + return true; // Need to translate since CN file doesn't exist + } +} +export default async function fancyName(path: string) { const fullPath = PathGenerator(path); await translate("Chinese", fullPath); } (async () => { + await setupCleanupHandlers(); + try { - await Promise.all([ - // fancyName("2"), - // fancyName("1.1"), - fancyName("1.1.2"), - // fancyName("1.1.3"), - // fancyName("1.1.4"), - // fancyName("1.1.5"), - // fancyName("1.1.6"), - // fancyName("1.1.7"), - // fancyName("1.1.8"), - // translate("Chinese", "1"), - ]); + // Get the absolute path to the xml/en directory using proper path resolution + const enDirPath = path.resolve(__dirname, "../xml/en"); + + console.log(`Scanning directory: ${enDirPath}`); + + // Find all XML files + xmlFiles = await findAllXmlFiles(enDirPath); + + console.log(`Found ${xmlFiles.length} XML files to check for translation`); + + // Filter files that need translation + filesToTranslate = []; + for (const file of xmlFiles) { + if (await needsTranslation(file)) { + filesToTranslate.push(file as never); + } + } + + console.log(`${filesToTranslate.length} files need translation`); + + if (filesToTranslate.length === 0) { + console.log("No files need translation. Exiting."); + return; + } + + // Process files in batches to avoid overwhelming the system + const batchSize: number = max_trans_num; + + // Track all failed translations with their errors + failures = []; + + for (let i = 0; i < filesToTranslate.length; i += batchSize) { + const batch = filesToTranslate.slice(i, i + batchSize); + console.log( + `Starting batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` + ); + + // Process each file in the batch, but handle errors individually + const results = await Promise.allSettled( + batch.map(async file => { + try { + console.log(`Translating file: ${file}`); + await translate("Chinese", file); + return { file, success: true }; + } catch (error) { + // Return failure with error but don't log yet + return { file, success: false, error }; + } + }) + ); + + // Count successes and failures + for (const result of results) { + processedCount++; + + if (result.status === "fulfilled") { + if (result.value.success) { + successCount++; + } else { + failureCount++; + failures.push({ + file: result.value.file, + error: result.value.error + }); + console.error( + `Translation failed for ${result.value.file}: ${result.value.error}` + ); + } + } else { + // This is for Promise rejections (should be rare with our error handling) + failureCount++; + failures.push({ + file: "Unknown file in batch", + error: result.reason + }); + console.error(`Promise rejected for file: ${result.reason}`); + } + } + + console.log( + `Completed batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` + ); + console.log( + `Progress: ${successCount} successful, ${failureCount} failed, ${processedCount} processed out of ${filesToTranslate.length} files` + ); + } + + console.log("All translations completed!"); + console.log( + `Final results: ${successCount} successful, ${failureCount} failed out of ${filesToTranslate.length} files` + ); + + // If there are failures, report them all at the end + if (failures.length > 0) { + console.log("\n===== FAILED TRANSLATIONS ====="); + failures.forEach((failure, index) => { + console.log(`${index + 1}. Failed file: ${failure.file}`); + console.log(` Error: ${failure.error}`); + }); + console.log("==============================\n"); + } + + // Save a detailed summary to a log file + await saveSummaryLog(); } catch (e) { - console.error(e); + console.error("Error during translation process:", e); } })(); diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index b98733453..634e8933a 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -1,5 +1,11 @@ import fs from "fs"; import OpenAI from "openai/index.mjs"; +import path, { dirname } from "path"; +import { fileURLToPath } from "url"; + +// Get the directory name of the current module +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); export default async function createAssistant(language: string, ai: OpenAI) { const assistant = await ai.beta.assistants.create({ @@ -17,7 +23,7 @@ export default async function createAssistant(language: string, ai: OpenAI) { }); const fileStreams = [ - "/home/yihao/projects/XML_translater/metadatas/try.txt" + path.resolve(__dirname, "../../dictionary/cn.txt") ].map(path => fs.createReadStream(path)); // Create a vector store including our two files. From 48fe8dfbdf230c3d28625f7a1592408cc6709138 Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 14:38:31 +0800 Subject: [PATCH 21/55] modified index.js to properly handle updated repository structure. Added try catch when parsing xml to json to report failed parsing possibly attributed to unsound xml structure. --- javascript/index.js | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/javascript/index.js b/javascript/index.js index 485753cdb..cb49f2f87 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -8,6 +8,7 @@ import { DOMParser as dom } from "xmldom"; const readdir = util.promisify(fs.readdir); const open = util.promisify(fs.open); const readFile = util.promisify(fs.readFile); +const errors = []; // latex (pdf version) import { @@ -43,6 +44,8 @@ import { setupSnippetsJson } from "./processingFunctions/processSnippetJson"; import { createTocJson } from "./generateTocJson"; import { setupReferencesJson } from "./processingFunctions/processReferenceJson"; import { SourceTextModule } from "vm"; +import { threadId } from "worker_threads"; +import { exitCode } from "process"; export let parseType; let version; @@ -163,7 +166,7 @@ async function translateXml(filepath, filename, option) { } if (parseType == "json") { - const relativeFilePath = path.join( + try {const relativeFilePath = path.join( filepath, filename.replace(/\.xml$/, "") + ".html" ); @@ -188,6 +191,8 @@ async function translateXml(filepath, filename, option) { stream.write(JSON.stringify(jsonObj)); stream.end(); }); + } } catch (error) { + errors.push(filepath + " " + error); } return; } @@ -215,9 +220,11 @@ async function recursiveTranslateXml(filepath, option, lang = "en") { } const fullPath = path.join(inputDir, filepath); + console.log(fullPath); files = await readdir(fullPath); const promises = []; files.forEach(file => { + console.log(file); if (file.match(/\.xml$/)) { // console.log(file + " being processed"); if ( @@ -364,14 +371,16 @@ async function main() { recursiveTranslateXml("", "parseXml"); } else if (parseType == "json") { const languages = await getDirectories(path.join(__dirname, "../xml")); - console.dir(languages) - + console.dir(languages); - languages.forEach(async lang => { + for (const lang of languages) { outputDir = path.join(__dirname, "../json", lang); + allFilepath = []; + tableOfContent = {}; + createMain(); - console.log("\ngenerate table of content\n"); + console.log(`\ngenerate table of content for ${lang}\n`); await recursiveTranslateXml("", "generateTOC", lang); allFilepath = sortTOC(allFilepath); createTocJson(outputDir); @@ -379,12 +388,30 @@ async function main() { console.log("setup snippets and references\n"); await recursiveXmlToHtmlInOrder("setupSnippet"); console.log("setup snippets and references done\n"); - await recursiveXmlToHtmlInOrder("parseXml"); writeRewritedSearchData(); // this is meant to be temp; also, will remove the original "generateSearchData" after the updation at the frontend is completed. //testIndexSearch(); - }); + } + } + try { + let summaryLog = "Parsing failed for: "; + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + for (const err of errors) { + summaryLog += "\n" + err; + } + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const logPath = path.join(logDir, `json-summary-${timestamp}.log`); + fs.writeFileSync(logPath, summaryLog); + console.log( + `Summary log saved to logs/translation-summary-${timestamp}.log` + ); + } catch (logError) { + console.error("Failed to save log file:", logError); } } From 8e0e318e3edee68d202895c90c17211b81e43e93 Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 14:39:44 +0800 Subject: [PATCH 22/55] added troubleshoot toggle that skips calling openai api --- i18n/controllers/recurTranslate.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 073771784..3bf805d75 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -30,6 +30,10 @@ const ignoredTags = [ const MAXLEN = Number(process.env.MAX_LEN) || 3000; +// change to true to avoid calling openai api, useful for troubleshooting +// chunking logic +const troubleshoot = false; + // Centralized logging to prevent duplicate messages const errorMessages = new Set(); // Track errors by file for summary reporting @@ -404,7 +408,7 @@ async function recursivelyTranslate( } async function translateChunk(chunk: string): Promise { - return chunk; + if (troubleshoot) return chunk; if (chunk.trim() === "" || chunk.trim() === "," || chunk.trim() === ".") { return chunk; } From f0c039fea5cf1ded43d3999526d56b3a3a4cda4a Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 14:43:06 +0800 Subject: [PATCH 23/55] Add references, index preface, and making section for SICP JS project - Created a new XML file for references (97references97.xml) containing a comprehensive list of references used in the SICP JS project. - Added a new XML file for the index preface (98indexpreface98.xml) to provide context and formatting for the index section. - Introduced a new XML file for the making section (99making99.xml) detailing the background, interactive features, and development history of the SICP JS project. - Updated subsection2.xml to close the previously open SUBSECTION tag and include a comment for clarity. --- dictionary/cn.txt | 3214 ++++++++++++++++ i18n/.env.example | 8 + xml/cn/chapter1/chapter1.xml | 407 ++ xml/cn/chapter1/section1/section1.xml | 220 +- xml/cn/chapter1/section1/subsection1.xml | 500 +++ xml/cn/chapter1/section1/subsection2.xml | 994 +---- xml/cn/chapter1/section1/subsection3.xml | 709 ++-- xml/cn/chapter1/section1/subsection4.xml | 2505 ++----------- xml/cn/chapter1/section1/subsection5.xml | 3420 ++--------------- xml/cn/chapter1/section1/subsection6.xml | 1094 +++--- xml/cn/chapter1/section1/subsection7.xml | 468 +-- xml/cn/chapter1/section1/subsection8.xml | 1431 +++++--- xml/cn/chapter1/section2/section2.xml | 110 + xml/cn/chapter1/section2/subsection1.xml | 820 +++++ xml/cn/chapter1/section2/subsection2.xml | 777 ++++ xml/cn/chapter1/section2/subsection3.xml | 402 ++ xml/cn/chapter1/section2/subsection4.xml | 662 ++++ xml/cn/chapter1/section2/subsection5.xml | 306 ++ xml/cn/chapter1/section2/subsection6.xml | 1166 ++++++ xml/cn/chapter1/section3/section3.xml | 139 + xml/cn/chapter1/section3/subsection1.xml | 1112 ++++++ xml/cn/chapter1/section3/subsection2.xml | 964 +++++ xml/cn/chapter1/section3/subsection3.xml | 988 +++++ xml/cn/chapter1/section3/subsection4.xml | 1319 +++++++ xml/cn/chapter2/chapter2.xml | 371 +- xml/cn/chapter2/section1/section1.xml | 105 + xml/cn/chapter2/section1/subsection1.xml | 907 +++++ xml/cn/chapter2/section1/subsection2.xml | 528 +++ xml/cn/chapter2/section1/subsection3.xml | 269 ++ xml/cn/chapter2/section1/subsection4.xml | 573 +++ xml/cn/chapter2/section2/section2.xml | 206 ++ xml/cn/chapter2/section2/subsection1.xml | 1960 ++++++++++ xml/cn/chapter2/section2/subsection2.xml | 1504 ++++++++ xml/cn/chapter2/section2/subsection3.xml | 2599 +++++++++++++ xml/cn/chapter2/section2/subsection4.xml | 2541 +++++++++++++ xml/cn/chapter2/section3/section3.xml | 31 + xml/cn/chapter2/section3/subsection1.xml | 746 ++++ xml/cn/chapter2/section3/subsection2.xml | 1217 ++++++ xml/cn/chapter2/section3/subsection3.xml | 1687 +++++++++ xml/cn/chapter2/section3/subsection4.xml | 1243 +++++++ xml/cn/chapter2/section4/section4.xml | 177 + xml/cn/chapter2/section4/subsection1.xml | 362 ++ xml/cn/chapter2/section4/subsection2.xml | 536 +++ xml/cn/chapter2/section4/subsection3.xml | 1714 +++++++++ xml/cn/chapter2/section5/section5.xml | 103 + xml/cn/chapter2/section5/subsection1.xml | 1012 +++++ xml/cn/chapter2/section5/subsection2.xml | 899 +++++ xml/cn/chapter2/section5/subsection3.xml | 1914 ++++++++++ xml/cn/chapter3/chapter3.xml | 101 + xml/cn/chapter3/section1/section1.xml | 42 + xml/cn/chapter3/section1/subsection1.xml | 1558 ++++++++ xml/cn/chapter3/section1/subsection2.xml | 678 ++++ xml/cn/chapter3/section1/subsection3.xml | 1015 +++++ xml/cn/chapter3/section2/section2.xml | 195 + xml/cn/chapter3/section2/subsection1.xml | 568 +++ xml/cn/chapter3/section2/subsection2.xml | 103 + xml/cn/chapter3/section2/subsection3.xml | 655 ++++ xml/cn/chapter3/section2/subsection4.xml | 619 ++++ xml/cn/chapter3/section2/subsection5.xml | 35 + xml/cn/chapter3/section3/section3.xml | 43 + xml/cn/chapter3/section3/subsection1.xml | 1917 ++++++++++ xml/cn/chapter3/section3/subsection2.xml | 961 +++++ xml/cn/chapter3/section3/subsection3.xml | 1000 +++++ xml/cn/chapter3/section3/subsection4.xml | 1960 ++++++++++ xml/cn/chapter3/section4/section4.xml | 75 + xml/cn/chapter3/section4/subsection1.xml | 458 +++ xml/cn/chapter3/section4/subsection2.xml | 1999 ++++++++++ xml/cn/chapter3/section5/section5.xml | 96 + xml/cn/chapter3/section5/subsection1.xml | 1530 ++++++++ xml/cn/chapter3/section5/subsection2.xml | 1499 ++++++++ xml/cn/chapter3/section5/subsection3.xml | 1545 ++++++++ xml/cn/chapter3/section5/subsection4.xml | 881 +++++ xml/cn/chapter3/section5/subsection5.xml | 601 +++ xml/cn/chapter4/chapter4.xml | 330 ++ xml/cn/chapter4/section1/section1.xml | 414 +++ xml/cn/chapter4/section1/subsection1.xml | 1467 ++++++++ xml/cn/chapter4/section1/subsection2.xml | 2735 ++++++++++++++ xml/cn/chapter4/section1/subsection3.xml | 1110 ++++++ xml/cn/chapter4/section1/subsection4.xml | 906 +++++ xml/cn/chapter4/section1/subsection5.xml | 487 +++ xml/cn/chapter4/section1/subsection6.xml | 885 +++++ xml/cn/chapter4/section1/subsection7.xml | 1111 ++++++ xml/cn/chapter4/section2/section2.xml | 85 + xml/cn/chapter4/section2/subsection1.xml | 399 ++ xml/cn/chapter4/section2/subsection2.xml | 1687 +++++++++ xml/cn/chapter4/section2/subsection3.xml | 518 +++ xml/cn/chapter4/section3/section3.xml | 277 ++ xml/cn/chapter4/section3/subsection1.xml | 765 ++++ xml/cn/chapter4/section3/subsection2.xml | 1091 ++++++ xml/cn/chapter4/section3/subsection3.xml | 2557 +++++++++++++ xml/cn/chapter4/section4/section4.xml | 418 +++ xml/cn/chapter4/section4/subsection1.xml | 2345 ++++++++++++ xml/cn/chapter4/section4/subsection2.xml | 1442 ++++++++ xml/cn/chapter4/section4/subsection3.xml | 753 ++++ xml/cn/chapter4/section4/subsection4.xml | 4290 ++++++++++++++++++++++ xml/cn/chapter5/chapter5.xml | 236 ++ xml/cn/chapter5/section1/section1.xml | 270 ++ xml/cn/chapter5/section1/subsection1.xml | 619 ++++ xml/cn/chapter5/section1/subsection2.xml | 216 ++ xml/cn/chapter5/section1/subsection3.xml | 357 ++ xml/cn/chapter5/section1/subsection4.xml | 668 ++++ xml/cn/chapter5/section1/subsection5.xml | 173 + xml/cn/chapter5/section2/section2.xml | 292 ++ xml/cn/chapter5/section2/subsection1.xml | 793 ++++ xml/cn/chapter5/section2/subsection2.xml | 558 +++ xml/cn/chapter5/section2/subsection3.xml | 1452 ++++++++ xml/cn/chapter5/section2/subsection4.xml | 351 ++ xml/cn/chapter5/section3/section3.xml | 89 + xml/cn/chapter5/section3/subsection1.xml | 886 +++++ xml/cn/chapter5/section3/subsection2.xml | 1909 ++++++++++ xml/cn/chapter5/section4/section4.xml | 265 ++ xml/cn/chapter5/section4/subsection1.xml | 824 +++++ xml/cn/chapter5/section4/subsection2.xml | 958 +++++ xml/cn/chapter5/section4/subsection3.xml | 363 ++ xml/cn/chapter5/section4/subsection4.xml | 1356 +++++++ xml/cn/chapter5/section5/section5.xml | 621 ++++ xml/cn/chapter5/section5/subsection1.xml | 858 +++++ xml/cn/chapter5/section5/subsection2.xml | 1291 +++++++ xml/cn/chapter5/section5/subsection3.xml | 1460 ++++++++ xml/cn/chapter5/section5/subsection4.xml | 476 +++ xml/cn/chapter5/section5/subsection5.xml | 1453 ++++++++ xml/cn/chapter5/section5/subsection6.xml | 880 +++++ xml/cn/chapter5/section5/subsection7.xml | 1624 ++++++++ xml/cn/others/02foreword02.xml | 210 ++ xml/cn/others/02foreword84.xml | 198 + xml/cn/others/03prefaces03.xml | 132 + xml/cn/others/03prefaces96.xml | 218 ++ xml/cn/others/04acknowledgements04.xml | 220 ++ xml/cn/others/06see06.xml | 157 + xml/cn/others/97references97.xml | 497 +++ xml/cn/others/98indexpreface98.xml | 23 + xml/cn/others/99making99.xml | 98 + xml/en/chapter2/section3/subsection2.xml | 2 +- 133 files changed, 108104 insertions(+), 8094 deletions(-) create mode 100644 dictionary/cn.txt create mode 100644 i18n/.env.example create mode 100644 xml/cn/chapter1/chapter1.xml create mode 100644 xml/cn/chapter1/section1/subsection1.xml create mode 100644 xml/cn/chapter1/section2/section2.xml create mode 100644 xml/cn/chapter1/section2/subsection1.xml create mode 100644 xml/cn/chapter1/section2/subsection2.xml create mode 100644 xml/cn/chapter1/section2/subsection3.xml create mode 100644 xml/cn/chapter1/section2/subsection4.xml create mode 100644 xml/cn/chapter1/section2/subsection5.xml create mode 100644 xml/cn/chapter1/section2/subsection6.xml create mode 100644 xml/cn/chapter1/section3/section3.xml create mode 100644 xml/cn/chapter1/section3/subsection1.xml create mode 100644 xml/cn/chapter1/section3/subsection2.xml create mode 100644 xml/cn/chapter1/section3/subsection3.xml create mode 100644 xml/cn/chapter1/section3/subsection4.xml create mode 100644 xml/cn/chapter2/section1/section1.xml create mode 100644 xml/cn/chapter2/section1/subsection1.xml create mode 100644 xml/cn/chapter2/section1/subsection2.xml create mode 100644 xml/cn/chapter2/section1/subsection3.xml create mode 100644 xml/cn/chapter2/section1/subsection4.xml create mode 100644 xml/cn/chapter2/section2/section2.xml create mode 100644 xml/cn/chapter2/section2/subsection1.xml create mode 100644 xml/cn/chapter2/section2/subsection2.xml create mode 100644 xml/cn/chapter2/section2/subsection3.xml create mode 100644 xml/cn/chapter2/section2/subsection4.xml create mode 100644 xml/cn/chapter2/section3/section3.xml create mode 100644 xml/cn/chapter2/section3/subsection1.xml create mode 100644 xml/cn/chapter2/section3/subsection2.xml create mode 100644 xml/cn/chapter2/section3/subsection3.xml create mode 100644 xml/cn/chapter2/section3/subsection4.xml create mode 100644 xml/cn/chapter2/section4/section4.xml create mode 100644 xml/cn/chapter2/section4/subsection1.xml create mode 100644 xml/cn/chapter2/section4/subsection2.xml create mode 100644 xml/cn/chapter2/section4/subsection3.xml create mode 100644 xml/cn/chapter2/section5/section5.xml create mode 100644 xml/cn/chapter2/section5/subsection1.xml create mode 100644 xml/cn/chapter2/section5/subsection2.xml create mode 100644 xml/cn/chapter2/section5/subsection3.xml create mode 100644 xml/cn/chapter3/chapter3.xml create mode 100644 xml/cn/chapter3/section1/section1.xml create mode 100644 xml/cn/chapter3/section1/subsection1.xml create mode 100644 xml/cn/chapter3/section1/subsection2.xml create mode 100644 xml/cn/chapter3/section1/subsection3.xml create mode 100644 xml/cn/chapter3/section2/section2.xml create mode 100644 xml/cn/chapter3/section2/subsection1.xml create mode 100644 xml/cn/chapter3/section2/subsection2.xml create mode 100644 xml/cn/chapter3/section2/subsection3.xml create mode 100644 xml/cn/chapter3/section2/subsection4.xml create mode 100644 xml/cn/chapter3/section2/subsection5.xml create mode 100644 xml/cn/chapter3/section3/section3.xml create mode 100644 xml/cn/chapter3/section3/subsection1.xml create mode 100644 xml/cn/chapter3/section3/subsection2.xml create mode 100644 xml/cn/chapter3/section3/subsection3.xml create mode 100644 xml/cn/chapter3/section3/subsection4.xml create mode 100644 xml/cn/chapter3/section4/section4.xml create mode 100644 xml/cn/chapter3/section4/subsection1.xml create mode 100644 xml/cn/chapter3/section4/subsection2.xml create mode 100644 xml/cn/chapter3/section5/section5.xml create mode 100644 xml/cn/chapter3/section5/subsection1.xml create mode 100644 xml/cn/chapter3/section5/subsection2.xml create mode 100644 xml/cn/chapter3/section5/subsection3.xml create mode 100644 xml/cn/chapter3/section5/subsection4.xml create mode 100644 xml/cn/chapter3/section5/subsection5.xml create mode 100644 xml/cn/chapter4/chapter4.xml create mode 100644 xml/cn/chapter4/section1/section1.xml create mode 100644 xml/cn/chapter4/section1/subsection1.xml create mode 100644 xml/cn/chapter4/section1/subsection2.xml create mode 100644 xml/cn/chapter4/section1/subsection3.xml create mode 100644 xml/cn/chapter4/section1/subsection4.xml create mode 100644 xml/cn/chapter4/section1/subsection5.xml create mode 100644 xml/cn/chapter4/section1/subsection6.xml create mode 100644 xml/cn/chapter4/section1/subsection7.xml create mode 100644 xml/cn/chapter4/section2/section2.xml create mode 100644 xml/cn/chapter4/section2/subsection1.xml create mode 100644 xml/cn/chapter4/section2/subsection2.xml create mode 100644 xml/cn/chapter4/section2/subsection3.xml create mode 100644 xml/cn/chapter4/section3/section3.xml create mode 100644 xml/cn/chapter4/section3/subsection1.xml create mode 100644 xml/cn/chapter4/section3/subsection2.xml create mode 100644 xml/cn/chapter4/section3/subsection3.xml create mode 100644 xml/cn/chapter4/section4/section4.xml create mode 100644 xml/cn/chapter4/section4/subsection1.xml create mode 100644 xml/cn/chapter4/section4/subsection2.xml create mode 100644 xml/cn/chapter4/section4/subsection3.xml create mode 100644 xml/cn/chapter4/section4/subsection4.xml create mode 100644 xml/cn/chapter5/chapter5.xml create mode 100644 xml/cn/chapter5/section1/section1.xml create mode 100644 xml/cn/chapter5/section1/subsection1.xml create mode 100644 xml/cn/chapter5/section1/subsection2.xml create mode 100644 xml/cn/chapter5/section1/subsection3.xml create mode 100644 xml/cn/chapter5/section1/subsection4.xml create mode 100644 xml/cn/chapter5/section1/subsection5.xml create mode 100644 xml/cn/chapter5/section2/section2.xml create mode 100644 xml/cn/chapter5/section2/subsection1.xml create mode 100644 xml/cn/chapter5/section2/subsection2.xml create mode 100644 xml/cn/chapter5/section2/subsection3.xml create mode 100644 xml/cn/chapter5/section2/subsection4.xml create mode 100644 xml/cn/chapter5/section3/section3.xml create mode 100644 xml/cn/chapter5/section3/subsection1.xml create mode 100644 xml/cn/chapter5/section3/subsection2.xml create mode 100644 xml/cn/chapter5/section4/section4.xml create mode 100644 xml/cn/chapter5/section4/subsection1.xml create mode 100644 xml/cn/chapter5/section4/subsection2.xml create mode 100644 xml/cn/chapter5/section4/subsection3.xml create mode 100644 xml/cn/chapter5/section4/subsection4.xml create mode 100644 xml/cn/chapter5/section5/section5.xml create mode 100644 xml/cn/chapter5/section5/subsection1.xml create mode 100644 xml/cn/chapter5/section5/subsection2.xml create mode 100644 xml/cn/chapter5/section5/subsection3.xml create mode 100644 xml/cn/chapter5/section5/subsection4.xml create mode 100644 xml/cn/chapter5/section5/subsection5.xml create mode 100644 xml/cn/chapter5/section5/subsection6.xml create mode 100644 xml/cn/chapter5/section5/subsection7.xml create mode 100644 xml/cn/others/02foreword02.xml create mode 100644 xml/cn/others/02foreword84.xml create mode 100644 xml/cn/others/03prefaces03.xml create mode 100644 xml/cn/others/03prefaces96.xml create mode 100644 xml/cn/others/04acknowledgements04.xml create mode 100644 xml/cn/others/06see06.xml create mode 100644 xml/cn/others/97references97.xml create mode 100644 xml/cn/others/98indexpreface98.xml create mode 100644 xml/cn/others/99making99.xml diff --git a/dictionary/cn.txt b/dictionary/cn.txt new file mode 100644 index 000000000..da5cf49bd --- /dev/null +++ b/dictionary/cn.txt @@ -0,0 +1,3214 @@ +" (double quote): 双引号 +' (single quote): 单引号 +` (back quote): 反引号 ++: 加号 ++ (as numeric addition operator): + (作为数值加法运算符) ++ (as string concatenation operator): + (作为字符串连接运算符) +-: 减号 +- (as numeric negation operator): - (作为数值取反运算符) +- (as numeric subtraction operator): - (作为数值减法运算符) +* (multiplication operator): * (乘法运算符) +/ (division operator): / (除法运算符) +% (remainder operator): % (求余运算符) +&& (logical conjunction): && (逻辑与) +&& (as derived component): && (作为派生组件) +&& (evaluation of): && (求值) +&& (implementing in metacircular evaluator): && (在元循环求值器中实现) +&& (parsing of): && (解析) +&& (why a syntactic form): && (为何是语法形式) +|| (logical disjunction): || (逻辑或) +|| (as derived component): || (作为派生组件) +|| (evaluation of): || (求值) +|| (implementing in metacircular evaluator): || (在元循环求值器中实现) +|| (parsing of): || (解析) +|| (why a syntactic form): || (为何是语法形式) +? :: 条件运算符 (三元运算符) +! (logical negation operator): ! (逻辑非运算符) +!==: 不全等运算符 +!== (as numeric comparison operator): !== (作为数值比较运算符) +!== (as string comparison operator): !== (作为字符串比较运算符) +=: 赋值运算符 +===: 全等运算符 +=== (as equality of pointers): === (作为指针相等性判断) +=== (as general comparison operator): === (作为通用比较运算符) +=== (as numeric equality operator): === (作为数值相等性运算符) +=== (as string comparison operator): === (作为字符串比较运算符) +> (numeric comparison operator): > (数值比较运算符) +>= (numeric comparison operator): >= (数值比较运算符) +< (numeric comparison operator): < (数值比较运算符) +<= (numeric comparison operator): <= (数值比较运算符) +=>: 箭头函数定义符 +-> notation for mathematical function: -> (数学函数表示法) +[ , ] (box notation for pairs): [ , ] (序对的方框表示法) +;: 分号 +... (rest parameter and spread syntax): ... (剩余参数与展开语法) +$$ (pattern variables starting with): $$ (以此开头的模式变量) +// (for comments in programs): // (用于程序注释) +theta(f(n)) (theta of f(n)): theta(f(n)) (Theta 记号) +lambda calculus (lambda calculus): lambda 演算 +pi: 圆周率 (pi) +Sigma (sigma) notation: Sigma (求和) 记号 + +A + +abs: abs (绝对值函数) +absolute value: 绝对值 +abstract data: 抽象数据 +abstraction: 抽象 +abstraction (common pattern and): 抽象 (与共同模式) +abstraction (functional): 抽象 (函数式) +abstraction (metalinguistic): 抽象 (元语言) +abstraction (in register-machine design): 抽象 (在寄存器机设计中) +abstraction (of search in nondeterministic programming): 抽象 (非确定性编程中搜索的抽象) +abstraction barriers: 抽象屏障 +abstraction barriers (in complex-number system): 抽象屏障 (在复数系统中) +abstraction barriers (in generic arithmetic system): 抽象屏障 (在通用算术系统中) +abstraction barriers (in query language): 抽象屏障 (在查询语言中) +abstraction barriers (in representing JavaScript syntax): 抽象屏障 (在表示 JavaScript 语法中) +abstract models for data: 数据的抽象模型 +abstract syntax: 抽象语法 +abstract syntax (in metacircular evaluator): 抽象语法 (在元循环求值器中) +abstract syntax (in query interpreter): 抽象语法 (在查询解释器中) +accelerated_sequence: accelerated_sequence (加速序列函数) +accumulate: accumulate (累积函数) +accumulate (same as fold_right): accumulate (与 fold_right 相同) +accumulate_n: accumulate_n (多元累积函数) +accumulator: 累加器 +Ackermann's function: 阿克曼函数 +acquire a mutex: 获取互斥锁 +actions, in register machine: 动作 (在寄存器机中) +actual_value: actual_value (实际值函数) +Ada: Ada (编程语言名) +Adams, Norman I., IV: Adams, Norman I., IV (人名) +add (generic): add (通用加法) +add (generic, used for polynomial coefficients): add (通用加法, 用于多项式系数) +add_action: add_action (添加动作函数) +add_complex: add_complex (复数加法函数) +add_complex_to_javascript_num: add_complex_to_javascript_num (复数与JS数字相加函数) +addend: addend (加数选择器) +adder (primitive constraint): adder (加法器原语约束) +adder: 加法器 +adder (full): 加法器 (全加器) +adder (half): 加法器 (半加器) +adder (ripple-carry): 加法器 (行波进位) +add_interval: add_interval (区间加法函数) +additivity: 可加性 +add_lists: add_lists (列表相加函数) +add_poly: add_poly (多项式加法函数) +add_rat: add_rat (有理数加法函数) +address: 地址 +address arithmetic: 地址算术 +add_rule_or_assertion: add_rule_or_assertion (添加规则或断言函数) +add_streams: add_streams (流加法函数) +add_terms: add_terms (项加法函数) +add_to_agenda: add_to_agenda (添加到议程函数) +add_vect: add_vect (向量加法函数) +adjoin_arg: adjoin_arg (添加参数函数) +adjoining to a list with pair: 使用 pair 添加到列表 +adjoin_set: adjoin_set (添加到集合函数) +adjoin_set (binary-tree representation): adjoin_set (二叉树表示) +adjoin_set (ordered-list representation): adjoin_set (有序列表表示) +adjoin_set (unordered-list representation): adjoin_set (无序列表表示) +adjoin_set (for weighted sets): adjoin_set (用于加权集合) +adjoin_term: adjoin_term (添加项函数) +Adleman, Leonard: Adleman, Leonard (人名) +administrative assistant, importance of: 行政助理的重要性 +advance_pc: advance_pc (推进程序计数器函数) +after_delay: after_delay (延迟后执行函数) +agenda: 议程 (模拟) +A'h-mose: A'h-mose (人名) +algebra, symbolic: 符号代数 +algebraic expression: 代数表达式 +algebraic expression (differentiating): 代数表达式 (求导) +algebraic expression (representing): 代数表达式 (表示) +algebraic expression (simplifying): 代数表达式 (化简) +algebraic specification for data: 数据的代数规约 +Algol: Algol (编程语言名) +Algol (block structure): Algol (块结构) +Algol (call-by-name argument passing): Algol (传名调用参数传递) +Algol (thunks): Algol (thunk) +algorithm: 算法 +algorithm (optimal): 算法 (最优) +algorithm (probabilistic): 算法 (概率性) +aliasing: 别名 / 混淆 +Al-Karaji: Al-Karaji (人名) +Allen, John: Allen, John (人名) +all_regs (compiler): all_regs (编译器使用的寄存器列表) +alternative: 替换部分 / 备选项 +alternative (of conditional expression): 替换部分 (条件表达式的) +alternative (of conditional statement): 替换部分 (条件语句的) +always_true: always_true (总是为真函数) +amb: amb (歧义选择运算符) +amb evaluator: amb 求值器 +ambeval: ambeval (amb 求值器主函数) +analog computer: 模拟计算机 +analyze: analyze (分析函数) +analyze (metacircular): analyze (元循环求值器版本) +analyze (nondeterministic): analyze (非确定性版本) +analyze_...: analyze_... (系列分析函数) +analyze_... (metacircular): analyze_... (元循环求值器版本) +analyze_... (nondeterministic): analyze_... (非确定性版本) +analyze_amb: analyze_amb (分析 amb 表达式函数) +analyzing evaluator: 分析求值器 +analyzing evaluator (as basis for nondeterministic evaluator): 分析求值器 (作为非确定性求值器的基础) +and (query language): and (查询语言中的与) +and (query language, evaluation of): and (查询语言中与的求值) +and-gate: 与门 +and_gate: and_gate (与门函数) +an_element_of: an_element_of (集合元素选择函数) +angle: angle (角度函数/选择器) +angle (data-directed): angle (数据导向版本) +angle (polar representation): angle (极坐标表示) +angle (rectangular representation): angle (直角坐标表示) +angle (with tagged data): angle (带标签数据版本) +angle_polar: angle_polar (极坐标角度函数) +angle_rectangular: angle_rectangular (直角坐标角度函数) +an_integer_starting_from: an_integer_starting_from (从...开始的整数函数) +Appel, Andrew W.: Appel, Andrew W. (人名) +append: append (列表连接函数) +append (as accumulation): append (作为累积) +append (append_mutator vs.): append (与 append_mutator 的比较) +append (as register machine): append (作为寄存器机) +append ("what is" (rules) vs. "how to" (function)): append ("是什么"(规则) vs. "如何做"(函数)) +append_instruction_sequences: append_instruction_sequences (连接指令序列函数) +append_mutator: append_mutator (列表原地连接函数) +append_mutator (as register machine): append_mutator (作为寄存器机) +append_to_form (rules): append_to_form (规则系统中的连接形式函数) +applicative-order evaluation: 应用序求值 +applicative-order evaluation (in JavaScript): 应用序求值 (在 JavaScript 中) +applicative-order evaluation (normal order vs.): 应用序求值 (与正则序的比较) +apply (lazy): apply (惰性求值版本) +apply (metacircular): apply (元循环求值器版本) +apply (metacircular, tail recursion and): apply (元循环求值器版本, 与尾递归) +apply (primitive method): apply (JavaScript 内建方法) +apply_a_rule: apply_a_rule (应用规则函数) +apply_dispatch: apply_dispatch (应用分派函数) +apply_dispatch (modified for compiled code): apply_dispatch (为编译代码修改的版本) +apply_generic: apply_generic (应用通用操作函数) +apply_generic (with coercion): apply_generic (带强制转换) +apply_generic (with coercion by raising): apply_generic (通过提升进行强制转换) +apply_generic (with coercion of multiple arguments): apply_generic (多参数强制转换) +apply_generic (with coercion to simplify): apply_generic (通过强制转换简化) +apply_generic (with message passing): apply_generic (带消息传递) +apply_generic (with tower of types): apply_generic (带类型塔) +apply_in_underlying_javascript: apply_in_underlying_javascript (在底层 JavaScript 中应用函数) +apply_primitive_function: apply_primitive_function (应用原语函数) +apply_rules: apply_rules (应用规则函数) +arbiter: 仲裁器 +arctangent: 反正切 +arg_expressions: arg_expressions (参数表达式选择器) +argl register: argl 寄存器 (参数列表寄存器) +argument(s): 参数 +argument(s) (arbitrary number of): 参数 (任意数量) +argument(s) (delayed): 参数 (延迟求值) +argument passing: 参数传递 +Aristotle's De caelo (Buridan's commentary on): 亚里士多德的《论天》(布里丹的评注) +arithmetic: 算术 +arithmetic (address arithmetic): 算术 (地址算术) +arithmetic (generic): 算术 (通用) +arithmetic (on complex numbers): 算术 (复数) +arithmetic (on intervals): 算术 (区间) +arithmetic (on polynomials): 算术 (多项式) +arithmetic (on power series): 算术 (幂级数) +arithmetic (on rational numbers): 算术 (有理数) +arithmetic (operators for): 算术 (运算符) +array: 数组 +arrow function: 箭头函数 +articles: articles (冠词列表) +ASCII code: ASCII 码 +assemble: assemble (汇编函数) +assembler: 汇编器 +assert (query interpreter): assert (查询解释器中的断言命令) +assertion: 断言 +assertion (implicit): 断言 (隐式) +assertion_body: assertion_body (断言体选择器) +assign (in register machine): assign (寄存器机中的赋值指令) +assign (in register machine, instruction constructor): assign (赋值指令构造器) +assign (in register machine, simulating): assign (模拟赋值指令) +assign (in register machine, storing label in register): assign (将标号存入寄存器) +assignment: 赋值 +assignment (assignment expression): 赋值 (赋值表达式) +assignment (assignment operation): 赋值 (赋值操作) +assignment (benefits of): 赋值 (优点) +assignment (bugs associated with): 赋值 (相关的错误) +assignment (constant/variable declaration vs.): 赋值 (与常量/变量声明的比较) +assignment (costs of): 赋值 (代价) +assignment (equality test vs.): 赋值 (与相等性测试的比较) +assignment (evaluation of): 赋值 (求值) +assignment (parsing of): 赋值 (解析) +assignment (value of): 赋值 (值) +assignment_symbol: assignment_symbol (赋值符号选择器) +assignment_value_expression: assignment_value_expression (赋值的值表达式选择器) +assign_reg_name: assign_reg_name (赋值指令的寄存器名选择器) +assign_symbol_value: assign_symbol_value (设置符号值函数) +assign_value_exp: assign_value_exp (赋值指令的值表达式选择器) +assoc: assoc (关联列表查找函数) +associativity: 结合律 / 结合性 +associativity (of conditional expression): 结合性 (条件表达式的) +associativity (of operators): 结合性 (运算符的) +atomic operations supported in hardware: 硬件支持的原子操作 +atomic requirement for test_and_set: test_and_set 的原子性要求 +attach_tag: attach_tag (附加标签函数) +attach_tag (using JavaScript data types): attach_tag (使用 JavaScript 数据类型) +augend: augend (被加数选择器) +automagically: 自动魔法般地 +automatic search: 自动搜索 +automatic search (history of): 自动搜索 (历史) +automatic storage allocation: 自动存储分配 +average: average (平均值函数) +average_damp: average_damp (平均阻尼函数) +average damping: 平均阻尼 +averager (constraint): averager (平均值约束) + +B + +back quotes: 反引号 +backtracking: 回溯 +Backus, John: Backus, John (人名) +Baker, Henry G., Jr.: Baker, Henry G., Jr. (人名) +balanced binary tree: 平衡二叉树 +balanced mobile: 平衡的悬挂物 +bank account: 银行账户 +bank account (exchanging balances): 银行账户 (交换余额) +bank account (joint): 银行账户 (联名) +bank account (joint, modeled with streams): 银行账户 (联名, 用流建模) +bank account (joint, with concurrent access): 银行账户 (联名, 带并发访问) +bank account (password-protected): 银行账户 (密码保护) +bank account (serialized): 银行账户 (序列化) +bank account (stream model): 银行账户 (流模型) +bank account (transferring money): 银行账户 (转账) +barrier synchronization: 屏障同步 +Barth, John: Barth, John (人名) +Batali, John Dean: Batali, John Dean (人名) +below: below (下方组合函数 - 图片语言) +Bertrand's Hypothesis: 伯特兰假设 +beside: beside (旁边组合函数 - 图片语言) +Bhaskara: Bhaskara (人名) +bignum: 大数 (任意精度整数) +binary numbers, addition of: 二进制数加法 +binary operator: 二元运算符 +binary search: 二分搜索 +binary tree: 二叉树 +binary tree (balanced): 二叉树 (平衡) +binary tree (converting a list to a): 二叉树 (将列表转换为) +binary tree (converting to a list): 二叉树 (转换为列表) +binary tree (for Huffman encoding): 二叉树 (用于霍夫曼编码) +binary tree (represented with lists): 二叉树 (用列表表示) +binary tree (set represented as): 二叉树 (集合表示为) +binary tree (table structured as): 二叉树 (表结构为) +bind: 约束 / 绑定 +binding: 绑定 +binding (deep): 绑定 (深绑定) +binding_in_frame: binding_in_frame (帧内绑定查找函数) +binding_value: binding_value (绑定值选择器) +binding_variable: binding_variable (绑定变量选择器) +binomial coefficients: 二项式系数 +black box: 黑箱 +block: 块 / 代码块 +block (empty): 块 (空) +block (parsing of): 块 (解析) +block_body: block_body (块体选择器) +blocked process: 阻塞进程 +block structure: 块结构 +block structure (in environment model): 块结构 (在环境模型中) +block structure (in query language): 块结构 (在查询语言中) +body of a function: 函数体 +boolean values (true, false): 布尔值 (true, false) +Borning, Alan: Borning, Alan (人名) +Borodin, Alan: Borodin, Alan (人名) +bound name: 约束变量 / 绑定名 +box-and-pointer notation: 方框与指针表示法 +box-and-pointer notation (end-of-list marker): 方框与指针表示法 (列表结束标记) +box notation for pairs: 序对的方框表示法 +branch (in register machine): branch (寄存器机中的分支指令) +branch (in register machine, instruction constructor): branch (分支指令构造器) +branch (in register machine, simulating): branch (模拟分支指令) +branch_dest: branch_dest (分支目标选择器) +branch of a tree: 树的分支 +break (keyword): break (关键字) +breakpoint: 断点 +broken heart: 破碎的心 (垃圾回收相关) +B-tree: B 树 +bug: 错误 / Bug +bug (capturing a free name): 错误 (捕获自由变量) +bug (order of assignments): 错误 (赋值顺序) +bug (side effect with aliasing): 错误 (别名引起的副作用) +bureaucracy: 官僚机构 / 繁琐手续 +Buridan, Jean: Buridan, Jean (人名) +busy-waiting: 忙等待 + +C + +C: C 语言 +C (compiling JavaScript into): C (将 JavaScript 编译到) +C (error handling): C (错误处理) +C (JavaScript interpreter written in): C (用 C 编写的 JavaScript 解释器) +C (recursive functions in): C (递归函数) +cache-coherence protocols: 缓存一致性协议 +calculator, fixed points with: 计算器 (用计算器求不动点) +call-by-name argument passing: 传名调用参数传递 +call-by-need argument passing: 传需求调用参数传递 +call-by-need argument passing (memoization and): 传需求调用参数传递 (与记忆化) +call_each: call_each (调用每个函数) +camel case: 驼峰命名法 +canonical form, for polynomials: 规范形式 (多项式的) +capturing a free name: 捕获自由变量 +Carmichael numbers: 卡迈克尔数 +case analysis: 情况分析 +case analysis (data-directed programming vs.): 情况分析 (与数据导向编程的比较) +case analysis (general): 情况分析 (通用) +case analysis (as sequence of clauses): 情况分析 (作为子句序列) +cell, in serializer implementation: 单元 (序列化器实现中) +celsius_fahrenheit_converter: celsius_fahrenheit_converter (摄氏华氏转换器) +celsius_fahrenheit_converter (expression-oriented): celsius_fahrenheit_converter (面向表达式版本) +center: center (中心选择器 - 区间算术) +Chaitin, Gregory: Chaitin, Gregory (人名) +Chandah-sutra: Chandah-sutra (文献名) +change and sameness: 变化与同一性 +change and sameness (meaning of): 变化与同一性 (含义) +change and sameness (shared data and): 变化与同一性 (与共享数据) +changing money: 换零钱 +Chapman, David: Chapman, David (人名) +character, ASCII encoding: 字符, ASCII 编码 +Charniak, Eugene: Charniak, Eugene (人名) +Chebyshev, Pafnutii L'vovich: Chebyshev, Pafnutii L'vovich (人名) +check_an_assertion: check_an_assertion (检查断言函数) +chess, eight-queens puzzle: 国际象棋, 八皇后问题 +chip implementation of Scheme: Scheme 的芯片实现 +chronological backtracking: 时序回溯 +Church, Alonzo: Church, Alonzo (人名) +Church numerals: 丘奇数 +Church–Turing thesis: 丘奇-图灵论题 +circuit: 电路 +circuit (digital): 电路 (数字) +circuit (modeled with streams): 电路 (用流建模) +Clark, Keith L.: Clark, Keith L. (人名) +clause of a case analysis: 子句 (情况分析的) +Clinger, William: Clinger, William (人名) +closed world assumption: 封闭世界假设 +closure: 闭包 / 闭合性 +closure (in abstract algebra): 闭合性 (在抽象代数中) +closure (closure property of picture-language operations): 闭合性 (图片语言操作的闭合性质) +closure (closure property of pair): 闭合性 (pair 的闭合性质) +coal, bituminous: 烟煤 +code: 编码 / 代码 +code (ASCII): 编码 (ASCII) +code (fixed-length): 编码 (定长) +code (Huffman): 编码 (霍夫曼) +code (Morse): 编码 (摩尔斯) +code (prefix): 编码 (前缀) +code (variable-length): 编码 (变长) +code generator: 代码生成器 +code generator (arguments of): 代码生成器 (参数) +code generator (value of): 代码生成器 (值) +coeff: coeff (系数选择器) +coercion: 强制类型转换 +coercion (function): 强制类型转换 (函数) +coercion (in algebraic manipulation): 强制类型转换 (在代数操作中) +coercion (in polynomial arithmetic): 强制类型转换 (在多项式算术中) +coercion (table): 强制类型转换 (表) +Colmerauer, Alain: Colmerauer, Alain (人名) +combination: 组合式 +combination, means of: 组合的方法 +comments in programs: 程序注释 +comp register: comp 寄存器 (编译后代码寄存器) +compacting garbage collector: 紧凑式垃圾回收器 +compilation: 编译 +compile: compile (编译函数) +compile_and_go: compile_and_go (编译并执行函数) +compile_and_run: compile_and_run (编译并运行函数) +compile_application: compile_application (编译应用式函数) +compile_assignment: compile_assignment (编译赋值函数) +compile_block: compile_block (编译块函数) +compile_conditional: compile_conditional (编译条件式函数) +compiled_apply: compiled_apply (编译后的 apply 函数) +compile_declaration: compile_declaration (编译声明函数) +compiled_function_entry: compiled_function_entry (编译后函数的入口点) +compiled_function_env: compiled_function_env (编译后函数的环境) +compile_fun_appl: compile_fun_appl (编译函数应用辅助函数) +compile_function_call: compile_function_call (编译函数调用函数) +compile_lambda_body: compile_lambda_body (编译 lambda 体函数) +compile_lambda_expression: compile_lambda_expression (编译 lambda 表达式函数) +compile_linkage: compile_linkage (编译链接描述函数) +compile_literal: compile_literal (编译字面量函数) +compile_name: compile_name (编译名称函数) +compiler: 编译器 +compiler (interpreter vs.): 编译器 (与解释器的比较) +compiler (tail recursion, stack allocation, and garbage-collection): 编译器 (尾递归, 栈分配和垃圾回收) +compile_return_statement: compile_return_statement (编译返回语句函数) +compiler for JavaScript: JavaScript 编译器 +compiler for JavaScript (analyzing evaluator vs.): JavaScript 编译器 (与分析求值器的比较) +compiler for JavaScript (assignments): JavaScript 编译器 (赋值) +compiler for JavaScript (blocks): JavaScript 编译器 (块) +compiler for JavaScript (code generators): JavaScript 编译器 (代码生成器) +compiler for JavaScript (combinations): JavaScript 编译器 (组合式) +compiler for JavaScript (conditionals): JavaScript 编译器 (条件式) +compiler for JavaScript (dead code analysis): JavaScript 编译器 (死代码分析) +compiler for JavaScript (declarations): JavaScript 编译器 (声明) +compiler for JavaScript (efficiency): JavaScript 编译器 (效率) +compiler for JavaScript (example compilation): JavaScript 编译器 (编译示例) +compiler for JavaScript (explicit-control evaluator vs.): JavaScript 编译器 (与显式控制求值器的比较) +compiler for JavaScript (expression-syntax functions): JavaScript 编译器 (表达式语法函数) +compiler for JavaScript (function applications): JavaScript 编译器 (函数应用) +compiler for JavaScript (interfacing to evaluator): JavaScript 编译器 (与求值器接口) +compiler for JavaScript (label generation): JavaScript 编译器 (标号生成) +compiler for JavaScript (lambda expressions): JavaScript 编译器 (lambda 表达式) +compiler for JavaScript (lexical addressing): JavaScript 编译器 (词法寻址) +compiler for JavaScript (linkage code): JavaScript 编译器 (链接代码) +compiler for JavaScript (literals): JavaScript 编译器 (字面量) +compiler for JavaScript (machine-operation use): JavaScript 编译器 (机器操作使用) +compiler for JavaScript (monitoring performance (stack use) of compiled code): JavaScript 编译器 (监控编译代码性能(栈使用)) +compiler for JavaScript (names): JavaScript 编译器 (名称) +compiler for JavaScript (open coding of primitives): JavaScript 编译器 (原语的开放编码) +compiler for JavaScript (order of argument evaluation): JavaScript 编译器 (参数求值顺序) +compiler for JavaScript (register use): JavaScript 编译器 (寄存器使用) +compiler for JavaScript (return statements): JavaScript 编译器 (返回语句) +compiler for JavaScript (running compiled code): JavaScript 编译器 (运行编译代码) +compiler for JavaScript (scanning out internal declarations): JavaScript 编译器 (扫描出内部声明) +compiler for JavaScript (sequences of statements): JavaScript 编译器 (语句序列) +compiler for JavaScript (stack usage): JavaScript 编译器 (栈使用) +compiler for JavaScript (structure of): JavaScript 编译器 (结构) +compiler for JavaScript (tail-recursive code generated by): JavaScript 编译器 (生成的尾递归代码) +compile_sequence: compile_sequence (编译序列函数) +compile-time environment: 编译时环境 +complex package: complex 包 (复数包) +complex-number arithmetic: 复数算术 +complex-number arithmetic (interfaced to generic arithmetic system): 复数算术 (与通用算术系统接口) +complex-number arithmetic (structure of system): 复数算术 (系统结构) +complex numbers: 复数 +complex numbers (polar representation): 复数 (极坐标表示) +complex numbers (rectangular representation): 复数 (直角坐标表示) +complex numbers (rectangular vs. polar form): 复数 (直角坐标 vs. 极坐标形式) +complex numbers (represented as tagged data): 复数 (表示为带标签数据) +complex_to_complex: complex_to_complex (复数到复数转换函数) +composition of functions: 函数复合 +compound_apply: compound_apply (复合函数应用函数) +compound data, need for: 复合数据, 需求 +compound expression: 复合表达式 +compound expression (as function expression of application): 复合表达式 (作为应用的函数表达式) +compound function: 复合函数 +compound function (used like primitive function): 复合函数 (像原语函数一样使用) +compound query: 复合查询 +compound query (processing): 复合查询 (处理) +computability: 可计算性 +computational process: 计算过程 +computer science: 计算机科学 +computer science (mathematics vs.): 计算机科学 (与数学的比较) +concatenating strings: 连接字符串 +conclusion: conclusion (结论选择器 - 规则) +concrete data representation: 具体数据表示 +concurrency: 并发 +concurrency (correctness of concurrent programs): 并发 (并发程序的正确性) +concurrency (deadlock): 并发 (死锁) +concurrency (functional programming and): 并发 (与函数式编程) +concurrency (mechanisms for controlling): 并发 (控制机制) +concurrent_execute: concurrent_execute (并发执行函数) +conditional_alternative: conditional_alternative (条件替换部分选择器) +conditional_consequent: conditional_consequent (条件后续部分选择器) +conditional expression: 条件表达式 +conditional expression (as alternative of conditional expression): 条件表达式 (作为条件表达式的替换部分) +conditional expression (evaluation of): 条件表达式 (求值) +conditional expression (non-boolean value as predicate): 条件表达式 (非布尔值作为谓词) +conditional expression (normal-order evaluation of): 条件表达式 (正则序求值) +conditional expression (as operand of operator combination): 条件表达式 (作为组合式的操作数) +conditional expression (parsing of): 条件表达式 (解析) +conditional expression (precedence of): 条件表达式 (优先级) +conditional expression (right-associativity of): 条件表达式 (右结合性) +conditional expression (why a syntactic form): 条件表达式 (为何是语法形式) +conditional_predicate: conditional_predicate (条件谓词选择器) +conditional statement: 条件语句 +conditional statement (alternative statements of): 条件语句 (替换语句部分) +conditional statement (conditional instead of alternative block): 条件语句 (条件式代替替换块) +conditional statement (consequent statements of): 条件语句 (后续语句部分) +conditional statement (need for): 条件语句 (必要性) +conditional statement (one-armed (without alternative)): 条件语句 (单臂 (无替换部分)) +conditional statement (parsing of): 条件语句 (解析) +conditional statement (predicate, consequent, and alternative of): 条件语句 (谓词, 后续部分和替换部分) +congruent modulo n: 模 n 同余 +conjoin: conjoin (合取函数) +conjunction: 合取 / 逻辑与 +connect: connect (连接函数 - 约束系统) +connector(s), in constraint system: 连接器 (在约束系统中) +connector(s), in constraint system (operations on): 连接器 (操作) +connector(s), in constraint system (representing): 连接器 (表示) +Conniver: Conniver (编程语言名) +consciousness, expansion of: 意识的扩展 +consequent: 后续部分 +consequent (of clause): 后续部分 (子句的) +consequent (of conditional expression): 后续部分 (条件表达式的) +consequent (of conditional statement): 后续部分 (条件语句的) +const (keyword): const (关键字) +constant (in register machine): constant (寄存器机中的常量操作数) +constant (in register machine, simulating): constant (模拟常量操作数) +constant (in register machine, syntax of): constant (常量操作数语法) +constant (primitive constraint): constant (常量约束) +constant (in JavaScript): 常量 (在 JavaScript 中) +constant (in JavaScript, detecting assignment to): 常量 (检测对其赋值) +constant (in JavaScript, value of): 常量 (值) +constant declaration: 常量声明 +constant declaration (parsing of): 常量声明 (解析) +constant declaration (why a syntactic form): 常量声明 (为何是语法形式) +constant_exp_value: constant_exp_value (常量表达式值选择器) +constraint(s): 约束 +constraint(s) (primitive): 约束 (原语) +constraint(s) (propagation of): 约束 (传播) +constraint network: 约束网络 +construct_arglist: construct_arglist (构造参数列表函数) +constructing a list with pair: 使用 pair 构造列表 +constructor: 构造函数 / 构造器 +constructor (as abstraction barrier): 构造函数 (作为抽象屏障) +contents: contents (内容选择器) +contents (using JavaScript data types): contents (使用 JavaScript 数据类型) +continuation: 续延 / 后续 +continuation (in nondeterministic evaluator): 续延 (在非确定性求值器中) +continuation (in register-machine simulator): 续延 (在寄存器机模拟器中) +continue (keyword): continue (关键字) +continue register: continue 寄存器 (用于保存返回地址) +continue register (in explicit-control evaluator): continue 寄存器 (在显式控制求值器中) +continue register (recursion and): continue 寄存器 (与递归) +continued fraction: 连分数 +continued fraction (e as): 连分数 (e 表示为) +continued fraction (golden ratio as): 连分数 (黄金分割比表示为) +continued fraction (tangent as): 连分数 (正切表示为) +controller for register machine: 控制器 (寄存器机的) +controller for register machine (controller diagram): 控制器 (控制器图) +control structure: 控制结构 +conventional interface: 约定接口 +conventional interface (sequence as): 约定接口 (序列作为) +convert: convert (转换函数) +convert_to_query_syntax: convert_to_query_syntax (转换为查询语法函数) +Cormen, Thomas H.: Cormen, Thomas H. (人名) +corner_split: corner_split (角分裂函数 - 图片语言) +correctness of a program: 程序正确性 +cosine: 余弦 +cosine (fixed point of): 余弦 (不动点) +cosine (power series for): 余弦 (幂级数) +cosmic radiation: 宇宙辐射 +count_change: count_change (换零钱方式计数函数) +counting change: 换零钱方式计数 +count_leaves: count_leaves (计算叶节点数函数) +count_leaves (as accumulation): count_leaves (作为累积) +count_leaves (as register machine): count_leaves (作为寄存器机) +count_pairs: count_pairs (计算序对数函数) +credit-card accounts, international: 国际信用卡账户 +Cressey, David: Cressey, David (人名) +Crockford, Douglas: Crockford, Douglas (人名) +cross-type operations: 跨类型操作 +cryptography: 密码学 +cube: cube (立方函数) +cube_root: cube_root (立方根函数) +cube root: 立方根 +cube root (as fixed point): 立方根 (作为不动点) +cube root (by Newton's method): 立方根 (通过牛顿法) +current_time: current_time (当前时间函数 - 模拟) +current time, for simulation agenda: 当前时间 (用于模拟议程) +Curry, Haskell Brooks: Curry, Haskell Brooks (人名) +currying: 柯里化 +cycle in list: 列表中的环 +cycle in list (detecting): 列表中的环 (检测) + +D + +Darlington, John: Darlington, John (人名) +data: 数据 +data (abstract): 数据 (抽象) +data (abstract models for): 数据 (抽象模型) +data (algebraic specification for): 数据 (代数规约) +data (compound): 数据 (复合) +data (concrete representation of): 数据 (具体表示) +data (functional representation of): 数据 (函数表示) +data (hierarchical): 数据 (层次) +data (list-structured): 数据 (列表结构) +data (meaning of): 数据 (含义) +data (mutable): 数据 (可变) +data (numerical): 数据 (数值) +data (as program): 数据 (作为程序) +data (shared): 数据 (共享) +data (symbolic): 数据 (符号) +data (tagged): 数据 (带标签) +data abstraction: 数据抽象 +data abstraction (for queue): 数据抽象 (用于队列) +data base: 数据库 +data base (data-directed programming and): 数据库 (与数据导向编程) +data base (Gargle personnel): 数据库 (Gargle 公司人事) +data base (indexing): 数据库 (索引) +data base (Insatiable Enterprises personnel): 数据库 (Insatiable Enterprises 公司人事) +data base (logic programming and): 数据库 (与逻辑编程) +data base (as set of records): 数据库 (作为记录集合) +data-directed programming: 数据导向编程 +data-directed programming (case analysis vs.): 数据导向编程 (与情况分析比较) +data-directed programming (in metacircular evaluator): 数据导向编程 (在元循环求值器中) +data-directed programming (in query interpreter): 数据导向编程 (在查询解释器中) +data-directed recursion: 数据导向递归 +data paths for register machine: 数据通路 (寄存器机的) +data paths for register machine (data-path diagram): 数据通路 (数据通路图) +data types: 数据类型 +data types (in JavaScript): 数据类型 (在 JavaScript 中) +data types (in statically typed languages): 数据类型 (在静态类型语言中) +deadlock: 死锁 +deadlock (avoidance): 死锁 (避免) +deadlock (recovery): 死锁 (恢复) +debug: 调试 / Debug +declaration: 声明 +declaration (of constant (`const`)): 声明 (常量 `const`) +declaration (environment model of): 声明 (环境模型) +declaration (of function (`function`)): 声明 (函数 `function`) +declaration (use of name before): 声明 (在声明前使用名称) +declaration (of variable (`let`)): 声明 (变量 `let`) +declaration_symbol: declaration_symbol (声明符号选择器) +declaration_value_expression: declaration_value_expression (声明的值表达式选择器) +declarative vs. imperative knowledge: 说明性知识 vs. 命令性知识 +declarative vs. imperative knowledge (logic programming and): 说明性知识 vs. 命令性知识 (与逻辑编程) +declarative vs. imperative knowledge (nondeterministic computing and): 说明性知识 vs. 命令性知识 (与非确定性计算) +decode: decode (解码函数) +decomposition of program into parts: 程序分解为部分 +deep binding: 深绑定 +deep_reverse: deep_reverse (深层反转列表函数) +deferred operations: 推迟的操作 +definite integral: 定积分 +definite integral (estimated with Monte Carlo simulation): 定积分 (用蒙特卡洛模拟估计) +de Kleer, Johan: de Kleer, Johan (人名) +delay, in digital circuit: 延迟 (在数字电路中) +delayed argument: 延迟参数 +delayed evaluation: 延迟求值 +delayed evaluation (assignment and): 延迟求值 (与赋值) +delayed evaluation (explicit vs. automatic): 延迟求值 (显式 vs. 自动) +delayed evaluation (in lazy evaluator): 延迟求值 (在惰性求值器中) +delayed evaluation (normal-order evaluation and): 延迟求值 (与正则序求值) +delayed evaluation (printing and): 延迟求值 (与打印) +delayed evaluation (streams and): 延迟求值 (与流) +delayed expression: 延迟表达式 +delayed expression (explicit): 延迟表达式 (显式) +delayed expression (explicit vs. automatic): 延迟表达式 (显式 vs. 自动) +delayed expression (lazy evaluation and): 延迟表达式 (与惰性求值) +delayed expression (memoized): 延迟表达式 (记忆化) +delay_it: delay_it (延迟执行函数) +delete_queue: delete_queue (从队列删除函数) +denom: denom (分母选择器) +denom (axiom for): denom (公理) +denom (reducing to lowest terms): denom (化简到最简形式) +dense polynomial: 稠密多项式 +dependency-directed backtracking: 依赖导向回溯 +depends_on: depends_on (依赖关系检查函数) +deposit message for bank account: deposit (存款消息 - 银行账户) +deposit, with external serializer: deposit (带外部序列化器的存款) +depth-first search: 深度优先搜索 +deque: 双端队列 +deriv (numerical): deriv (数值微分函数) +deriv (symbolic): deriv (符号微分函数) +deriv (symbolic, data-directed): deriv (符号微分, 数据导向版本) +derivative of a function: 函数的导数 +derived component: 派生组件 +derived component (adding to explicit-control evaluator): 派生组件 (添加到显式控制求值器) +derived components in evaluator: 求值器中的派生组件 +derived components in evaluator (function declaration): 求值器中的派生组件 (函数声明) +derived components in evaluator (operator combination): 求值器中的派生组件 (运算符组合) +design, stratified: 分层设计 +differential equation: 微分方程 +differential equation (second-order): 微分方程 (二阶) +differentiation: 微分 / 求导 +differentiation (numerical): 微分 (数值) +differentiation (rules for): 微分 (规则) +differentiation (symbolic): 微分 (符号) +diffusion, simulation of: 扩散模拟 +digital-circuit simulation: 数字电路模拟 +digital-circuit simulation (agenda): 数字电路模拟 (议程) +digital-circuit simulation (agenda implementation): 数字电路模拟 (议程实现) +digital-circuit simulation (primitive function boxes): 数字电路模拟 (原语函数盒) +digital-circuit simulation (representing wires): 数字电路模拟 (表示导线) +digital-circuit simulation (sample simulation): 数字电路模拟 (模拟示例) +digital signal: 数字信号 +Dijkstra, Edsger Wybe: Dijkstra, Edsger Wybe (人名) +Dinesman, Howard P.: Dinesman, Howard P. (人名) +Diophantus's Arithmetic, Fermat's copy of: 丢番图的《算术》, 费马的版本 +Dirichlet, Peter Gustav Lejeune: Dirichlet, Peter Gustav Lejeune (人名) +dirichlet_stream: dirichlet_stream (狄利克雷流) +dirichlet_test: dirichlet_test (狄利克雷测试函数) +disjoin: disjoin (析取函数) +disjoin (without delayed expression): disjoin (无延迟表达式版本) +disjunction: 析取 / 逻辑或 +dispatching: 分派 +dispatching (comparing different styles): 分派 (比较不同风格) +dispatching (on type): 分派 (基于类型) +display (primitive function): display (显示原语函数) +display operation in register machine: display 操作 (寄存器机中) +display_stream: display_stream (显示流函数) +distinct: distinct (不同检查函数) +div (generic): div (通用除法) +div_complex: div_complex (复数除法函数) +divides: divides (整除检查函数) +div_interval: div_interval (区间除法函数) +div_interval (division by zero): div_interval (除以零) +div_poly: div_poly (多项式除法) +div_rat: div_rat (有理数除法函数) +div_series: div_series (幂级数除法) +div_terms: div_terms (项除法函数) +dog, perfectly rational behavior of: 狗的完全理性行为 +dot_product: dot_product (点积函数) +Doyle, Jon: Doyle, Jon (人名) +draw_line: draw_line (画线函数) +driver_loop: driver_loop (驱动循环) +driver_loop (for lazy evaluator): driver_loop (惰性求值器) +driver_loop (for metacircular evaluator): driver_loop (元循环求值器) +driver_loop (for nondeterministic evaluator): driver_loop (非确定性求值器) +driver loop: 驱动循环 +driver loop (in explicit-control evaluator): 驱动循环 (在显式控制求值器中) +driver loop (in lazy evaluator): 驱动循环 (在惰性求值器中) +driver loop (in metacircular evaluator): 驱动循环 (在元循环求值器中) +driver loop (in nondeterministic evaluator): 驱动循环 (在非确定性求值器中) +driver loop (in query interpreter): 驱动循环 (在查询解释器中) +duplicate parameters: 重复参数 + +E + +e: 自然常数 e +e (as continued fraction): e (表示为连分数) +e (as solution to differential equation): e (作为微分方程的解) +e^x, power series for: e^x (幂级数) +Earth, measuring circumference of: 地球周长测量 +eceval: eceval (显式控制求值器主函数) +ECMAScript: ECMAScript (标准) +ECMAScript (Math object): ECMAScript (Math 对象) +edge1_frame: edge1_frame (边1框架选择器) +edge2_frame: edge2_frame (边2框架选择器) +Edwards, Anthony William Fairbank: Edwards, Anthony William Fairbank (人名) +efficiency: 效率 +efficiency (of compilation): 效率 (编译的) +efficiency (of data-base access): 效率 (数据库访问的) +efficiency (of evaluation): 效率 (求值的) +efficiency (of query processing): 效率 (查询处理的) +efficiency (of tree-recursive process): 效率 (树形递归过程的) +Eich, Brendan: Eich, Brendan (人名) +EIEIO: EIEIO (Old MacDonald Had a Farm 歌词) +eight-queens puzzle: 八皇后问题 +electrical circuits, modeled with streams: 电路 (用流建模) +element_expressions: element_expressions (元素表达式选择器) +else (keyword): else (关键字) +embedded language, language design using: 嵌入式语言 (使用嵌入式语言设计语言) +empty_arglist: empty_arglist (空参数列表) +empty list: 空列表 +empty stream: 空流 +encapsulated name: 封装名称 +enclosing_environment: enclosing_environment (外层环境选择器) +enclosing environment: 外层环境 +encode: encode (编码函数) +end-of-list marker: 列表结束标记 +end_segment: end_segment (线段终点选择器) +end_with_linkage: end_with_linkage (结束并带链接描述符函数) +engineering vs. mathematics: 工程学 vs. 数学 +entry: entry (入口/条目 - 树节点) +enumerate_interval: enumerate_interval (枚举区间函数) +enumerate_tree: enumerate_tree (枚举树函数) +enumerator: 枚举器 +env register: env 寄存器 (环境寄存器) +environment: 环境 +environment (compile-time): 环境 (编译时) +environment (as context for evaluation): 环境 (作为求值上下文) +environment (enclosing): 环境 (外层) +environment (global): 环境 (全局) +environment (lexical scoping and): 环境 (与词法作用域) +environment (program): 环境 (程序) +environment (in query interpreter): 环境 (在查询解释器中) +environment (renaming vs.): 环境 (与重命名比较) +environment model of evaluation: 求值的环境模型 +environment model of evaluation (environment structure): 求值的环境模型 (环境结构) +environment model of evaluation (function application): 求值的环境模型 (函数应用) +environment model of evaluation (function-application example): 求值的环境模型 (函数应用示例) +environment model of evaluation (internal declarations): 求值的环境模型 (内部声明) +environment model of evaluation (local state): 求值的环境模型 (局部状态) +environment model of evaluation (message passing): 求值的环境模型 (消息传递) +environment model of evaluation (metacircular evaluator and): 求值的环境模型 (与元循环求值器) +environment model of evaluation (rules for evaluation): 求值的环境模型 (求值规则) +environment model of evaluation (tail recursion and): 求值的环境模型 (与尾递归) +equal: equal (相等性检查函数 - 列表) +equality: 相等性 +equality (in generic arithmetic system): 相等性 (在通用算术系统中) +equality (of lists): 相等性 (列表的) +equality (of numbers): 相等性 (数值的) +equality (referential transparency and): 相等性 (与引用透明性) +equality (of strings): 相等性 (字符串的) +equal_rat: equal_rat (有理数相等性检查函数) +equation, solving: 方程求解 +Eratosthenes: Eratosthenes (人名) +error (primitive function): error (错误原语函数) +error handling: 错误处理 +error handling (in compiled code): 错误处理 (在编译代码中) +error handling (in explicit-control evaluator): 错误处理 (在显式控制求值器中) +Escher, Maurits Cornelis: Escher, Maurits Cornelis (人名) +estimate_integral: estimate_integral (估算积分函数) +estimate_pi: estimate_pi (估算 pi 函数) +Euclid's Algorithm: 欧几里得算法 (辗转相除法) +Euclid's Algorithm (order of growth): 欧几里得算法 (增长阶) +Euclid's Algorithm (for polynomials): 欧几里得算法 (用于多项式) +Euclid's Elements: 欧几里得《几何原本》 +Euclid's proof of infinite number of primes: 欧几里得证明素数无限 +Euclidean ring: 欧几里得环 +Euler, Leonhard: Euler, Leonhard (人名) +Euler, Leonhard (proof of Fermat's Little Theorem): Euler, Leonhard (费马小定理证明) +Euler, Leonhard (series accelerator): Euler, Leonhard (级数加速器) +euler_transform: euler_transform (欧拉变换函数) +eval (primitive function in JavaScript): eval (JavaScript 内建求值函数) +eval_assignment: eval_assignment (求值赋值函数) +eval_block: eval_block (求值块函数) +eval_conditional (lazy): eval_conditional (惰性求值版本) +eval_conditional (metacircular): eval_conditional (元循环求值器版本) +eval_declaration: eval_declaration (求值声明函数) +eval_dispatch: eval_dispatch (求值分派函数) +eval_return_statement: eval_return_statement (求值返回语句函数) +eval_sequence: eval_sequence (求值序列函数) +evaluate (lazy): evaluate (惰性求值版本) +evaluate (metacircular): evaluate (元循环求值器版本) +evaluate (metacircular, analyzing version): evaluate (元循环分析版本) +evaluate (metacircular, data-directed): evaluate (元循环数据导向版本) +evaluate_query: evaluate_query (求值查询函数) +evaluation: 求值 +evaluation (applicative-order): 求值 (应用序) +evaluation (delayed): 求值 (延迟) +evaluation (environment model of): 求值 (环境模型) +evaluation (models of): 求值 (模型) +evaluation (normal-order): 求值 (正则序) +evaluation (of &&): 求值 (&& 的) +evaluation (of ||): 求值 (|| 的) +evaluation (of conditional expression): 求值 (条件表达式的) +evaluation (of function application): 求值 (函数应用的) +evaluation (of operator combination): 求值 (运算符组合的) +evaluation (of primitive expression): 求值 (原语表达式的) +evaluation (order of subexpression evaluation): 求值 (子表达式求值顺序) +evaluation (substitution model of): 求值 (代换模型) +evaluator: 求值器 +evaluator (as abstract machine): 求值器 (作为抽象机) +evaluator (metacircular): 求值器 (元循环) +evaluator (as universal machine): 求值器 (作为通用机) +evaluators: 求值器 (各种类型) +ev_application: ev_application (求值应用式 - 显式控制) +ev_assignment: ev_assignment (求值赋值 - 显式控制) +ev_block: ev_block (求值块 - 显式控制) +ev_conditional: ev_conditional (求值条件式 - 显式控制) +ev_declaration: ev_declaration (求值声明 - 显式控制) +even_fibs: even_fibs (偶数斐波那契数函数/序列) +event-driven simulation: 事件驱动模拟 +ev_function_declaration: ev_function_declaration (求值函数声明 - 显式控制) +ev_lambda: ev_lambda (求值 lambda 表达式 - 显式控制) +evlis tail recursion: evlis 尾递归 (求值参数列表的尾递归) +ev_literal: ev_literal (求值字面量 - 显式控制) +ev_name: ev_name (求值名称 - 显式控制) +ev_operator_combination: ev_operator_combination (求值运算符组合 - 显式控制) +ev_return: ev_return (求值返回 - 显式控制) +ev_sequence: ev_sequence (求值序列 - 显式控制) +exchange: exchange (交换函数) +execute: execute (执行函数) +execute_application: execute_application (执行应用函数) +execute_application (metacircular): execute_application (元循环版本) +execute_application (nondeterministic): execute_application (非确定性版本) +execution function: 执行函数 +execution function (in analyzing evaluator): 执行函数 (在分析求值器中) +execution function (in nondeterministic evaluator): 执行函数 (在非确定性求值器中) +execution function (in register-machine simulator): 执行函数 (在寄存器机模拟器中) +explicit-control evaluator for JavaScript: JavaScript 的显式控制求值器 +explicit-control evaluator for JavaScript (argument evaluation): 显式控制求值器 (参数求值) +explicit-control evaluator for JavaScript (assignments): 显式控制求值器 (赋值) +explicit-control evaluator for JavaScript (blocks): 显式控制求值器 (块) +explicit-control evaluator for JavaScript (combinations): 显式控制求值器 (组合式) +explicit-control evaluator for JavaScript (compound functions): 显式控制求值器 (复合函数) +explicit-control evaluator for JavaScript (conditionals): 显式控制求值器 (条件式) +explicit-control evaluator for JavaScript (controller): 显式控制求值器 (控制器) +explicit-control evaluator for JavaScript (data paths): 显式控制求值器 (数据通路) +explicit-control evaluator for JavaScript (declarations): 显式控制求值器 (声明) +explicit-control evaluator for JavaScript (derived components): 显式控制求值器 (派生组件) +explicit-control evaluator for JavaScript (driver loop): 显式控制求值器 (驱动循环) +explicit-control evaluator for JavaScript (error handling): 显式控制求值器 (错误处理) +explicit-control evaluator for JavaScript (expressions with no subexpressions to evaluate): 显式控制求值器 (无子表达式求值的表达式) +explicit-control evaluator for JavaScript (function application): 显式控制求值器 (函数应用) +explicit-control evaluator for JavaScript (as machine-language program): 显式控制求值器 (作为机器语言程序) +explicit-control evaluator for JavaScript (machine model): 显式控制求值器 (机器模型) +explicit-control evaluator for JavaScript (modified for compiled code): 显式控制求值器 (为编译代码修改) +explicit-control evaluator for JavaScript (monitoring performance (stack use)): 显式控制求值器 (监控性能(栈使用)) +explicit-control evaluator for JavaScript (normal-order evaluation): 显式控制求值器 (正则序求值) +explicit-control evaluator for JavaScript (operations): 显式控制求值器 (操作) +explicit-control evaluator for JavaScript (optimizations (additional)): 显式控制求值器 (额外优化) +explicit-control evaluator for JavaScript (primitive functions): 显式控制求值器 (原语函数) +explicit-control evaluator for JavaScript (registers): 显式控制求值器 (寄存器) +explicit-control evaluator for JavaScript (return statements): 显式控制求值器 (返回语句) +explicit-control evaluator for JavaScript (running): 显式控制求值器 (运行) +explicit-control evaluator for JavaScript (sequences of statements): 显式控制求值器 (语句序列) +explicit-control evaluator for JavaScript (stack usage): 显式控制求值器 (栈使用) +explicit-control evaluator for JavaScript (syntactic forms (additional)): 显式控制求值器 (额外语法形式) +explicit-control evaluator for JavaScript (tail recursion): 显式控制求值器 (尾递归) +explicit-control evaluator for JavaScript (as universal machine): 显式控制求值器 (作为通用机) +expmod: expmod (模幂函数) +exponential growth: 指数增长 +exponential growth (of tree-recursive Fibonacci-number computation): 指数增长 (树形递归斐波那契数计算) +exponentiation: 幂运算 / 求幂 +exponentiation (modulo n): 幂运算 (模 n) +expression: 表达式 +expression (algebraic): 表达式 (代数) +expression (literal): 表达式 (字面量) +expression (primitive boolean): 表达式 (原语布尔) +expression (symbolic): 表达式 (符号) +expression-oriented vs. imperative programming style: 面向表达式 vs. 命令式编程风格 +expression statement: 表达式语句 +expression statement (parsing of): 表达式语句 (解析) +expt: expt (幂运算函数) +expt (linear iterative version): expt (线性迭代版本) +expt (linear recursive version): expt (线性递归版本) +expt (register machine for): expt (寄存器机) +extend: extend (扩展函数 - 模式匹配) +extend_environment: extend_environment (扩展环境函数) +extend_if_consistent: extend_if_consistent (如果一致则扩展函数) +extend_if_possible: extend_if_possible (如果可能则扩展函数) +external_entry: external_entry (外部入口点) +extract_labels: extract_labels (提取标号函数) + +F + +factorial: factorial (阶乘函数) +factorial (as an abstract machine): factorial (作为抽象机) +factorial (compilation of): factorial (编译) +factorial (environment structure in evaluating): factorial (求值时的环境结构) +factorial (linear iterative version): factorial (线性迭代版本) +factorial (linear recursive version): factorial (线性递归版本) +factorial (register machine for (iterative)): factorial (寄存器机 (迭代)) +factorial (register machine for (recursive)): factorial (寄存器机 (递归)) +factorial (stack usage, compiled): factorial (栈使用, 编译后) +factorial (stack usage, interpreted): factorial (栈使用, 解释执行) +factorial (stack usage, register machine): factorial (栈使用, 寄存器机) +factorial (with assignment): factorial (带赋值版本) +factorial (with higher-order functions): factorial (带高阶函数版本) +factorial (with while loop): factorial (带 while 循环版本) +factorial: 阶乘 +factorial (without declaration or assignment): 阶乘 (无声明或赋值) +factorial (infinite stream): 阶乘 (无限流) +failure, in nondeterministic computation: 失败 (在非确定性计算中) +failure, in nondeterministic computation (bug vs.): 失败 (与 Bug 比较) +failure, in nondeterministic computation (searching and): 失败 (与搜索) +failure continuation (nondeterministic evaluator): 失败续延 (非确定性求值器) +failure continuation (nondeterministic evaluator, constructed by amb): 失败续延 (由 amb 构造) +failure continuation (nondeterministic evaluator, constructed by assignment): 失败续延 (由赋值构造) +failure continuation (nondeterministic evaluator, constructed by driver loop): 失败续延 (由驱动循环构造) +false (keyword): false (关键字) +falsiness: 假值性 (JavaScript 中的假值概念) +fast_expt: fast_expt (快速幂函数) +fast_is_prime: fast_is_prime (快速素性测试函数) +feedback loop, modeled with streams: 反馈回路 (用流建模) +Feeley, Marc: Feeley, Marc (人名) +Feigenbaum, Edward: Feigenbaum, Edward (人名) +Fenichel, Robert: Fenichel, Robert (人名) +Fermat, Pierre de: Fermat, Pierre de (人名) +Fermat's Little Theorem: 费马小定理 +Fermat's Little Theorem (alternate form): 费马小定理 (替代形式) +Fermat's Little Theorem (proof): 费马小定理 (证明) +fermat_test: fermat_test (费马测试函数) +Fermat test for primality: 费马素性测试 +Fermat test for primality (variant of): 费马素性测试 (变体) +fetch_assertions: fetch_assertions (获取断言函数) +fetch_rules: fetch_rules (获取规则函数) +fib: fib (斐波那契数函数) +fib (linear iterative version): fib (线性迭代版本) +fib (logarithmic version): fib (对数版本) +fib (register machine for (tree-recursive)): fib (寄存器机 (树形递归)) +fib (stack usage, compiled): fib (栈使用, 编译后) +fib (stack usage, interpreted): fib (栈使用, 解释执行) +fib (tree-recursive version): fib (树形递归版本) +fib (with memoization): fib (带记忆化版本) +Fibonacci numbers: 斐波那契数 +Fibonacci numbers (Euclid's GCD algorithm and): 斐波那契数 (与欧几里得 GCD 算法) +Fibonacci numbers (infinite stream of): 斐波那契数 (无限流) +fibs (infinite stream): fibs (斐波那契无限流) +fibs (infinite stream, implicit definition): fibs (斐波那契无限流, 隐式定义) +FIFO buffer: 先入先出缓冲器 (队列) +filter: filter (过滤函数) +filter: 过滤器 +filtered_accumulate: filtered_accumulate (带过滤的累积函数) +find_assertions: find_assertions (查找断言函数) +find_divisor: find_divisor (查找因子函数) +first_agenda_item: first_agenda_item (首个议程项选择器) +first-class elements in language: 语言中的一等公民 +first_conjunct: first_conjunct (首个合取项选择器) +first_disjunct: first_disjunct (首个析取项选择器) +first_frame: first_frame (首个帧选择器) +first_segment: first_segment (首个段选择器) +first_statement: first_statement (首条语句选择器) +first_term: first_term (首项选择器) +fixed-length code: 定长编码 +fixed_point: fixed_point (不动点函数) +fixed_point (as iterative improvement): fixed_point (作为迭代改进) +fixed point: 不动点 +fixed point (computing with calculator): 不动点 (用计算器计算) +fixed point (of cosine): 不动点 (余弦的) +fixed point (cube root as): 不动点 (立方根作为) +fixed point (fourth root as): 不动点 (四次方根作为) +fixed point (golden ratio as): 不动点 (黄金分割比作为) +fixed point (as iterative improvement): 不动点 (作为迭代改进) +fixed point (in Newton's method): 不动点 (在牛顿法中) +fixed point (nth root as): 不动点 (n 次方根作为) +fixed point (square root as): 不动点 (平方根作为) +fixed point (of transformed function): 不动点 (变换后函数) +fixed point (unification and): 不动点 (与合一) +fixed_point_of_transform: fixed_point_of_transform (变换后函数的不动点函数) +flag register: 标志寄存器 +flatmap: flatmap (扁平映射函数) +flatten_stream: flatten_stream (扁平化流函数) +flip_horiz: flip_horiz (水平翻转函数 - 图片语言) +flipped_pairs: flipped_pairs (翻转序对函数 - 图片语言) +flip_vert: flip_vert (垂直翻转函数 - 图片语言) +Floyd, Robert: Floyd, Robert (人名) +fold_left: fold_left (左折叠函数) +fold_right: fold_right (右折叠函数) +Forbus, Kenneth D.: Forbus, Kenneth D. (人名) +force_it: force_it (强制求值函数) +force_it (memoized version): force_it (记忆化版本) +forcing: 强制求值 +forcing (tail of stream): 强制求值 (流的尾部) +forcing (of thunk): 强制求值 (thunk 的) +for_each: for_each (遍历执行函数) +for_each_except: for_each_except (除...之外遍历执行函数) +forget_value: forget_value (忘记值函数 - 约束系统) +formal parameters: 形式参数 +Fortran: Fortran (编程语言名) +Fortran (inventor of): Fortran (发明者) +forwarding address: 转发地址 (垃圾回收) +fourth root, as fixed point: 四次方根 (作为不动点) +fraction: 分数 +frame (environment model): 帧 (环境模型) +frame (environment model, as repository of local state): 帧 (作为局部状态的存储库) +frame (environment model, global): 帧 (全局) +frame (picture language): 框架 (图片语言) +frame (picture language, coordinate map): 框架 (坐标映射) +frame (query interpreter): 帧 (查询解释器) +frame (query interpreter, representation): 帧 (表示) +frame_coord_map: frame_coord_map (框架坐标映射选择器) +framed-stack discipline: 帧栈规则 +frame_symbols: frame_symbols (帧符号选择器) +frame_values: frame_values (帧值选择器) +free register: free 寄存器 (空闲内存指针) +free list: 空闲列表 (内存管理) +free name: 自由变量 / 自由名 +free name (capturing): 自由变量 (捕获) +free name (in internal declaration): 自由变量 (在内部声明中) +Friedman, Daniel P.: Friedman, Daniel P. (人名) +fringe: fringe (树叶列表函数) +fringe (as a tree enumeration): fringe (作为树的枚举) +front_ptr: front_ptr (队头指针) +front_queue: front_queue (队头元素函数) +full-adder: 全加器 +full_adder: full_adder (全加器函数) +fun register: fun 寄存器 (函数寄存器) +function (keyword): function (关键字) +function (JavaScript): 函数 (JavaScript) +function (JavaScript, anonymous): 函数 (匿名) +function (JavaScript, as argument): 函数 (作为参数) +function (JavaScript, as black box): 函数 (作为黑箱) +function (JavaScript, body of): 函数 (体) +function (JavaScript, compound): 函数 (复合) +function (JavaScript, creating with function declaration): 函数 (用函数声明创建) +function (JavaScript, creating with lambda expression): 函数 (用 lambda 表达式创建) +function (JavaScript, declaration of): 函数 (声明) +function (JavaScript, first-class): 函数 (一等公民) +function (JavaScript, as general method): 函数 (作为通用方法) +function (JavaScript, generic): 函数 (通用) +function (JavaScript, higher-order): 函数 (高阶) +function (JavaScript, mathematical function vs.): 函数 (与数学函数的比较) +function (JavaScript, memoized): 函数 (记忆化) +function (JavaScript, monitored): 函数 (被监控) +function (JavaScript, name of): 函数 (名称) +function (JavaScript, naming (with function declaration)): 函数 (命名 (用函数声明)) +function (JavaScript, parameters of): 函数 (参数) +function (JavaScript, as pattern for local evolution of a process): 函数 (作为过程局部演化的模式) +function (JavaScript, primitive): 函数 (原语) +function (JavaScript, as returned value): 函数 (作为返回值) +function (JavaScript, returning multiple values): 函数 (返回多个值) +function (JavaScript, scope of parameters): 函数 (参数作用域) +function (JavaScript, syntactic form vs.): 函数 (与语法形式比较) +function (JavaScript, with any number of arguments): 函数 (带任意数量参数) +function (mathematical): 函数 (数学) +function (mathematical, -> notation for): 函数 (数学, -> 表示法) +function (mathematical, Ackermann's): 函数 (数学, 阿克曼) +function (mathematical, composition of): 函数 (数学, 复合) +function (mathematical, derivative of): 函数 (数学, 导数) +function (mathematical, fixed point of): 函数 (数学, 不动点) +function (mathematical, JavaScript function vs.): 函数 (数学, 与 JavaScript 函数比较) +function (mathematical, rational): 函数 (数学, 有理) +function (mathematical, repeated application of): 函数 (数学, 重复应用) +function (mathematical, smoothing of): 函数 (数学, 平滑) +functional abstraction: 函数抽象 +functional programming: 函数式编程 +functional programming (concurrency and): 函数式编程 (与并发) +functional programming (functional programming languages): 函数式编程 (函数式编程语言) +functional programming (time and): 函数式编程 (与时间) +functional representation of data: 数据的函数表示 +functional representation of data (mutable data): 数据的函数表示 (可变数据) +function application: 函数应用 +function application (compound expression as function expression of): 函数应用 (复合表达式作为其函数表达式) +function application (environment model of): 函数应用 (环境模型) +function application (evaluation of): 函数应用 (求值) +function application (as function expression of application): 函数应用 (作为应用的函数表达式) +function application (parsing of): 函数应用 (解析) +function application (substitution model of): 函数应用 (代换模型) +function_body: function_body (函数体选择器) +function box, in digital circuit: 函数盒 (在数字电路中) +function declaration: 函数声明 +function declaration (as derived component): 函数声明 (作为派生组件) +function declaration (hoisting of): 函数声明 (提升) +function declaration (lambda expression vs.): 函数声明 (与 lambda 表达式比较) +function declaration (parsing of): 函数声明 (解析) +function_declaration_body: function_declaration_body (函数声明体选择器) +function_declaration_name: function_declaration_name (函数声明名称选择器) +function_declaration_parameters: function_declaration_parameters (函数声明参数选择器) +function_decl_to_constant_decl: function_decl_to_constant_decl (函数声明转常量声明函数) +function_environment: function_environment (函数环境选择器) +function_expression: function_expression (函数表达式选择器) +function expression: 函数表达式 +function expression (application as): 函数表达式 (应用作为) +function expression (compound expression as): 函数表达式 (复合表达式作为) +function expression (lambda expression as): 函数表达式 (lambda 表达式作为) +function_parameters: function_parameters (函数参数选择器) + +G + +Gabriel, Richard P.: Gabriel, Richard P. (人名) +garbage collection: 垃圾回收 +garbage collection (memoization and): 垃圾回收 (与记忆化) +garbage collection (mutation and): 垃圾回收 (与修改) +garbage collection (tail recursion and): 垃圾回收 (与尾递归) +garbage collector: 垃圾回收器 +garbage collector (compacting): 垃圾回收器 (紧凑式) +garbage collector (mark-sweep): 垃圾回收器 (标记-清除) +garbage collector (stop-and-copy): 垃圾回收器 (停止-复制) +Gargle: Gargle (虚构公司名) +GCD: 最大公约数 (GCD) +gcd: gcd (最大公约数函数) +gcd (register machine for): gcd (寄存器机) +gcd_machine: gcd_machine (GCD 寄存器机定义) +gcd_terms: gcd_terms (项的最大公约数函数) +general-purpose computer, as universal machine: 通用计算机 (作为通用机) +generate_huffman_tree: generate_huffman_tree (生成霍夫曼树函数) +generating sentences: 生成句子 +generic arithmetic operations: 通用算术操作 +generic arithmetic operations (structure of system): 通用算术操作 (系统结构) +generic function: 通用函数 +generic function (generic selector): 通用函数 (通用选择器) +generic operation: 通用操作 +generic types: 泛型类型 +Genesis: 《创世纪》 +get: get (获取函数 - 表操作) +get_contents: get_contents (获取内容函数 - 寄存器/内存) +get_current_environment: get_current_environment (获取当前环境函数) +get_register: get_register (获取寄存器对象函数) +get_register_contents: get_register_contents (获取寄存器内容函数) +get_signal: get_signal (获取信号值函数) +get_time (primitive function): get_time (获取时间原语函数) +get_value: get_value (获取值函数 - 约束系统) +global environment: 全局环境 +global environment (in metacircular evaluator): 全局环境 (在元循环求值器中) +global frame: 全局帧 +Goguen, Joseph: Goguen, Joseph (人名) +golden ratio: 黄金分割比 +golden ratio (as continued fraction): 黄金分割比 (表示为连分数) +golden ratio (as fixed point): 黄金分割比 (作为不动点) +good parts of JavaScript: JavaScript 的精华部分 +Gordon, Michael: Gordon, Michael (人名) +go_to (in register machine): go_to (寄存器机中的跳转指令) +go_to (in register machine, destination in register): go_to (目标在寄存器中) +go_to (in register machine, instruction constructor): go_to (跳转指令构造器) +go_to (in register machine, simulating): go_to (模拟跳转指令) +go_to_dest: go_to_dest (跳转目标选择器) +grammar: 语法 / 文法 +graphics: 图形学 +Gray, Jim: Gray, Jim (人名) +greatest common divisor: 最大公约数 +greatest common divisor (generic): 最大公约数 (通用) +greatest common divisor (of polynomials): 最大公约数 (多项式的) +greatest common divisor (used to estimate pi): 最大公约数 (用于估算 pi) +greatest common divisor (used in rational-number arithmetic): 最大公约数 (用于有理数算术) +Green, Cordell: Green, Cordell (人名) +Guttag, John Vogel: Guttag, John Vogel (人名) + +H + +half-adder: 半加器 +half_adder: half_adder (半加器函数) +half_adder (simulation of): half_adder (模拟) +half-interval method: 半区间法 +half_interval_method: half_interval_method (半区间法函数) +half-interval method (Newton's method vs.): 半区间法 (与牛顿法比较) +halting problem: 停机问题 +Halting Theorem: 停机定理 +Hamming, Richard Wesley: Hamming, Richard Wesley (人名) +Hanson, Christopher P.: Hanson, Christopher P. (人名) +Hardy, Godfrey Harold: Hardy, Godfrey Harold (人名) +Haskell: Haskell (编程语言名) +Hassle: Hassle (编程语言名) +has_value: has_value (是否有值检查函数 - 约束系统) +Havender, J.: Havender, J. (人名) +Haynes, Christopher T.: Haynes, Christopher T. (人名) +head (primitive function): head (列表头元素原语函数) +head (primitive function, axiom for): head (公理) +head (primitive function, functional implementation of): head (函数式实现) +head (primitive function, implemented with vectors): head (用向量实现) +head (primitive function, as list operation): head (作为列表操作) +headed list: 带头结点的列表 +Henderson, Peter: Henderson, Peter (人名) +Henderson, Peter (Henderson diagram): Henderson, Peter (Henderson 图) +Henz, Martin, children of: Henz, Martin 的孩子们 +Heraclitus: Heraclitus (人名) +Heron of Alexandria: Heron of Alexandria (人名) +Hewitt, Carl Eddie: Hewitt, Carl Eddie (人名) +hiding principle: 隐藏原则 +hierarchical data structures: 层次数据结构 +hierarchy of types: 类型层次结构 +hierarchy of types (inadequacy of): 类型层次结构 (不足之处) +hierarchy of types (in symbolic algebra): 类型层次结构 (在符号代数中) +higher-order functions: 高阶函数 +higher-order functions (function as argument): 高阶函数 (函数作为参数) +higher-order functions (function as general method): 高阶函数 (函数作为通用方法) +higher-order functions (function as returned value): 高阶函数 (函数作为返回值) +higher-order functions (in metacircular evaluator): 高阶函数 (在元循环求值器中) +higher-order functions (static typing and): 高阶函数 (与静态类型) +high-level language, machine language vs.: 高级语言 vs. 机器语言 +Hilfinger, Paul: Hilfinger, Paul (人名) +Hoare, Charles Antony Richard: Hoare, Charles Antony Richard (人名) +Hodges, Andrew: Hodges, Andrew (人名) +Hofstadter, Douglas R.: Hofstadter, Douglas R. (人名) +hoisting of function declarations: 函数声明提升 +Horner, W. G.: Horner, W. G. (人名) +Horner's rule: 霍纳规则 (秦九韶算法) +"how to" vs. "what is" description: "如何做" vs. "是什么" 的描述 +Huffman, David: Huffman, David (人名) +Huffman code: 霍夫曼编码 +Huffman code (optimality of): 霍夫曼编码 (最优性) +Huffman code (order of growth of encoding): 霍夫曼编码 (编码的增长阶) +Hughes, R. J. M.: Hughes, R. J. M. (人名) + +I + +identity: identity (恒等函数) +if (keyword): if (关键字) +imag_part: imag_part (虚部选择器) +imag_part (data-directed): imag_part (数据导向版本) +imag_part (polar representation): imag_part (极坐标表示) +imag_part (rectangular representation): imag_part (直角坐标表示) +imag_part (with tagged data): imag_part (带标签数据版本) +imag_part_polar: imag_part_polar (极坐标虚部函数) +imag_part_rectangular: imag_part_rectangular (直角坐标虚部函数) +immediately invoked lambda expression: 立即调用 lambda 表达式 (IILE) +imperative programming: 命令式编程 +imperative vs. declarative knowledge: 命令性知识 vs. 说明性知识 +imperative vs. declarative knowledge (logic programming and): 命令性知识 vs. 说明性知识 (与逻辑编程) +imperative vs. declarative knowledge (nondeterministic computing and): 命令性知识 vs. 说明性知识 (与非确定性计算) +imperative vs. expression-oriented programming style: 命令式 vs. 面向表达式编程风格 +inc: inc (加一函数) +incremental development of programs: 程序的增量开发 +indentation: 缩进 +indeterminate of a polynomial: 不定元 (多项式的) +indexing a data base: 数据库索引 +inference, method of: 推理方法 +infinite series: 无穷级数 +infinite stream(s): 无限流 +infinite stream(s) (merging): 无限流 (合并) +infinite stream(s) (merging as a relation): 无限流 (合并作为关系) +infinite stream(s) (of factorials): 无限流 (阶乘) +infinite stream(s) (of Fibonacci numbers): 无限流 (斐波那契数) +infinite stream(s) (of integers): 无限流 (整数) +infinite stream(s) (of pairs): 无限流 (序对) +infinite stream(s) (of prime numbers): 无限流 (素数) +infinite stream(s) (of random numbers): 无限流 (随机数) +infinite stream(s) (representing power series): 无限流 (表示幂级数) +infinite stream(s) (to model signals): 无限流 (用于模拟信号) +infinite stream(s) (to sum a series): 无限流 (用于级数求和) +infix notation: 中缀表示法 +infix notation (prefix notation vs.): 中缀表示法 (与前缀表示法比较) +infix operator: 中缀运算符 +inform_about_no_value: inform_about_no_value (通知无值函数) +inform_about_value: inform_about_value (通知有值函数) +information retrieval: 信息检索 +Ingerman, Peter: Ingerman, Peter (人名) +initialize_stack operation in register machine: initialize_stack 操作 (寄存器机中) +insert: insert (插入函数 - 表操作) +insert (in one-dimensional table): insert (在一维表中) +insert (in two-dimensional table): insert (在二维表中) +insert_queue: insert_queue (插入队列函数) +install_complex_package: install_complex_package (安装复数包函数) +install_javascript_number_package: install_javascript_number_package (安装 JS 数字包函数) +install_polar_package: install_polar_package (安装极坐标包函数) +install_polynomial_package: install_polynomial_package (安装多项式包函数) +install_rational_package: install_rational_package (安装有理数包函数) +install_rectangular_package: install_rectangular_package (安装直角坐标包函数) +instantiate a pattern: 实例化模式 +instantiate_expression: instantiate_expression (实例化表达式函数) +instantiate_term: instantiate_term (实例化项函数) +inst_controller_instruction: inst_controller_instruction (指令的控制器指令部分) +inst_execution_fun: inst_execution_fun (指令的执行函数) +instruction counting: 指令计数 +instruction execution function: 指令执行函数 +instructions: instructions (指令列表选择器) +instruction sequence: 指令序列 +instruction tracing: 指令跟踪 +integer(s): 整数 +integerizing factor: 整数化因子 (多项式 GCD) +integers (infinite stream): integers (整数无限流) +integers (infinite stream, implicit definition): integers (整数无限流, 隐式定义) +integers (infinite stream, lazy-list version): integers (整数无限流, 惰性列表版本) +integers_starting_from: integers_starting_from (从...开始的整数流函数) +integral: integral (积分函数/过程) +integral (with delayed argument): integral (带延迟参数) +integral (with lambda expression): integral (带 lambda 表达式) +integral (lazy-list version): integral (惰性列表版本) +integral (need for delayed evaluation): integral (延迟求值需求) +integral: 积分 +integral (of a power series): 积分 (幂级数的) +integrated-circuit implementation of Scheme: Scheme 的集成电路实现 +integrate_series: integrate_series (幂级数积分函数) +integrator, for signals: 积分器 (用于信号) +interleave: interleave (交错合并流函数) +interleave_delayed: interleave_delayed (延迟交错合并流函数) +internal declaration: 内部声明 +internal declaration (in environment model): 内部声明 (在环境模型中) +internal declaration (free name in): 内部声明 (其中的自由变量) +internal declaration (names distinct from parameters): 内部声明 (名称与参数不同) +internal declaration (in nondeterministic evaluator): 内部声明 (在非确定性求值器中) +internal declaration (position of): 内部声明 (位置) +internal declaration (restrictions on): 内部声明 (限制) +internal declaration (scanning out): 内部声明 (扫描出) +internal declaration (scope of name): 内部声明 (名称作用域) +Internet "Worm": 互联网“蠕虫” +interning strings: 字符串驻留 +interpreter: 解释器 +interpreter (compiler vs.): 解释器 (与编译器比较) +interpreter (read-evaluate-print loop): 解释器 (读取-求值-打印循环) +intersection_set: intersection_set (集合交集函数) +intersection_set (binary-tree representation): intersection_set (二叉树表示) +intersection_set (ordered-list representation): intersection_set (有序列表表示) +intersection_set (unordered-list representation): intersection_set (无序列表表示) +interval arithmetic: 区间算术 +invariant quantity of an iterative process: 迭代过程的不变量 +inverter: 反相器 / 非门 +inverter: inverter (反相器函数) +is_, in predicate names: is_ (谓词命名约定) +is_amb: is_amb (检查是否为 amb 表达式) +is_application: is_application (检查是否为应用式) +is_assertion: is_assertion (检查是否为断言) +is_assignment: is_assignment (检查是否为赋值) +is_block: is_block (检查是否为块) +is_boolean: is_boolean (检查是否为布尔值) +is_compiled_function: is_compiled_function (检查是否为编译后函数) +is_compound_function: is_compound_function (检查是否为复合函数) +is_conditional: is_conditional (检查是否为条件式) +is_constant_exp: is_constant_exp (检查是否为常量表达式) +is_declaration: is_declaration (检查是否为声明) +is_divisible: is_divisible (检查是否可整除) +is_element_of_set: is_element_of_set (检查是否为集合元素) +is_element_of_set (binary-tree representation): is_element_of_set (二叉树表示) +is_element_of_set (ordered-list representation): is_element_of_set (有序列表表示) +is_element_of_set (unordered-list representation): is_element_of_set (无序列表表示) +is_empty_agenda: is_empty_agenda (检查议程是否为空) +is_empty_conjunction: is_empty_conjunction (检查合取是否为空) +is_empty_disjunction: is_empty_disjunction (检查析取是否为空) +is_empty_queue: is_empty_queue (检查队列是否为空) +is_empty_sequence: is_empty_sequence (检查序列是否为空) +is_empty_termlist: is_empty_termlist (检查项列表是否为空) +is_equal (generic predicate): is_equal (通用相等性谓词) +is_equal_to_zero (generic): is_equal_to_zero (通用零值检查) +is_equal_to_zero (generic, for polynomials): is_equal_to_zero (通用零值检查, 用于多项式) +is_even: is_even (检查是否为偶数) +is_falsy: is_falsy (检查是否为假值 - JavaScript) +is_falsy (full JavaScript version): is_falsy (完整 JavaScript 版本) +is_falsy (why used in explicit-control evaluator): is_falsy (为何在显式控制求值器中使用) +is_function_declaration: is_function_declaration (检查是否为函数声明) +is_label_exp: is_label_exp (检查是否为标号表达式) +is_lambda_expression: is_lambda_expression (检查是否为 lambda 表达式) +is_last_argument_expression: is_last_argument_expression (检查是否为最后一个参数表达式) +is_last_statement: is_last_statement (检查是否为最后一条语句) +is_leaf: is_leaf (检查是否为叶节点) +is_list_construction: is_list_construction (检查是否为列表构造) +is_literal: is_literal (检查是否为字面量) +is_name: is_name (检查是否为名称) +is_null (primitive function): is_null (检查是否为空列表原语函数) +is_null (primitive function, implemented with typed pointers): is_null (用类型指针实现) +is_number (primitive function): is_number (检查是否为数值原语函数) +is_number (primitive function, data types and): is_number (与数据类型) +is_number (primitive function, implemented with typed pointers): is_number (用类型指针实现) +is_operation_exp: is_operation_exp (检查是否为操作表达式) +is_pair (primitive function): is_pair (检查是否为序对原语函数) +is_pair (primitive function, implemented with typed pointers): is_pair (用类型指针实现) +is_polar: is_polar (检查是否为极坐标表示) +is_prime: is_prime (检查是否为素数) +is_primitive_function: is_primitive_function (检查是否为原语函数) +is_product: is_product (检查是否为乘积) +is_rectangular: is_rectangular (检查是否为直角坐标表示) +is_register_exp: is_register_exp (检查是否为寄存器表达式) +is_return_statement: is_return_statement (检查是否为返回语句) +is_return_value: is_return_value (检查是否为返回值) +is_rule: is_rule (检查是否为规则) +is_same_variable: is_same_variable (检查是否为相同变量) +is_sequence: is_sequence (检查是否为序列) +is_string (primitive function): is_string (检查是否为字符串原语函数) +is_string (primitive function, data types and): is_string (与数据类型) +is_string (primitive function, implemented with typed pointers): is_string (用类型指针实现) +is_sum: is_sum (检查是否为和) +is_tagged_list: is_tagged_list (检查是否为带标签列表) +is_truthy: is_truthy (检查是否为真值 - JavaScript) +is_truthy (full JavaScript version): is_truthy (完整 JavaScript 版本) +is_undefined (primitive function): is_undefined (检查是否未定义原语函数) +is_variable: is_variable (检查是否为变量) +is_variable (for algebraic expressions): is_variable (用于代数表达式) +is_variable (in query system): is_variable (在查询系统中) +iteration contructs: 迭代结构 +iterative improvement: 迭代改进 +iterative process: 迭代过程 +iterative process (as a stream process): 迭代过程 (作为流过程) +iterative process (design of algorithm): 迭代过程 (算法设计) +iterative process (implemented by function call): 迭代过程 (通过函数调用实现) +iterative process (linear): 迭代过程 (线性) +iterative process (recursive process vs.): 迭代过程 (与递归过程比较) +iterative process (register machine for): 迭代过程 (寄存器机) + +J + +Jaffar, Joxan: Jaffar, Joxan (人名) +Java: Java (编程语言名) +Java (recursive functions in): Java (递归函数) +JavaScript: JavaScript (编程语言) +JavaScript (applicative-order evaluation in): JavaScript (应用序求值) +JavaScript (eval in): JavaScript (eval 函数) +JavaScript (first-class functions in): JavaScript (一等公民函数) +JavaScript (good parts): JavaScript (精华部分) +JavaScript (history of): JavaScript (历史) +JavaScript (internal type system): JavaScript (内部类型系统) +JavaScript (tail recursion in): JavaScript (尾递归) +JavaScript environment used in this book: 本书中使用的 JavaScript 环境 +javascript_number package: javascript_number 包 (JS 数字包) +javascript_number_to_complex: javascript_number_to_complex (JS 数字转复数函数) +javascript_number_to_javascript_number: javascript_number_to_javascript_number (JS 数字转 JS 数字函数) +JavaScript package sicp: JavaScript sicp 包 +javascript_predicate (query interpreter): javascript_predicate (查询解释器中的 JS 谓词) +javascript_predicate (query language): javascript_predicate (查询语言中的 JS 谓词) +javascript_predicate (query language, evaluation of): javascript_predicate (查询语言中 JS 谓词的求值) +javascript_predicate_expression: javascript_predicate_expression (JS 谓词表达式选择器) +Jayaraman, Sundaresan: Jayaraman, Sundaresan (人名) + +K + +Kaldewaij, Anne: Kaldewaij, Anne (人名) +Karr, Alphonse: Karr, Alphonse (人名) +Kepler, Johannes: Kepler, Johannes (人名) +key: 键 / 关键字 +key of a record: 记录的键 +key of a record (in a data base): 记录的键 (在数据库中) +key of a record (in a table): 记录的键 (在表中) +key of a record (testing equality of): 记录的键 (测试相等性) +keyword: 关键字 +keywords: 关键字 (列表) +keywords (break): 关键字 (break) +keywords (const): 关键字 (const) +keywords (continue): 关键字 (continue) +keywords (else): 关键字 (else) +keywords (false): 关键字 (false) +keywords (function): 关键字 (function) +keywords (if): 关键字 (if) +keywords (let): 关键字 (let) +keywords (null): 关键字 (null) +keywords (return): 关键字 (return) +keywords (true): 关键字 (true) +keywords (while): 关键字 (while) +Knuth, Donald E.: Knuth, Donald E. (人名) +Kolmogorov, A. N.: Kolmogorov, A. N. (人名) +Konopasek, Milos: Konopasek, Milos (人名) +Kowalski, Robert: Kowalski, Robert (人名) +KRC: KRC (编程语言名) + +L + +label (in register machine): label (寄存器机中的标号) +label (in register machine, simulating): label (模拟标号) +label_exp_label: label_exp_label (标号表达式的标号选择器) +Lagrange interpolation formula: 拉格朗日插值公式 +lambda calculus (lambda calculus): lambda 演算 +lambda_body: lambda_body (lambda 体选择器) +lambda expression: lambda 表达式 +lambda expression (block as body of): lambda 表达式 (块作为体) +lambda expression (as function expression of application): lambda 表达式 (作为应用的函数表达式) +lambda expression (function declaration vs.): lambda 表达式 (与函数声明比较) +lambda expression (immediately invoked): lambda 表达式 (立即调用) +lambda expression (lazy evaluation and): lambda 表达式 (与惰性求值) +lambda expression (parsing of): lambda 表达式 (解析) +lambda expression (precedence of): lambda 表达式 (优先级) +lambda expression (value of): lambda 表达式 (值) +lambda_parameter_symbols: lambda_parameter_symbols (lambda 参数符号选择器) +Lambert, J.H.: Lambert, J.H. (人名) +Lamé, Gabriel: Lamé, Gabriel (人名) +Lamé's Theorem: 拉梅定理 +Lamport, Leslie: Lamport, Leslie (人名) +Lampson, Butler: Lampson, Butler (人名) +Landin, Peter: Landin, Peter (人名) +language: 语言 +Lapalme, Guy: Lapalme, Guy (人名) +last_pair: last_pair (列表最后一个序对函数) +last_pair (rules): last_pair (规则版本) +lazy evaluation: 惰性求值 / 延迟求值 +lazy evaluator: 惰性求值器 +lazy list: 惰性列表 +lazy pair: 惰性序对 +lazy tree: 惰性树 +least commitment, principle of: 最少承诺原则 +left-associative: 左结合 +left_branch: left_branch (左分支选择器) +Leibniz, Baron Gottfried Wilhelm von: Leibniz, Baron Gottfried Wilhelm von (人名) +Leibniz, Baron Gottfried Wilhelm von (proof of Fermat's Little Theorem): Leibniz (费马小定理证明) +Leibniz, Baron Gottfried Wilhelm von (series for pi): Leibniz (pi 级数) +Leiserson, Charles E.: Leiserson, Charles E. (人名) +length: length (列表长度函数) +length (as accumulation): length (作为累积) +length (iterative version): length (迭代版本) +length (recursive version): length (递归版本) +let (keyword): let (关键字) +let* (Scheme variant of let): let* (Scheme 中 let 的变体) +lexical_address_assign: lexical_address_assign (词法地址赋值函数) +lexical addressing: 词法寻址 +lexical_address_lookup: lexical_address_lookup (词法地址查找函数) +lexical scoping: 词法作用域 +lexical scoping (environment structure and): 词法作用域 (与环境结构) +Lieberman, Henry: Lieberman, Henry (人名) +LIFO buffer: 后入先出缓冲器 (栈) +linear growth: 线性增长 +linear iterative process: 线性迭代过程 +linear iterative process (order of growth): 线性迭代过程 (增长阶) +linear recursive process: 线性递归过程 +linear recursive process (order of growth): 线性递归过程 (增长阶) +line segment: 线段 +line segment (represented as pair of points): 线段 (表示为点对) +line segment (represented as pair of vectors): 线段 (表示为向量对) +linkage descriptor: 链接描述符 +Liskov, Barbara Huberman: Liskov, Barbara Huberman (人名) +Lisp: Lisp (编程语言名) +Lisp (as ancestor of JavaScript): Lisp (作为 JavaScript 的祖先) +Lisp (on DEC PDP-1): Lisp (在 DEC PDP-1 上) +Lisp (efficiency of): Lisp (效率) +Lisp (MDL dialect of): Lisp (MDL 方言) +list (primitive function): list (列表构造原语函数) +list(s): 列表 +list(s) (adjoining to with pair): 列表 (用 pair 添加) +list(s) (combining with append): 列表 (用 append 连接) +list(s) (constructing with pair): 列表 (用 pair 构造) +list(s) (converting a binary tree to a): 列表 (从二叉树转换) +list(s) (converting to a binary tree): 列表 (转换为二叉树) +list(s) (empty): 列表 (空) +list(s) (equality of): 列表 (相等性) +list(s) (headed): 列表 (带头结点) +list(s) (last pair of): 列表 (最后一个序对) +list(s) (lazy): 列表 (惰性) +list(s) (length of): 列表 (长度) +list(s) (list structure vs.): 列表 (与列表结构比较) +list(s) (manipulation with head, tail, and pair): 列表 (用 head, tail, pair 操作) +list(s) (mapping over): 列表 (映射) +list(s) (nth element of): 列表 (第 n 个元素) +list(s) (operations on): 列表 (操作) +list(s) (printed representation of): 列表 (打印表示) +list(s) (reversing): 列表 (反转) +list(s) (techniques for manipulating): 列表 (操作技巧) +list(s) (walking down with tail): 列表 (用 tail 遍历) +list_difference: list_difference (列表差集函数) +list notation for data: 数据的列表表示法 +list_of_arg_values: list_of_arg_values (参数值列表函数) +list_of_delayed_args: list_of_delayed_args (延迟参数列表函数) +list_of_values: list_of_values (值列表函数) +list_of_values (without higher-order functions): list_of_values (无高阶函数版本) +list_ref: list_ref (列表引用函数) +list structure: 列表结构 +list structure (list vs.): 列表结构 (与列表比较) +list structure (mutable): 列表结构 (可变) +list structure (represented using vectors): 列表结构 (用向量表示) +list-structured memory: 列表结构内存 +list_to_tree: list_to_tree (列表转树函数) +list_union: list_union (列表并集函数) +literal expression: 字面量表达式 +literal expression (parsing of): 字面量表达式 (解析) +literal_value: literal_value (字面量值选择器) +lives_near (rule): lives_near (规则: 住在附近) +local evolution of a process: 过程的局部演化 +local name: 局部名称 / 局部变量 +local state: 局部状态 +local state (maintained in frames): 局部状态 (在帧中维护) +local state variable: 局部状态变量 +location: 位置 (内存) +Locke, John: Locke, John (人名) +logarithm, approximating ln 2: 对数 (近似 ln 2) +logarithmic growth: 对数增长 +logical and (digital logic): 逻辑与 (数字逻辑) +logical conjunction: 逻辑与 +logical disjunction: 逻辑或 +logical_not: logical_not (逻辑非函数) +logical or (digital logic): 逻辑或 (数字逻辑) +logic programming: 逻辑编程 +logic programming (computers for): 逻辑编程 (计算机) +logic programming (history of): 逻辑编程 (历史) +logic programming (in Japan): 逻辑编程 (在日本) +logic programming (logic programming languages): 逻辑编程 (逻辑编程语言) +logic programming (mathematical logic vs.): 逻辑编程 (与数理逻辑比较) +logic puzzles: 逻辑谜题 +lookup: lookup (查找函数 - 表) +lookup (in one-dimensional table): lookup (在一维表中) +lookup (in set of records): lookup (在记录集合中) +lookup (in two-dimensional table): lookup (在二维表中) +lookup_label: lookup_label (查找标号函数) +lookup_prim: lookup_prim (查找原语操作函数) +lookup_symbol_value: lookup_symbol_value (查找符号值函数) +lookup_symbol_value (for scanned-out declarations): lookup_symbol_value (用于扫描出的声明) +looping constructs: 循环结构 +lower_bound: lower_bound (下界选择器 - 区间) + +M + +machine language: 机器语言 +machine language (high-level language vs.): 机器语言 (与高级语言比较) +magician: 魔术师 +magnitude: magnitude (幅度/模长选择器) +magnitude (data-directed): magnitude (数据导向版本) +magnitude (polar representation): magnitude (极坐标表示) +magnitude (rectangular representation): magnitude (直角坐标表示) +magnitude (with tagged data): magnitude (带标签数据版本) +magnitude_polar: magnitude_polar (极坐标幅度函数) +magnitude_rectangular: magnitude_rectangular (直角坐标幅度函数) +make_account: make_account (创建账户函数) +make_account (in environment model): make_account (在环境模型中) +make_account (with serialization): make_account (带序列化版本) +make_account_and_serializer: make_account_and_serializer (创建账户和序列化器函数) +make_accumulator: make_accumulator (创建累加器函数) +make_agenda: make_agenda (创建议程函数) +make_application: make_application (创建应用式函数) +make_assign_ef: make_assign_ef (创建赋值执行函数) +make_binding: make_binding (创建绑定函数) +make_branch_ef: make_branch_ef (创建分支执行函数) +make_center_percent: make_center_percent (根据中心和百分比创建区间) +make_center_width: make_center_width (根据中心和宽度创建区间) +make_code_tree: make_code_tree (创建编码树函数) +make_compiled_function: make_compiled_function (创建编译后函数对象) +make_complex_from_mag_ang: make_complex_from_mag_ang (根据模长和角度创建复数) +make_complex_from_real_imag: make_complex_from_real_imag (根据实部和虚部创建复数) +make_connector: make_connector (创建连接器函数) +make_constant_declaration: make_constant_declaration (创建常量声明函数) +make_cycle: make_cycle (创建循环列表函数) +make_decrementer: make_decrementer (创建递减器函数) +make_execution_function: make_execution_function (创建执行函数) +make_frame: make_frame (创建框架函数 - 图片/环境) +make_from_mag_ang: make_from_mag_ang (根据模长和角度创建对象 - 通用) +make_from_mag_ang (message-passing): make_from_mag_ang (消息传递版本) +make_from_mag_ang (polar representation): make_from_mag_ang (极坐标表示) +make_from_mag_ang (rectangular representation): make_from_mag_ang (直角坐标表示) +make_from_mag_ang_polar: make_from_mag_ang_polar (极坐标版本) +make_from_mag_ang_rectangular: make_from_mag_ang_rectangular (直角坐标版本) +make_from_real_imag: make_from_real_imag (根据实部和虚部创建对象 - 通用) +make_from_real_imag (message-passing): make_from_real_imag (消息传递版本) +make_from_real_imag (polar representation): make_from_real_imag (极坐标表示) +make_from_real_imag (rectangular representation): make_from_real_imag (直角坐标表示) +make_from_real_imag_polar: make_from_real_imag_polar (极坐标版本) +make_from_real_imag_rectangular: make_from_real_imag_rectangular (直角坐标版本) +make_function: make_function (创建函数对象函数) +make_go_to_ef: make_go_to_ef (创建跳转执行函数) +make_inst: make_inst (创建指令对象函数) +make_instruction_sequence: make_instruction_sequence (创建指令序列函数) +make_interval: make_interval (创建区间函数) +make_javascript_number: make_javascript_number (创建 JS 数字对象函数) +make_joint: make_joint (创建联名账户函数) +make_label: make_label (创建标号函数) +make_label_entry: make_label_entry (创建标号条目函数) +make_lambda_expression: make_lambda_expression (创建 lambda 表达式函数) +make_leaf: make_leaf (创建叶节点函数) +make_leaf_set: make_leaf_set (创建叶节点集合函数) +make_literal: make_literal (创建字面量函数) +make_machine: make_machine (创建寄存器机函数) +make_monitored: make_monitored (创建被监控函数) +make_mutex: make_mutex (创建互斥锁函数) +make_name: make_name (创建名称/标识符函数) +make_new_machine: make_new_machine (创建新机器函数) +make_new_variable: make_new_variable (创建新变量函数 - 查询系统) +make_operation_exp_ef: make_operation_exp_ef (创建操作表达式执行函数) +make_perform_ef: make_perform_ef (创建 perform 执行函数) +make_point: make_point (创建点函数) +make_poly: make_poly (创建多项式函数) +make_polynomial: make_polynomial (创建多项式对象函数) +make_primitive_exp_ef: make_primitive_exp_ef (创建原语表达式执行函数) +make_product: make_product (创建乘积函数/节点) +make_queue: make_queue (创建队列函数) +make_rat: make_rat (创建有理数函数) +make_rat (axiom for): make_rat (公理) +make_rat (reducing to lowest terms): make_rat (化简到最简形式) +make_rational: make_rational (创建有理数对象函数 - 通用算术) +make_register: make_register (创建寄存器函数) +make_restore_ef: make_restore_ef (创建 restore 执行函数) +make_return_value: make_return_value (创建返回值对象函数) +make_save_ef: make_save_ef (创建 save 执行函数) +make_segment: make_segment (创建线段函数) +make_serializer: make_serializer (创建序列化器函数) +make_simplified_withdraw: make_simplified_withdraw (创建简化取款函数) +make_stack: make_stack (创建栈函数) +make_stack (with monitored stack): make_stack (带监控栈) +make_sum: make_sum (创建和函数/节点) +make_table: make_table (创建表函数) +make_table (message-passing implementation): make_table (消息传递实现) +make_table (one-dimensional table): make_table (一维表) +make_tableau: make_tableau (创建 tableau 函数 - 级数加速) +make_term: make_term (创建项函数) +make_test_ef: make_test_ef (创建 test 执行函数) +make_time_segment: make_time_segment (创建时间段函数) +make_tree: make_tree (创建树函数) +make_vect: make_vect (创建向量函数) +make_wire: make_wire (创建导线函数) +make_withdraw: make_withdraw (创建取款函数) +make_withdraw (in environment model): make_withdraw (在环境模型中) +make_withdraw (using immediately invoked lambda expression): make_withdraw (使用立即调用 lambda 表达式) +making change: 换零钱 +map: map (映射函数) +map (as accumulation): map (作为累积) +mapping: 映射 +mapping (over lists): 映射 (列表上) +mapping (nested): 映射 (嵌套) +mapping (as a transducer): 映射 (作为转换器) +mapping (over trees): 映射 (树上) +map_successive_pairs: map_successive_pairs (映射连续对函数) +mark-sweep garbage collector: 标记-清除垃圾回收器 +math_atan2 (primitive function): math_atan2 (atan2 原语函数) +math_cos (primitive function): math_cos (cos 原语函数) +mathematical function: 数学函数 +mathematics: 数学 +mathematics (computer science vs.): 数学 (与计算机科学比较) +mathematics (engineering vs.): 数学 (与工程学比较) +math_floor (primitive function): math_floor (floor 原语函数) +math_log (primitive function): math_log (log 原语函数) +math_max (primitive function): math_max (max 原语函数) +math_min (primitive function): math_min (min 原语函数) +math_random (primitive function): math_random (random 原语函数) +math_random (primitive function, assignment needed for): math_random (需要赋值) +math_round (primitive function): math_round (round 原语函数) +math_sin (primitive function): math_sin (sin 原语函数) +math_trunc (primitive function): math_trunc (trunc 原语函数) +matrix, represented as sequence: 矩阵 (表示为序列) +matrix_times_matrix: matrix_times_matrix (矩阵乘矩阵函数) +matrix_times_vector: matrix_times_vector (矩阵乘向量函数) +McAllester, David Allen: McAllester, David Allen (人名) +McCarthy, John: McCarthy, John (人名) +McDermott, Drew: McDermott, Drew (人名) +MDL: MDL (编程语言名) +means of abstraction: 抽象的方法 +means of abstraction (constant declaration as): 抽象的方法 (常量声明作为) +means of combination: 组合的方法 +measure in a Euclidean ring: 欧几里得环中的度量 +member: member (列表成员检查函数) +member (extended to use pointer equality): member (扩展为使用指针相等性) +memo: memo (记忆化辅助函数) +memo_fib: memo_fib (记忆化斐波那契函数) +memoization: 记忆化 / 缓存 +memoization (call-by-need and): 记忆化 (与传需求调用) +memoization (garbage collection and): 记忆化 (与垃圾回收) +memoization (in stream tail): 记忆化 (在流尾部) +memoization (of thunks): 记忆化 (thunk 的) +memoize: memoize (记忆化高阶函数) +memory: 内存 / 存储器 +memory (in 1965): 内存 (1965年) +memory (list-structured): 内存 (列表结构) +merge: merge (合并流函数) +merge_weighted: merge_weighted (加权合并流函数) +merging infinite streams: 合并无限流 +message passing: 消息传递 +message passing (environment model and): 消息传递 (与环境模型) +message passing (in bank account): 消息传递 (在银行账户中) +message passing (in digital-circuit simulation): 消息传递 (在数字电路模拟中) +message passing (tail recursion and): 消息传递 (与尾递归) +metacircular evaluator: 元循环求值器 +metacircular evaluator for JavaScript: JavaScript 的元循环求值器 +metacircular evaluator for JavaScript (&& (logical conjunction)): 元循环求值器 (&& 逻辑与) +metacircular evaluator for JavaScript (|| (logical disjunction)): 元循环求值器 (|| 逻辑或) +metacircular evaluator for JavaScript (analyzing version): 元循环求值器 (分析版本) +metacircular evaluator for JavaScript (compilation of): 元循环求值器 (编译) +metacircular evaluator for JavaScript (component representation): 元循环求值器 (组件表示) +metacircular evaluator for JavaScript (data abstraction in): 元循环求值器 (数据抽象) +metacircular evaluator for JavaScript (data-directed evaluate): 元循环求值器 (数据导向 evaluate) +metacircular evaluator for JavaScript (derived components): 元循环求值器 (派生组件) +metacircular evaluator for JavaScript (driver loop): 元循环求值器 (驱动循环) +metacircular evaluator for JavaScript (efficiency of): 元循环求值器 (效率) +metacircular evaluator for JavaScript (environment model of evaluation in): 元循环求值器 (求值环境模型) +metacircular evaluator for JavaScript (environment operations): 元循环求值器 (环境操作) +metacircular evaluator for JavaScript (evaluate and apply): 元循环求值器 (evaluate 和 apply) +metacircular evaluator for JavaScript (evaluate–apply cycle): 元循环求值器 (evaluate-apply 循环) +metacircular evaluator for JavaScript (global environment): 元循环求值器 (全局环境) +metacircular evaluator for JavaScript (higher-order functions in): 元循环求值器 (高阶函数) +metacircular evaluator for JavaScript (implemented language vs. implementation language): 元循环求值器 (被实现语言 vs. 实现语言) +metacircular evaluator for JavaScript (job of): 元循环求值器 (工作) +metacircular evaluator for JavaScript (let* (Scheme variant of let)): 元循环求值器 (let*) +metacircular evaluator for JavaScript (order of argument evaluation): 元循环求值器 (参数求值顺序) +metacircular evaluator for JavaScript (parameters distinct from local names): 元循环求值器 (参数与局部名称区分) +metacircular evaluator for JavaScript (preventing duplicate parameters): 元循环求值器 (防止重复参数) +metacircular evaluator for JavaScript (primitive functions): 元循环求值器 (原语函数) +metacircular evaluator for JavaScript (representation of environments): 元循环求值器 (环境表示) +metacircular evaluator for JavaScript (representation of functions): 元循环求值器 (函数表示) +metacircular evaluator for JavaScript (representation of true and false): 元循环求值器 (true 和 false 表示) +metacircular evaluator for JavaScript (return value): 元循环求值器 (返回值) +metacircular evaluator for JavaScript (running): 元循环求值器 (运行) +metacircular evaluator for JavaScript (symbolic differentiation and): 元循环求值器 (与符号微分) +metacircular evaluator for JavaScript (syntactic forms (additional)): 元循环求值器 (额外语法形式) +metacircular evaluator for JavaScript (syntactic forms as derived components): 元循环求值器 (语法形式作为派生组件) +metacircular evaluator for JavaScript (syntax of evaluated language): 元循环求值器 (被求值语言的语法) +metacircular evaluator for JavaScript (tail recursion and): 元循环求值器 (与尾递归) +metacircular evaluator for JavaScript (undefined): 元循环求值器 (undefined) +metacircular evaluator for JavaScript (value of program at top level): 元循环求值器 (顶层程序值) +metacircular evaluator for JavaScript (while loop): 元循环求值器 (while 循环) +metalinguistic abstraction: 元语言抽象 +MicroPlanner: MicroPlanner (编程语言名) +midpoint_segment: midpoint_segment (线段中点函数) +Miller, Gary L.: Miller, Gary L. (人名) +Miller, James S.: Miller, James S. (人名) +Miller–Rabin test for primality: Miller-Rabin 素性测试 +Milner, Robin: Milner, Robin (人名) +Minsky, Marvin Lee: Minsky, Marvin Lee (人名) +Miranda: Miranda (编程语言名) +MIT: MIT (麻省理工学院) +MIT (early history of): MIT (早期历史) +MIT (Research Laboratory of Electronics): MIT (电子学研究实验室) +ML: ML (编程语言名) +mobile: 悬挂物 (玩具) +Mocha: Mocha (JavaScript 早期名称) +modeling: 建模 +modeling (as a design strategy): 建模 (作为设计策略) +modeling (in science and engineering): 建模 (在科学与工程中) +models of evaluation: 求值模型 +modified registers: 被修改的寄存器 +modifies_register: modifies_register (检查寄存器是否被修改) +modularity: 模块性 +modularity (along object boundaries): 模块性 (沿对象边界) +modularity (functional programs vs. objects): 模块性 (函数式程序 vs. 对象) +modularity (hiding principle): 模块性 (隐藏原则) +modularity (streams and): 模块性 (与流) +modularity (through dispatching on type): 模块性 (通过类型分派) +modularity (through infinite streams): 模块性 (通过无限流) +modularity (through modeling with objects): 模块性 (通过对象建模) +modulo n: 模 n +modus ponens: 肯定前件 (推理规则) +money, changing: 换零钱 +monitored function: 被监控的函数 +monte_carlo: monte_carlo (蒙特卡洛模拟函数) +monte_carlo (infinite stream): monte_carlo (无限流版本) +Monte Carlo integration: 蒙特卡洛积分 +Monte Carlo simulation: 蒙特卡洛模拟 +Monte Carlo simulation (stream formulation): 蒙特卡洛模拟 (流形式) +Moon, David A.: Moon, David A. (人名) +Morris, J. H.: Morris, J. H. (人名) +Morse code: 摩尔斯电码 +Mouse, Minnie and Mickey: 米妮和米奇老鼠 +mul (generic): mul (通用乘法) +mul (generic, used for polynomial coefficients): mul (通用乘法, 用于多项式系数) +mul_complex: mul_complex (复数乘法函数) +mul_interval: mul_interval (区间乘法函数) +mul_interval (more efficient version): mul_interval (更高效版本) +mul_poly: mul_poly (多项式乘法函数) +mul_rat: mul_rat (有理数乘法函数) +mul_series: mul_series (幂级数乘法) +mul_streams: mul_streams (流乘法函数) +mul_terms: mul_terms (项乘法函数) +Multics time-sharing system: Multics 分时系统 +multiplicand: multiplicand (被乘数选择器) +multiplication by Russian peasant method: 俄罗斯农民乘法 +multiplier: multiplier (乘数选择器 / 乘法器约束) +multiplier (primitive constraint): multiplier (乘法器原语约束) +multiplier (selector): multiplier (乘数选择器) +Munro, Ian: Munro, Ian (人名) +mutable data objects: 可变数据对象 +mutable data objects (functional representation of): 可变数据对象 (函数表示) +mutable data objects (implemented with assignment): 可变数据对象 (用赋值实现) +mutable data objects (list structure): 可变数据对象 (列表结构) +mutable data objects (pairs): 可变数据对象 (序对) +mutable data objects (shared data): 可变数据对象 (共享数据) +mutator: 修改器 / 突变器 +mutex: 互斥锁 / 互斥量 +mutual exclusion: 互斥 +mutual recursion: 相互递归 +mystery: mystery (神秘函数 - 列表操作) + +N + +name: 名称 / 名字 / 标识符 +name (bound): 名称 (绑定) +name (encapsulated): 名称 (封装) +name (free): 名称 (自由) +name (of a function): 名称 (函数的) +name (of a parameter): 名称 (参数的) +name (parsing of): 名称 (解析) +name (scope of): 名称 (作用域) +name (unbound): 名称 (未绑定) +name (value of): 名称 (值) +naming: 命名 +naming (of computational objects): 命名 (计算对象) +naming (of functions): 命名 (函数) +naming conventions: 命名约定 +naming conventions ($ for pattern variables): 命名约定 ($ 用于模式变量) +naming conventions (is_ for predicates): 命名约定 (is_ 用于谓词) +naming conventions (snake case): 命名约定 (蛇形命名法) +NaN: NaN (非数值) +native language of machine: 机器的本地语言 +natural language parsing: 自然语言解析 +needed registers: 需要的寄存器 +needs_register: needs_register (检查是否需要寄存器) +negate: negate (否定函数 - 查询) +negated_query: negated_query (否定查询选择器) +negation: 否定 / 取反 +negation (logical (!)): 否定 (逻辑 !) +negation (numeric (-)): 否定 (数值 -) +negation as failure: 失败即否定 (逻辑编程) +nested declaration(s): 嵌套声明 +nested mapping(s): 嵌套映射 +nested operator combinations: 嵌套运算符组合 +Netscape Communications Corporation: Netscape 通信公司 +Netscape Navigator: Netscape Navigator (浏览器) +new register: new 寄存器 (垃圾回收) +new_heads register: new_heads 寄存器 (垃圾回收) +new_tails register: new_tails 寄存器 (垃圾回收) +Newton's method: 牛顿法 +Newton's method (for cube roots): 牛顿法 (用于立方根) +Newton's method (for differentiable functions): 牛顿法 (用于可微函数) +Newton's method (half-interval method vs.): 牛顿法 (与半区间法比较) +Newton's method (for square roots): 牛顿法 (用于平方根) +newtons_method: newtons_method (牛顿法函数) +newton_transform: newton_transform (牛顿变换函数) +new_withdraw: new_withdraw (新取款函数) +next (linkage descriptor): next (链接描述符) +next_to_in (rules): next_to_in (规则: 相邻) +node of a tree: 树的节点 +noncomputable: 不可计算的 +nondeterminism, in behavior of concurrent programs: 非确定性 (并发程序行为) +nondeterministic choice point: 非确定性选择点 +nondeterministic computing: 非确定性计算 +nondeterministic evaluator: 非确定性求值器 +nondeterministic evaluator (order of argument evaluation): 非确定性求值器 (参数求值顺序) +nondeterministic programming vs. JavaScript programming: 非确定性编程 vs. JavaScript 编程 +nondeterministic programs: 非确定性程序 +nondeterministic programs (logic puzzles): 非确定性程序 (逻辑谜题) +nondeterministic programs (pairs with prime sums): 非确定性程序 (素数和配对) +nondeterministic programs (parsing natural language): 非确定性程序 (解析自然语言) +nondeterministic programs (Pythagorean triples): 非确定性程序 (勾股数) +non-strict: 非严格求值 +normal-order evaluation: 正则序求值 +normal-order evaluation (applicative order vs.): 正则序求值 (与应用序比较) +normal-order evaluation (of conditional expressions): 正则序求值 (条件表达式) +normal-order evaluation (delayed evaluation and): 正则序求值 (与延迟求值) +normal-order evaluation (in explicit-control evaluator): 正则序求值 (在显式控制求值器中) +normal-order evaluator: 正则序求值器 +not (query language): not (查询语言中的非) +not (query language, evaluation of): not (查询语言中非的求值) +notation in this book: 本书中的表示法 +notation in this book (box notation for data): 本书中的表示法 (数据的方框表示) +notation in this book (italic symbols in expression syntax): 本书中的表示法 (表达式语法中的斜体符号) +notation in this book (list notation for data): 本书中的表示法 (数据的列表表示) +notation in this book (slanted characters for interpreter response): 本书中的表示法 (解释器响应的斜体字符) +nouns: nouns (名词列表) +nth root, as fixed point: n 次方根 (作为不动点) +null (keyword): null (关键字/值) +null (keyword, as empty list): null (作为空列表) +null (keyword, as end-of-list marker): null (作为列表结束标记) +null (keyword, recognizing with is_null): null (用 is_null 识别) +number(s): 数 / 数字 / 数值 +number(s) (bignum): 数 (大数) +number(s) (comparison of): 数 (比较) +number(s) (equality of): 数 (相等性) +number(s) (in generic arithmetic system): 数 (在通用算术系统中) +number(s) (integer vs. real number): 数 (整数 vs. 实数) +number(s) (in JavaScript): 数 (在 JavaScript 中) +number_equal: number_equal (数值相等性检查函数) +number theory: 数论 +numer: numer (分子选择器) +numer (axiom for): numer (公理) +numer (reducing to lowest terms): numer (化简到最简形式) +numerical analysis: 数值分析 +numerical analyst: 数值分析家 +numerical data: 数值数据 + +O + +object(s): 对象 +object(s) (benefits of modeling with): 对象 (用对象建模的好处) +object(s) (with time-varying state): 对象 (带时变状态) +object-oriented programming languages: 面向对象编程语言 +object program: 目标程序 +Ocaml: Ocaml (编程语言名) +office_move: office_move (办公室搬迁谜题函数) +old register: old 寄存器 (垃圾回收) +oldht register: oldht 寄存器 (垃圾回收) +ones (infinite stream): ones (全 1 无限流) +ones (infinite stream, lazy-list version): ones (全 1 无限流, 惰性列表版本) +op (in register machine): op (寄存器机中的操作符) +op (in register machine, simulating): op (模拟操作符) +open coding of primitives: 原语的开放编码 +operands of a combination: 组合式的操作数 +operation: 操作 / 运算 +operation (cross-type): 操作 (跨类型) +operation (generic): 操作 (通用) +operation (in register machine): 操作 (在寄存器机中) +operation-and-type table: 操作与类型表 +operation-and-type table (assignment needed for): 操作与类型表 (需要赋值) +operation-and-type table (implementing): 操作与类型表 (实现) +operation_exp_op: operation_exp_op (操作表达式的操作符选择器) +operation_exp_operands: operation_exp_operands (操作表达式的操作数选择器) +operator combination: 运算符组合 / 组合式 +operator combination (as function application): 运算符组合 (作为函数应用) +operator combination (as derived component): 运算符组合 (作为派生组件) +operator combination (evaluation of): 运算符组合 (求值) +operator combination (parsing of): 运算符组合 (解析) +operator combination (as a tree): 运算符组合 (作为树) +operator_combination_to_application: operator_combination_to_application (运算符组合转应用函数) +operator of a combination: 组合式的运算符 +operator precedence: 运算符优先级 +operators: 运算符 +operators (+ (for numeric addition)): 运算符 (+ 数值加法) +operators (+ (for string concatenation)): 运算符 (+ 字符串连接) +operators (- (numeric negation operator)): 运算符 (- 数值取反) +operators (- (numeric subtraction operator)): 运算符 (- 数值减法) +operators (* (multiplication)): 运算符 (* 乘法) +operators (/ (division)): 运算符 (/ 除法) +operators (% (remainder)): 运算符 (% 求余) +operators (! (logical negation)): 运算符 (! 逻辑非) +operators (!== (for numeric comparison)): 运算符 (!== 数值比较) +operators (!== (for string comparison)): 运算符 (!== 字符串比较) +operators (=== (for nonprimitive values)): 运算符 (=== 非原语值) +operators (=== (for numeric comparison)): 运算符 (=== 数值比较) +operators (=== (for string comparison)): 运算符 (=== 字符串比较) +operators (< (numeric comparison)): 运算符 (< 数值比较) +operators (<= (numeric comparison)): 运算符 (<= 数值比较) +operators (> (numeric comparison)): 运算符 (> 数值比较) +operators (>= (numeric comparison)): 运算符 (>= 数值比较) +optimality: 最优性 +optimality (of Horner's rule): 最优性 (霍纳规则) +optimality (of Huffman code): 最优性 (霍夫曼编码) +or (query language): or (查询语言中的或) +or (query language, evaluation of): or (查询语言中或的求值) +Oracle Corporation: 甲骨文公司 +order: order (阶选择器 - 多项式) +ordered-list representation of sets: 集合的有序列表表示 +order notation: 阶记号 (大 O, Theta 等) +order of evaluation: 求值顺序 +order of evaluation (assignment and): 求值顺序 (与赋值) +order of evaluation (in compiler): 求值顺序 (在编译器中) +order of evaluation (in explicit-control evaluator): 求值顺序 (在显式控制求值器中) +order of evaluation (in metacircular evaluator): 求值顺序 (在元循环求值器中) +order of evaluation (in JavaScript): 求值顺序 (在 JavaScript 中) +order of evaluation (in metacircular evaluator, other ref): 求值顺序 (在元循环求值器中, 另一处) +order of events: 事件顺序 +order of events (decoupling apparent from actual): 事件顺序 (解耦表观与实际) +order of events (indeterminacy in concurrent systems): 事件顺序 (并发系统中的不确定性) +order of growth: 增长阶 +order of growth (linear iterative process): 增长阶 (线性迭代过程) +order of growth (linear recursive process): 增长阶 (线性递归过程) +order of growth (logarithmic): 增长阶 (对数) +order of growth (tree-recursive process): 增长阶 (树形递归过程) +order of subexpression evaluation: 子表达式求值顺序 +ordinary numbers (in generic arithmetic system): 普通数 (在通用算术系统中) +or-gate: 或门 +or_gate: or_gate (或门函数) +origin_frame: origin_frame (原点框架) +Ostrowski, A. M.: Ostrowski, A. M. (人名) +outranked_by (rule): outranked_by (规则: 级别低于) +overloaded operator +: 重载运算符 + + +P + +P operation on semaphore: P 操作 (信号量) +package: 包 / 软件包 +package (complex-number): 包 (复数) +package (JavaScript-number): 包 (JavaScript 数字) +package (polar representation): 包 (极坐标表示) +package (polynomial): 包 (多项式) +package (rational-number): 包 (有理数) +package (rectangular representation): 包 (直角坐标表示) +painter(s): 画家 (图片语言) +painter(s) (higher-order operations): 画家 (高阶操作) +painter(s) (operations): 画家 (操作) +painter(s) (represented as functions): 画家 (表示为函数) +painter(s) (transforming and combining): 画家 (变换与组合) +pair (primitive function): pair (序对构造原语函数) +pair (primitive function, axiom for): pair (公理) +pair (primitive function, closure property of): pair (闭合性质) +pair (primitive function, functional implementation of): pair (函数式实现) +pair (primitive function, implemented with mutators): pair (用修改器实现) +pair (primitive function, implemented with vectors): pair (用向量实现) +pair (primitive function, as list operation): pair (作为列表操作) +pair(s): 序对 +pair(s) (axiomatic definition of): 序对 (公理化定义) +pair(s) (box-and-pointer notation for): 序对 (方框与指针表示法) +pair(s) (box notation for): 序对 (方框表示法) +pair(s) (functional representation of): 序对 (函数表示) +pair(s) (infinite stream of): 序对 (无限流) +pair(s) (lazy): 序对 (惰性) +pair(s) (mutable): 序对 (可变) +pair(s) (represented using vectors): 序对 (用向量表示) +pair(s) (used to represent sequence): 序对 (用于表示序列) +pair(s) (used to represent tree): 序对 (用于表示树) +pairs: pairs (序对流生成函数) +Pan, V. Y.: Pan, V. Y. (人名) +parallel_instruction_sequences: parallel_instruction_sequences (并行指令序列组合函数) +parameter passing: 参数传递 +parameters: 参数 +parameters (distinct from local names): 参数 (与局部名称区分) +parameters (duplicate): 参数 (重复) +parameters (names of): 参数 (名称) +parameters (scope of): 参数 (作用域) +parameters (as variables): 参数 (作为变量) +parentheses: 圆括号 / 括号 +parentheses (in function declaration): 圆括号 (在函数声明中) +parentheses (to group operator combinations): 圆括号 (用于组合运算符) +parentheses (around lambda expression): 圆括号 (包围 lambda 表达式) +parentheses (around parameters of lambda expression): 圆括号 (包围 lambda 参数) +parentheses (around predicate of conditional statement): 圆括号 (包围条件语句谓词) +parse: parse (解析函数) +parse (in query interpreter): parse (在查询解释器中) +parse_...: parse_... (系列解析函数 - 自然语言) +parsing JavaScript: 解析 JavaScript +parsing JavaScript (&& (logical conjunction)): 解析 JavaScript (&& 逻辑与) +parsing JavaScript (|| (logical disjunction)): 解析 JavaScript (|| 逻辑或) +parsing JavaScript (assignment): 解析 JavaScript (赋值) +parsing JavaScript (block): 解析 JavaScript (块) +parsing JavaScript (conditional expression): 解析 JavaScript (条件表达式) +parsing JavaScript (conditional statement): 解析 JavaScript (条件语句) +parsing JavaScript (constant declaration): 解析 JavaScript (常量声明) +parsing JavaScript (expression statement): 解析 JavaScript (表达式语句) +parsing JavaScript (function application): 解析 JavaScript (函数应用) +parsing JavaScript (function declaration): 解析 JavaScript (函数声明) +parsing JavaScript (lambda expression): 解析 JavaScript (lambda 表达式) +parsing JavaScript (literal expression): 解析 JavaScript (字面量表达式) +parsing JavaScript (name): 解析 JavaScript (名称) +parsing JavaScript (operator combination): 解析 JavaScript (运算符组合) +parsing JavaScript (return statement): 解析 JavaScript (返回语句) +parsing JavaScript (sequence of statements): 解析 JavaScript (语句序列) +parsing JavaScript (variable declaration): 解析 JavaScript (变量声明) +parsing natural language: 解析自然语言 +parsing natural language (real language understanding vs. toy parser): 解析自然语言 (真实语言理解 vs. 玩具解析器) +partial_sums: partial_sums (部分和流函数) +Pascal, Blaise: Pascal, Blaise (人名) +Pascal's triangle: 帕斯卡三角形 (杨辉三角) +Pascal: Pascal (编程语言名) +Pascal (lack of higher-order functions in): Pascal (缺少高阶函数) +password-protected bank account: 密码保护的银行账户 +pattern: 模式 +pattern_match: pattern_match (模式匹配函数) +pattern matching: 模式匹配 +pattern matching (implementation): 模式匹配 (实现) +pattern matching (unification vs.): 模式匹配 (与合一比较) +pattern variable: 模式变量 +pattern variable (representation of): 模式变量 (表示) +pc register: pc 寄存器 (程序计数器) +perform (in register machine): perform (寄存器机中的 perform 指令) +perform (in register machine, instruction constructor): perform (perform 指令构造器) +perform (in register machine, simulating): perform (模拟 perform 指令) +perform_action: perform_action (执行动作选择器) +Perlis, Alan J.: Perlis, Alan J. (人名) +Perlis, Alan J. (quips by): Perlis, Alan J. (引言) +permutations of a set: 集合的排列 +permutations: permutations (排列函数) +Peter, Paul and Mary: Peter, Paul and Mary (歌曲组合名) +Phillips, Hubert: Phillips, Hubert (人名) +pi (pi): 圆周率 pi +pi (pi, approximation with half-interval method): pi (用半区间法近似) +pi (pi, approximation with Monte Carlo integration): pi (用蒙特卡洛积分近似) +pi (pi, Dirichlet estimate for): pi (狄利克雷估计) +pi (pi, Leibniz's series for): pi (莱布尼茨级数) +pi (pi, stream of approximations): pi (近似值流) +pi (pi, Wallis's formula for): pi (沃利斯公式) +picture language: 图片语言 +Pingala, Áchárya: Pingala, Áchárya (人名) +pipelining: 流水线 +pi_stream: pi_stream (pi 近似值流) +pi_sum: pi_sum (pi 级数求和函数) +pi_sum (with higher-order functions): pi_sum (带高阶函数版本) +pi_sum (with lambda expression): pi_sum (带 lambda 表达式版本) +Planner: Planner (编程语言名) +point, represented as a pair: 点 (表示为序对) +pointer: 指针 +pointer (in box-and-pointer notation): 指针 (在方框与指针表示法中) +pointer (typed): 指针 (带类型) +polar package: polar 包 (极坐标包) +poly: 多项式 (缩写/类型) +polymorphic types: 多态类型 +polynomial package: polynomial 包 (多项式包) +polynomial(s): 多项式 +polynomial(s) (canonical form): 多项式 (规范形式) +polynomial(s) (dense): 多项式 (稠密) +polynomial(s) (evaluating with Horner's rule): 多项式 (用霍纳规则求值) +polynomial(s) (hierarchy of types): 多项式 (类型层次) +polynomial(s) (indeterminate of): 多项式 (不定元) +polynomial(s) (sparse): 多项式 (稀疏) +polynomial(s) (univariate): 多项式 (单变量) +polynomial arithmetic: 多项式算术 +polynomial arithmetic (addition): 多项式算术 (加法) +polynomial arithmetic (division): 多项式算术 (除法) +polynomial arithmetic (Euclid's Algorithm): 多项式算术 (欧几里得算法) +polynomial arithmetic (greatest common divisor): 多项式算术 (最大公约数) +polynomial arithmetic (interfaced to generic arithmetic system): 多项式算术 (与通用算术系统接口) +polynomial arithmetic (multiplication): 多项式算术 (乘法) +polynomial arithmetic (probabilistic algorithm for GCD): 多项式算术 (GCD 的概率算法) +polynomial arithmetic (rational functions): 多项式算术 (有理函数) +polynomial arithmetic (subtraction): 多项式算术 (减法) +pop: pop (出栈操作) +porting a language: 移植语言 +PowerPC: PowerPC (处理器架构) +power series, as stream: 幂级数 (作为流) +power series, as stream (adding): 幂级数 (加法) +power series, as stream (dividing): 幂级数 (除法) +power series, as stream (integrating): 幂级数 (积分) +power series, as stream (multiplying): 幂级数 (乘法) +precedence: 优先级 +precedence (of conditional expression): 优先级 (条件表达式的) +precedence (of lambda expression): 优先级 (lambda 表达式的) +precedence (of operators): 优先级 (运算符的) +precedence (of unary operators): 优先级 (一元运算符的) +predicate: 谓词 +predicate (of clause): 谓词 (子句的) +predicate (of conditional expression): 谓词 (条件表达式的) +predicate (of conditional statement): 谓词 (条件语句的) +predicate (naming convention for): 谓词 (命名约定) +prefix code: 前缀码 +prefix notation: 前缀表示法 +prefix notation (infix notation vs.): 前缀表示法 (与中缀表示法比较) +prefix operator: 前缀运算符 +prepositions: prepositions (介词列表) +preserving: preserving (保护寄存器的高阶函数) +pretty-printing: 漂亮打印 / 格式化输出 +prime number(s): 素数 / 质数 +prime number(s) (cryptography and): 素数 (与密码学) +prime number(s) (Eratosthenes's sieve for): 素数 (埃拉托斯特尼筛法) +prime number(s) (Fermat test for): 素数 (费马测试) +prime number(s) (infinite stream of): 素数 (无限流) +prime number(s) (Miller–Rabin test for): 素数 (Miller-Rabin 测试) +prime number(s) (testing for): 素数 (测试) +primes (infinite stream): primes (素数无限流) +primes (infinite stream, implicit definition): primes (素数无限流, 隐式定义) +prime_sum_pair: prime_sum_pair (素数和配对检查函数) +prime_sum_pairs: prime_sum_pairs (素数和配对列表函数) +prime_sum_pairs (infinite stream): prime_sum_pairs (无限流版本) +primitive_apply: primitive_apply (应用原语函数 - 显式控制) +primitive constraints: 原语约束 +primitive expression: 原语表达式 +primitive expression (evaluation of): 原语表达式 (求值) +primitive expression (name of constant): 原语表达式 (常量名称) +primitive expression (number): 原语表达式 (数字) +primitive function: 原语函数 +primitive_function_objects: primitive_function_objects (原语函数对象列表) +primitive functions: 原语函数 (列表) +primitive functions (apply): 原语函数 (apply) +primitive functions (display): 原语函数 (display) +primitive functions (error): 原语函数 (error) +primitive functions (eval): 原语函数 (eval) +primitive functions (get_time): 原语函数 (get_time) +primitive functions (head): 原语函数 (head) +primitive functions (is_null): 原语函数 (is_null) +primitive functions (is_number): 原语函数 (is_number) +primitive functions (is_pair): 原语函数 (is_pair) +primitive functions (is_string): 原语函数 (is_string) +primitive functions (is_undefined): 原语函数 (is_undefined) +primitive functions (list): 原语函数 (list) +primitive functions (math_atan2): 原语函数 (math_atan2) +primitive functions (math_cos): 原语函数 (math_cos) +primitive functions (math_floor): 原语函数 (math_floor) +primitive functions (math_log): 原语函数 (math_log) +primitive functions (math_max): 原语函数 (math_max) +primitive functions (math_min): 原语函数 (math_min) +primitive functions (math_random): 原语函数 (math_random) +primitive functions (math_round): 原语函数 (math_round) +primitive functions (math_sin): 原语函数 (math_sin) +primitive functions (math_trunc): 原语函数 (math_trunc) +primitive functions (pair): 原语函数 (pair) +primitive functions (prompt): 原语函数 (prompt) +primitive functions (set_head): 原语函数 (set_head) +primitive functions (set_tail): 原语函数 (set_tail) +primitive functions (stringify): 原语函数 (stringify) +primitive functions (tail): 原语函数 (tail) +primitive functions (vector_ref): 原语函数 (vector_ref) +primitive functions (vector_set): 原语函数 (vector_set) +primitive_function_symbols: primitive_function_symbols (原语函数符号列表) +primitive_implementation: primitive_implementation (原语实现选择器) +primitive query: 原语查询 / 简单查询 +principle of least commitment: 最少承诺原则 +print_point: print_point (打印点函数) +print_queue: print_queue (打印队列函数) +print_rat: print_rat (打印有理数函数) +print_result: print_result (打印结果函数) +print_result (monitored-stack version): print_result (带监控栈版本) +print_stack_statistics operation in register machine: print_stack_statistics 操作 (寄存器机中) +probabilistic algorithm: 概率算法 +probe: probe (探针函数 - 约束/电路) +probe (in constraint system): probe (在约束系统中) +probe (in digital-circuit simulator): probe (在数字电路模拟器中) +process: 过程 +process (iterative): 过程 (迭代) +process (linear iterative): 过程 (线性迭代) +process (linear recursive): 过程 (线性递归) +process (local evolution of): 过程 (局部演化) +process (order of growth of): 过程 (增长阶) +process (recursive): 过程 (递归) +process (resources required by): 过程 (所需资源) +process (shape of): 过程 (形态) +process (tree-recursive): 过程 (树形递归) +product: product (乘积函数) +product (as accumulation): product (作为累积) +program: 程序 +program (as abstract machine): 程序 (作为抽象机) +program (comments in): 程序 (注释) +program (as data): 程序 (作为数据) +program (incremental development of): 程序 (增量开发) +program (structured with subroutines): 程序 (用子例程构建) +program (structure of): 程序 (结构) +program (value of): 程序 (值) +program counter: 程序计数器 +program environment: 程序环境 +programming: 编程 +programming (data-directed): 编程 (数据导向) +programming (demand-driven): 编程 (需求驱动) +programming (elements of): 编程 (要素) +programming (functional): 编程 (函数式) +programming (imperative): 编程 (命令式) +programming (odious style): 编程 (糟糕风格) +programming language: 编程语言 +programming language (design of): 编程语言 (设计) +programming language (functional): 编程语言 (函数式) +programming language (logic): 编程语言 (逻辑) +programming language (object-oriented): 编程语言 (面向对象) +programming language (statically typed): 编程语言 (静态类型) +programming language (very high-level): 编程语言 (超高级) +Prolog: Prolog (编程语言名) +promise to evaluate: 求值承诺 (延迟求值) +prompt (primitive function): prompt (提示原语函数) +prompt operation in register machine: prompt 操作 (寄存器机中) +prompts: 提示符 +prompts (explicit-control evaluator): 提示符 (显式控制求值器) +prompts (lazy evaluator): 提示符 (惰性求值器) +prompts (metacircular evaluator): 提示符 (元循环求值器) +prompts (nondeterministic evaluator): 提示符 (非确定性求值器) +prompts (query interpreter): 提示符 (查询解释器) +propagate: propagate (传播函数 - 模拟) +propagation of constraints: 约束传播 +proving programs correct: 程序正确性证明 +pseudodivision of polynomials: 多项式伪除法 +pseudo-random sequence: 伪随机序列 +pseudoremainder of polynomials: 多项式伪余数 +push: push (入栈操作) +push_marker_to_stack (in register machine): push_marker_to_stack (标记入栈 - 寄存器机) +put: put (存放函数 - 表操作) +puzzles: 谜题 +puzzles (eight-queens puzzle): 谜题 (八皇后) +puzzles (logic puzzles): 谜题 (逻辑) +Pythagorean triples: 勾股数 / 毕达哥拉斯三元组 +Pythagorean triples (with nondeterministic programs): 勾股数 (用非确定性程序) +Pythagorean triples (with streams): 勾股数 (用流) +Python: Python (编程语言名) +Python (recursive functions in): Python (递归函数) + +Q + +quantum mechanics: 量子力学 +queens: queens (八皇后问题求解函数) +query: 查询 +query (compound): 查询 (复合) +query (simple): 查询 (简单) +query_driver_loop: query_driver_loop (查询驱动循环) +query interpreter: 查询解释器 +query interpreter (adding rule or assertion): 查询解释器 (添加规则或断言) +query interpreter (compound query): 查询解释器 (复合查询) +query interpreter (data base): 查询解释器 (数据库) +query interpreter (driver loop): 查询解释器 (驱动循环) +query interpreter (environment structure in): 查询解释器 (环境结构) +query interpreter (frame): 查询解释器 (帧) +query interpreter (improvements to): 查询解释器 (改进) +query interpreter (infinite loops): 查询解释器 (无限循环) +query interpreter (instantiation): 查询解释器 (实例化) +query interpreter (JavaScript interpreter vs.): 查询解释器 (与 JavaScript 解释器比较) +query interpreter (as nondeterministic program): 查询解释器 (作为非确定性程序) +query interpreter (overview): 查询解释器 (概述) +query interpreter (pattern matching): 查询解释器 (模式匹配) +query interpreter (pattern-variable representation): 查询解释器 (模式变量表示) +query interpreter (problems with not and javascript_predicate): 查询解释器 (not 和 javascript_predicate 的问题) +query interpreter (query evaluator): 查询解释器 (查询求值器) +query interpreter (rule): 查询解释器 (规则) +query interpreter (simple query): 查询解释器 (简单查询) +query interpreter (stream operations): 查询解释器 (流操作) +query interpreter (streams of frames): 查询解释器 (帧流) +query interpreter (syntax of query language): 查询解释器 (查询语言语法) +query interpreter (unification): 查询解释器 (合一) +query language: 查询语言 +query language (abstraction in): 查询语言 (抽象) +query language (compound query): 查询语言 (复合查询) +query language (data base): 查询语言 (数据库) +query language (equality testing in): 查询语言 (相等性测试) +query language (extensions to): 查询语言 (扩展) +query language (logical deductions): 查询语言 (逻辑推导) +query language (mathematical logic vs.): 查询语言 (与数理逻辑比较) +query language (rule): 查询语言 (规则) +query language (simple query): 查询语言 (简单查询) +query-language-specific representation: 查询语言特定表示 +query-language-specific representation (transforming JavaScript syntax into): 查询语言特定表示 (转换 JavaScript 语法到) +queue: 队列 +queue (double-ended): 队列 (双端) +queue (front of): 队列 (队头) +queue (functional implementation of): 队列 (函数式实现) +queue (operations on): 队列 (操作) +queue (rear of): 队列 (队尾) +queue (in simulation agenda): 队列 (在模拟议程中) +quotation marks: 引号 +quotation marks (back quotes): 引号 (反引号) +quotation marks (double): 引号 (双引号) +quotation marks (single): 引号 (单引号) + +R + +Rabin, Michael O.: Rabin, Michael O. (人名) +radicand: 被开方数 +Ramanujan, Srinivasa: Ramanujan, Srinivasa (人名) +Ramanujan numbers: 拉马努金数 (出租车数) +rand: rand (随机数生成器) +rand (with reset): rand (带重置) +random_in_range: random_in_range (范围内随机数函数) +random-number generator: 随机数生成器 +random-number generator (in Monte Carlo simulation): 随机数生成器 (在蒙特卡洛模拟中) +random-number generator (in primality testing): 随机数生成器 (在素性测试中) +random-number generator (with reset): 随机数生成器 (带重置) +random-number generator (with reset, stream version): 随机数生成器 (带重置, 流版本) +random_numbers (infinite stream): random_numbers (随机数无限流) +Raphael, Bertram: Raphael, Bertram (人名) +rational package: rational 包 (有理数包) +rational function: 有理函数 +rational function (reducing to lowest terms): 有理函数 (化简到最简形式) +rational number(s): 有理数 +rational number(s) (arithmetic operations on): 有理数 (算术操作) +rational number(s) (printing): 有理数 (打印) +rational number(s) (reducing to lowest terms): 有理数 (化简到最简形式) +rational number(s) (represented as pairs): 有理数 (表示为序对) +rational-number arithmetic: 有理数算术 +rational-number arithmetic (interfaced to generic arithmetic system): 有理数算术 (与通用算术系统接口) +rational-number arithmetic (need for compound data): 有理数算术 (复合数据需求) +rational tree: 有理树 (无限树) +Raymond, Eric: Raymond, Eric (人名) +RC circuit: RC 电路 +read_evaluate_print_loop: read_evaluate_print_loop (读取-求值-打印循环函数) +read-evaluate-print loop: 读取-求值-打印循环 (REPL) +real number: 实数 +real_part: real_part (实部选择器) +real_part (data-directed): real_part (数据导向版本) +real_part (polar representation): real_part (极坐标表示) +real_part (rectangular representation): real_part (直角坐标表示) +real_part (with tagged data): real_part (带标签数据版本) +real_part_polar: real_part_polar (极坐标实部函数) +real_part_rectangular: real_part_rectangular (直角坐标实部函数) +rear_ptr: rear_ptr (队尾指针) +receive function: receive 函数 (多值返回) +record, in a data base: 记录 (在数据库中) +rectangle, representing: 矩形 (表示) +rectangular package: rectangular 包 (直角坐标包) +recursion: 递归 +recursion (data-directed): 递归 (数据导向) +recursion (expressing complicated process): 递归 (表达复杂过程) +recursion (mutual): 递归 (相互) +recursion (in rules): 递归 (在规则中) +recursion (in working with trees): 递归 (在处理树时) +recursion theory: 递归论 +recursive function: 递归函数 +recursive function (recursive function declaration): 递归函数 (递归函数声明) +recursive function (recursive process vs.): 递归函数 (与递归过程比较) +recursive function (specifying without declaration): 递归函数 (无声明指定) +recursive process: 递归过程 +recursive process (iterative process vs.): 递归过程 (与迭代过程比较) +recursive process (linear): 递归过程 (线性) +recursive process (recursive function vs.): 递归过程 (与递归函数比较) +recursive process (register machine for): 递归过程 (寄存器机) +recursive process (tree): 递归过程 (树形) +red-black tree: 红黑树 +reducing to lowest terms: 化简到最简形式 +Rees, Jonathan A.: Rees, Jonathan A. (人名) +referential transparency: 引用透明性 +reg (in register machine): reg (寄存器机中的寄存器操作数) +reg (in register machine, simulating): reg (模拟寄存器操作数) +register(s): 寄存器 +register(s) (representing): 寄存器 (表示) +register(s) (tracing): 寄存器 (跟踪) +register_exp_reg: register_exp_reg (寄存器表达式的寄存器选择器) +register machine: 寄存器机 +register machine (actions): 寄存器机 (动作) +register machine (controller): 寄存器机 (控制器) +register machine (controller diagram): 寄存器机 (控制器图) +register machine (data-path diagram): 寄存器机 (数据通路图) +register machine (data paths): 寄存器机 (数据通路) +register machine (design of): 寄存器机 (设计) +register machine (language for describing): 寄存器机 (描述语言) +register machine (monitoring performance): 寄存器机 (监控性能) +register machine (simulator): 寄存器机 (模拟器) +register machine (stack): 寄存器机 (栈) +register machine (subroutine): 寄存器机 (子例程) +register machine (test operation): 寄存器机 (测试操作) +register-machine language: 寄存器机语言 +register-machine language (assign): 寄存器机语言 (assign) +register-machine language (branch): 寄存器机语言 (branch) +register-machine language (constant): 寄存器机语言 (constant) +register-machine language (entry point): 寄存器机语言 (入口点) +register-machine language (go_to): 寄存器机语言 (go_to) +register-machine language (instructions): 寄存器机语言 (指令) +register-machine language (label): 寄存器机语言 (label) +register-machine language (label, name): 寄存器机语言 (标号名称) +register-machine language (op): 寄存器机语言 (op) +register-machine language (perform): 寄存器机语言 (perform) +register-machine language (push_marker_to_stack): 寄存器机语言 (push_marker_to_stack) +register-machine language (reg): 寄存器机语言 (reg) +register-machine language (restore): 寄存器机语言 (restore) +register-machine language (revert_stack_to_marker): 寄存器机语言 (revert_stack_to_marker) +register-machine language (save): 寄存器机语言 (save) +register-machine language (test): 寄存器机语言 (test) +register-machine simulator: 寄存器机模拟器 +registers_modified: registers_modified (被修改寄存器列表选择器) +registers_needed: registers_needed (所需寄存器列表选择器) +register table, in simulator: 寄存器表 (在模拟器中) +relations, computing in terms of: 关系 (基于关系计算) +relatively prime: 互质 / 互素 +relativity, theory of: 相对论 +release a mutex: 释放互斥锁 +remainder: 余数 / 余项 +remainder (after integer division): 余数 (整数除法后) +remainder (modulo n): 余数 (模 n) +remainder_terms: remainder_terms (多项式除法的余项) +remove: remove (移除函数) +remove_first_agenda_item: remove_first_agenda_item (移除首个议程项函数) +rename_variables_in: rename_variables_in (重命名变量函数) +require: require (要求函数 - 非确定性) +require (as a syntactic form): require (作为语法形式) +resistance: 电阻 +resistance (formula for parallel resistors): 电阻 (并联电阻公式) +resistance (tolerance of resistors): 电阻 (电阻容差) +resolution, Horn-clause: 归结 (霍恩子句) +resolution principle: 归结原理 +restore (in register machine): restore (寄存器机中的 restore 指令) +restore (in register machine, implementing): restore (实现) +restore (in register machine, instruction constructor): restore (restore 指令构造器) +restore (in register machine, simulating): restore (模拟 restore 指令) +rest parameter and spread syntax: 剩余参数与展开语法 +rest_segments: rest_segments (剩余段选择器) +rest_statements: rest_statements (剩余语句选择器) +rest_terms: rest_terms (剩余项选择器) +retry: retry (重试函数 - 非确定性) +return (linkage descriptor): return (链接描述符) +return (keyword): return (关键字) +return_expression: return_expression (返回表达式选择器) +returning multiple values: 返回多个值 +return statement: 返回语句 +return statement (handling in compiler): 返回语句 (在编译器中处理) +return statement (handling in explicit-control evaluator): 返回语句 (在显式控制求值器中处理) +return statement (handling in metacircular evaluator): 返回语句 (在元循环求值器中处理) +return statement (parsing of): 返回语句 (解析) +return statement (tail recursion and): 返回语句 (与尾递归) +return_undefined: return_undefined (返回 undefined 函数) +return value: 返回值 +return value (representation in metacircular evaluator): 返回值 (在元循环求值器中表示) +return value (undefined as): 返回值 (undefined 作为) +return_value_content: return_value_content (返回值内容选择器) +Reuter, Andreas: Reuter, Andreas (人名) +reverse: reverse (反转列表函数) +reverse (as folding): reverse (作为折叠) +reverse (rules): reverse (规则版本) +revert_stack_to_marker (in register machine): revert_stack_to_marker (栈恢复到标记 - 寄存器机) +Rhind Papyrus: 莱茵德纸草书 +right-associative: 右结合 +right_branch: right_branch (右分支选择器) +right_split: right_split (右分裂函数 - 图片语言) +ripple-carry adder: 行波进位加法器 +Rivest, Ronald L.: Rivest, Ronald L. (人名) +RLC circuit: RLC 电路 +Robinson, J. A.: Robinson, J. A. (人名) +robustness: 鲁棒性 / 健壮性 +rock songs, 1950s: 摇滚歌曲 (1950年代) +Rogers, William Barton: Rogers, William Barton (人名) +root register: root 寄存器 (垃圾回收) +roots of equation: 方程根 +rotate90: rotate90 (旋转90度函数 - 图片语言) +roundoff error: 舍入误差 +Rozas, Guillermo Juan: Rozas, Guillermo Juan (人名) +RSA algorithm: RSA 算法 +rule (query language): 规则 (查询语言) +rule (query language, applying): 规则 (应用) +rule (query language, without body): 规则 (无体) +rule_body: rule_body (规则体选择器) +Runkle, John Daniel: Runkle, John Daniel (人名) +Russian peasant method of multiplication: 俄罗斯农民乘法 + +S + +same (rule): same (规则: 相同) +sameness and change: 同一性与变化 +sameness and change (meaning of): 同一性与变化 (含义) +sameness and change (shared data and): 同一性与变化 (与共享数据) +satisfy a compound query: 满足复合查询 +satisfy a pattern (simple query): 满足模式 (简单查询) +save (in register machine): save (寄存器机中的 save 指令) +save (in register machine, implementing): save (实现) +save (in register machine, instruction constructor): save (save 指令构造器) +save (in register machine, simulating): save (模拟 save 指令) +scale_list: scale_list (列表缩放函数) +scale_stream: scale_stream (流缩放函数) +scale_tree: scale_tree (树缩放函数) +scale_vect: scale_vect (向量缩放函数) +scan register: scan 寄存器 (垃圾回收) +scanning out declarations: 扫描出声明 +scanning out declarations (in compiler): 扫描出声明 (在编译器中) +scanning out declarations (in explicit-control evaluator): 扫描出声明 (在显式控制求值器中) +scanning out declarations (in metacircular evaluator): 扫描出声明 (在元循环求值器中) +scanning out declarations (sequential declaration processing vs.): 扫描出声明 (与顺序声明处理比较) +scan_out_declarations: scan_out_declarations (扫描出声明函数) +Scheme: Scheme (编程语言名) +Scheme (evaluators written in): Scheme (用 Scheme 编写的求值器) +Scheme (integrated-circuit implementation of): Scheme (集成电路实现) +Scheme (as JavaScript precursor): Scheme (作为 JavaScript 前身) +Scheme (let* in): Scheme (let*) +Scheme (nondeterministic extension of): Scheme (非确定性扩展) +Scheme (tail recursion in): Scheme (尾递归) +Scheme (use of "lambda" in): Scheme ("lambda" 的使用) +Scheme (Y operator written in): Scheme (用 Scheme 写的 Y 算子) +Scheme chip: Scheme 芯片 +Schmidt, Eric: Schmidt, Eric (人名) +scope of a name: 名称的作用域 +scope of a name (function's parameters): 名称的作用域 (函数参数) +scope of a name (internal declaration): 名称的作用域 (内部声明) +search: 搜索 +search (of binary tree): 搜索 (二叉树) +search (depth-first): 搜索 (深度优先) +search (systematic): 搜索 (系统性) +search: search (搜索函数 - 半区间法) +segment_queue: segment_queue (段队列选择器) +segments: segments (段列表选择器) +segments_to_painter: segments_to_painter (线段转画家函数) +segment_time: segment_time (段时间选择器) +selector: 选择器 / 选择函数 +selector (as abstraction barrier): 选择器 (作为抽象屏障) +selector (generic): 选择器 (通用) +Self: Self (编程语言名) +semaphore: 信号量 +semaphore (of size n): 信号量 (大小为 n) +semicolon (;): 分号 +semicolon (; cancer of): 分号 (分号癌) +semicolon (; ending statement): 分号 (结束语句) +separator code: 分隔符编码 +sequence(s): 序列 +sequence(s) (as conventional interface): 序列 (作为约定接口) +sequence(s) (as source of modularity): 序列 (作为模块性来源) +sequence(s) (operations on): 序列 (操作) +sequence(s) (represented by pairs): 序列 (用序对表示) +sequence accelerator: 序列加速器 +sequence of statements: 语句序列 +sequence of statements (in block): 语句序列 (在块中) +sequence of statements (in conditional statement): 语句序列 (在条件语句中) +sequence of statements (in function body): 语句序列 (在函数体中) +sequence of statements (as body of lambda expression): 语句序列 (作为 lambda 表达式体) +sequence of statements (parsing of): 语句序列 (解析) +sequential declaration processing vs. scanning out: 顺序声明处理 vs. 扫描出 +serialized_exchange: serialized_exchange (序列化交换函数) +serialized_exchange (with deadlock avoidance): serialized_exchange (带死锁避免) +serializer: 序列化器 +serializer (implementing): 序列化器 (实现) +serializer (with multiple shared resources): 序列化器 (带多个共享资源) +series, summation of: 级数求和 +series, summation of (accelerating sequence of approximations): 级数求和 (加速近似序列) +series, summation of (with streams): 级数求和 (用流) +set: 集合 +set (data base as): 集合 (数据库作为) +set (operations on): 集合 (操作) +set (permutations of): 集合 (排列) +set (represented as binary tree): 集合 (表示为二叉树) +set (represented as ordered list): 集合 (表示为有序列表) +set (represented as unordered list): 集合 (表示为无序列表) +set (subsets of): 集合 (子集) +set_contents: set_contents (设置内容函数 - 寄存器/内存) +set_current_environment: set_current_environment (设置当前环境函数) +set_current_time: set_current_time (设置当前时间函数) +set_front_ptr: set_front_ptr (设置队头指针函数) +set_head (primitive function): set_head (设置列表头元素原语函数) +set_head (primitive function, functional implementation of): set_head (函数式实现) +set_head (primitive function, implemented with vectors): set_head (用向量实现) +set_head (primitive function, value of): set_head (值) +set_inst_execution_fun: set_inst_execution_fun (设置指令执行函数) +set_rear_ptr: set_rear_ptr (设置队尾指针函数) +set_register_contents: set_register_contents (设置寄存器内容函数) +set_segments: set_segments (设置段列表函数) +set_signal: set_signal (设置信号值函数) +set_tail (primitive function): set_tail (设置列表尾元素原语函数) +set_tail (primitive function, functional implementation of): set_tail (函数式实现) +set_tail (primitive function, implemented with vectors): set_tail (用向量实现) +set_tail (primitive function, value of): set_tail (值) +setup_environment: setup_environment (设置环境函数) +set_value: set_value (设置值函数 - 约束系统) +shadow a binding: 遮蔽绑定 +Shamir, Adi: Shamir, Adi (人名) +shape of a process: 过程形态 +shared data: 共享数据 +shared resources: 共享资源 +shared state: 共享状态 +shrink_to_upper_right: shrink_to_upper_right (缩向右上角函数 - 图片语言) +Shrobe, Howard E.: Shrobe, Howard E. (人名) +SICP: SICP (书名缩写:《计算机程序的构造和解释》) +sicp JavaScript package: sicp JavaScript 包 +SICP JS: SICP JS (本书版本名称) +side-effect bug: 副作用错误 +sieve of Eratosthenes: 埃拉托斯特尼筛法 +sieve: sieve (筛法函数) +Sigma (sigma) notation: Sigma (求和) 记号 +signal, digital: 信号 (数字) +signal_error: signal_error (发信号错误函数) +signal-flow diagram: 信号流图 +signal processing: 信号处理 +signal processing (smoothing a function): 信号处理 (平滑函数) +signal processing (smoothing a signal): 信号处理 (平滑信号) +signal processing (stream model of): 信号处理 (流模型) +signal processing (zero crossings of a signal): 信号处理 (信号过零点) +signal-processing view of computation: 计算的信号处理观点 +simple_query: simple_query (简单查询处理函数) +simple_query (without delayed expression): simple_query (无延迟表达式版本) +simple query: 简单查询 +simple query (processing): 简单查询 (处理) +simplification of algebraic expressions: 代数表达式化简 +Simpson's Rule for numerical integration: 辛普森积分法则 +simulation: 模拟 +simulation (of digital circuit): 模拟 (数字电路) +simulation (event-driven): 模拟 (事件驱动) +simulation (as machine-design tool): 模拟 (作为机器设计工具) +simulation (for monitoring performance of register machine): 模拟 (用于监控寄存器机性能) +simulation (Monte Carlo): 模拟 (蒙特卡洛) +simulation (of register machine): 模拟 (寄存器机) +sine: 正弦 +sine (approximation for small angle): 正弦 (小角度近似) +sine (power series for): 正弦 (幂级数) +singleton_stream: singleton_stream (单元素流函数) +SKETCHPAD: SKETCHPAD (早期图形系统) +slash (double slash // for comments in programs): 斜杠 (双斜杠 // 用于程序注释) +smallest_divisor: smallest_divisor (最小因子函数) +smallest_divisor (more efficient version): smallest_divisor (更高效版本) +Smalltalk: Smalltalk (编程语言名) +smoothing a function: 平滑函数 +smoothing a signal: 平滑信号 +snake case: 蛇形命名法 +snarf: snarf (俚语: 快速获取/复制) +Solomonoff, Ray: Solomonoff, Ray (人名) +solve differential equation: solve (解微分方程函数) +solve differential equation (lazy-list version): solve (惰性列表版本) +solving equation: 方程求解 +source language: 源语言 +source program: 源程序 +Spafford, Eugene H.: Spafford, Eugene H. (人名) +sparse polynomial: 稀疏多项式 +split: split (分裂函数 - 图片语言) +spread and rest parameter syntax: 展开与剩余参数语法 +sqrt: sqrt (平方根函数) +sqrt (block structured): sqrt (块结构版本) +sqrt (in environment model): sqrt (在环境模型中) +sqrt (as fixed point): sqrt (作为不动点) +sqrt (as iterative improvement): sqrt (作为迭代改进) +sqrt (with Newton's method): sqrt (用牛顿法) +sqrt (register machine for): sqrt (寄存器机) +sqrt (as stream limit): sqrt (作为流极限) +sqrt_stream: sqrt_stream (平方根近似值流) +square: square (平方函数) +square (in environment model): square (在环境模型中) +square_limit: square_limit (方块极限函数 - 图片语言) +square_of_four: square_of_four (四方块函数 - 图片语言) +squarer (constraint): squarer (平方器约束) +square root: 平方根 +square root (stream of approximations): 平方根 (近似值流) +squash_inwards: squash_inwards (向内挤压函数 - 图片语言) +stack: 栈 +stack (framed): 栈 (带帧) +stack (for recursion in register machine): 栈 (用于寄存器机递归) +stack (representing): 栈 (表示) +stack allocation and tail recursion: 栈分配与尾递归 +stack_inst_reg_name: stack_inst_reg_name (栈指令寄存器名选择器) +Stallman, Richard M.: Stallman, Richard M. (人名) +start register machine: start (启动寄存器机函数) +start_eceval: start_eceval (启动显式控制求值器函数) +start_segment: start_segment (线段起点选择器) +state: 状态 +state (local): 状态 (局部) +state (shared): 状态 (共享) +state (vanishes in stream formulation): 状态 (在流形式中消失) +statement: 语句 +statement (value-producing and non-value-producing): 语句 (产生值和不产生值) +statement sequence: 语句序列 +state variable: 状态变量 +state variable (local): 状态变量 (局部) +statically typed language: 静态类型语言 +Steele, Guy Lewis Jr.: Steele, Guy Lewis Jr. (人名) +Stein, Clifford: Stein, Clifford (人名) +stop-and-copy garbage collector: 停止-复制垃圾回收器 +Stoy, Joseph E.: Stoy, Joseph E. (人名) +Strachey, Christopher: Strachey, Christopher (人名) +stratified design: 分层设计 +stream(s): 流 +stream(s) (delayed evaluation and): 流 (与延迟求值) +stream(s) (empty): 流 (空) +stream(s) (implemented as delayed lists): 流 (实现为延迟列表) +stream(s) (implemented as lazy lists): 流 (实现为惰性列表) +stream(s) (implicit definition): 流 (隐式定义) +stream(s) (infinite): 流 (无限) +stream(s) (used in query interpreter): 流 (在查询解释器中使用) +stream_append: stream_append (连接流函数) +stream_append_delayed: stream_append_delayed (延迟连接流函数) +stream_enumerate_interval: stream_enumerate_interval (枚举区间流函数) +stream_filter: stream_filter (过滤流函数) +stream_flatmap: stream_flatmap (扁平映射流函数) +stream_for_each: stream_for_each (遍历流执行函数) +stream_limit: stream_limit (流极限函数) +stream_map: stream_map (映射流函数) +stream_map_2: stream_map_2 (二元映射流函数) +stream_map_optimized: stream_map_optimized (优化映射流函数) +stream_ref: stream_ref (引用流元素函数) +stream_tail: stream_tail (流尾部函数) +stream_withdraw: stream_withdraw (流式取款函数) +strict: 严格求值 +string(s): 字符串 +string(s) (concatenation): 字符串 (连接) +string(s) (equality of): 字符串 (相等性) +string(s) (interning): 字符串 (驻留) +string(s) (quotation marks for): 字符串 (引号) +string(s) (representation of): 字符串 (表示) +string(s) (typed over multiple lines): 字符串 (跨多行输入) +string(s) (uniqueness of): 字符串 (唯一性) +stringify (primitive function): stringify (字符串化原语函数) +string pool: 字符串池 +Stuckey, Peter J.: Stuckey, Peter J. (人名) +sub (generic): sub (通用减法) +sub_complex: sub_complex (复数减法函数) +sub_interval: sub_interval (区间减法函数) +sub_rat: sub_rat (有理数减法函数) +subroutine in register machine: 子例程 (在寄存器机中) +subsets of a set: 集合的子集 +substitution model of function application: 函数应用的代换模型 +substitution model of function application (inadequacy of): 函数应用的代换模型 (不足之处) +substitution model of function application (shape of process): 函数应用的代换模型 (过程形态) +subtype: 子类型 +subtype (multiple): 子类型 (多个) +sub_vect: sub_vect (向量减法函数) +success continuation (nondeterministic evaluator): 成功续延 (非确定性求值器) +successive squaring: 逐次平方法 (快速幂) +sum: sum (求和函数) +sum (as accumulation): sum (作为累积) +sum (iterative version): sum (迭代版本) +sum_cubes: sum_cubes (立方和函数) +sum_cubes (with higher-order functions): sum_cubes (带高阶函数版本) +sum_integers: sum_integers (整数和函数) +sum_integers (with higher-order functions): sum_integers (带高阶函数版本) +summation of a series: 级数求和 +summation of a series (with streams): 级数求和 (用流) +sum_odd_squares: sum_odd_squares (奇数平方和函数) +sum_of_squares: sum_of_squares (平方和函数) +sum_of_squares (in environment model): sum_of_squares (在环境模型中) +sum_primes: sum_primes (素数和函数) +supertype: 超类型 +supertype (multiple): 超类型 (多个) +Sussman, Gerald Jay: Sussman, Gerald Jay (人名) +Sutherland, Ivan: Sutherland, Ivan (人名) +symbol(s): 符号 +symbol(s) (in environment operations): 符号 (在环境操作中) +symbol(s) (in global environment): 符号 (在全局环境中) +symbol(s) (in parsing of names): 符号 (在名称解析中) +symbol(s) (representing names in metacircular evaluator): 符号 (在元循环求值器中表示名称) +symbol(s) (in unparse): 符号 (在 unparse 中) +symbolic algebra: 符号代数 +symbolic differentiation: 符号微分 +symbolic expression: 符号表达式 +symbol_leaf: symbol_leaf (符号叶节点选择器) +symbol_of_name: symbol_of_name (名称的符号选择器) +symbols: symbols (符号列表选择器) +SYNC: SYNC (同步原语) +synchronization: 同步 +syntactic analysis, separated from execution: 语法分析 (与执行分离) +syntactic analysis, separated from execution (in metacircular evaluator): 语法分析 (在元循环求值器中) +syntactic analysis, separated from execution (in register-machine simulator): 语法分析 (在寄存器机模拟器中) +syntactic form: 语法形式 +syntactic form (as derived component): 语法形式 (作为派生组件) +syntactic form (function vs.): 语法形式 (与函数比较) +syntactic form (need for): 语法形式 (必要性) +syntactic forms: 语法形式 (列表) +syntactic forms (assignment): 语法形式 (赋值) +syntactic forms (block): 语法形式 (块) +syntactic forms (break statement): 语法形式 (break 语句) +syntactic forms (conditional expression): 语法形式 (条件表达式) +syntactic forms (conditional statement): 语法形式 (条件语句) +syntactic forms (constant declaration): 语法形式 (常量声明) +syntactic forms (continue statement): 语法形式 (continue 语句) +syntactic forms (function declaration): 语法形式 (函数声明) +syntactic forms (lambda expression): 语法形式 (lambda 表达式) +syntactic forms (logical conjunction (&&)): 语法形式 (逻辑与 &&) +syntactic forms (logical disjunction (||)): 语法形式 (逻辑或 ||) +syntactic forms (return statement): 语法形式 (返回语句) +syntactic forms (variable declaration): 语法形式 (变量声明) +syntactic forms (while loop): 语法形式 (while 循环) +syntactic sugar: 语法糖 +syntactic sugar (&& and || as): 语法糖 (&& 和 || 作为) +syntactic sugar (function vs. data as): 语法糖 (函数 vs. 数据作为) +syntactic sugar (looping constructs as): 语法糖 (循环结构作为) +syntax: 语法 +syntax (abstract): 语法 (抽象) +syntax (of expressions, describing): 语法 (表达式描述) +syntax (of a programming language): 语法 (编程语言的) +syntax interface: 语法接口 +systematic search: 系统搜索 + +T + +table: 表 / 表格 +table (backbone of): 表 (骨干) +table (for coercion): 表 (用于强制转换) +table (for data-directed programming): 表 (用于数据导向编程) +table (local): 表 (局部) +table (n-dimensional): 表 (n 维) +table (one-dimensional): 表 (一维) +table (operation-and-type): 表 (操作与类型) +table (represented as binary tree vs. unordered list): 表 (表示为二叉树 vs. 无序列表) +table (testing equality of keys): 表 (测试键相等性) +table (two-dimensional): 表 (二维) +table (used in simulation agenda): 表 (用于模拟议程) +table (used to store computed values): 表 (用于存储计算值) +tableau: 表格 (级数加速) +tabulation: 制表 / 列表 (记忆化) +tack_on_instruction_sequence: tack_on_instruction_sequence (附加指令序列函数) +tagged architecture: 带标签架构 +tagged data: 带标签数据 +tail (primitive function): tail (列表尾部原语函数) +tail (primitive function, axiom for): tail (公理) +tail (primitive function, functional implementation of): tail (函数式实现) +tail (primitive function, implemented with vectors): tail (用向量实现) +tail (primitive function, as list operation): tail (作为列表操作) +tail recursion: 尾递归 +tail recursion (compiler and): 尾递归 (与编译器) +tail recursion (environment model of evaluation and): 尾递归 (与求值环境模型) +tail recursion (explicit-control evaluator and): 尾递归 (与显式控制求值器) +tail recursion (garbage collection and): 尾递归 (与垃圾回收) +tail recursion (in JavaScript): 尾递归 (在 JavaScript 中) +tail recursion (metacircular evaluator and): 尾递归 (与元循环求值器) +tail recursion (return statement necessary for): 尾递归 (需要返回语句) +tail recursion (in Scheme): 尾递归 (在 Scheme 中) +tail-recursive evaluator: 尾递归求值器 +tangent: 正切 +tangent (as continued fraction): 正切 (表示为连分数) +tangent (power series for): 正切 (幂级数) +target register: 目标寄存器 +TDZ (temporal dead zone): TDZ (暂时性死区) +Technological University of Eindhoven: 埃因霍温理工大学 +temporal dead zone (TDZ): 暂时性死区 (TDZ) +terminal node of a tree: 树的终端节点 / 叶节点 +term_list: term_list (项列表选择器/构造器) +term list of polynomial: 多项式的项列表 +term list of polynomial (representing): 多项式的项列表 (表示) +ternary operator: 三元运算符 +test (in register machine): test (寄存器机中的 test 指令) +test (in register machine, instruction constructor): test (test 指令构造器) +test (in register machine, simulating): test (模拟 test 指令) +test_and_set: test_and_set (测试并设置原子操作) +test_condition: test_condition (测试条件选择器) +test operation in register machine: 测试操作 (寄存器机中) +Thatcher, James W.: Thatcher, James W. (人名) +the_empty_environment: the_empty_environment (空环境) +the_empty_termlist: the_empty_termlist (空项列表) +the_global_environment: the_global_environment (全局环境) +the_heads: the_heads (头指针向量/寄存器) +the_heads (register): the_heads (寄存器) +the_heads (vector): the_heads (向量) +THE Multiprogramming System: THE 多道程序系统 +theorem proving (automatic): 定理证明 (自动) +theta(f(n)) (theta of f(n)): theta(f(n)) (Theta 记号) +the_tails: the_tails (尾指针向量/寄存器) +the_tails (register): the_tails (寄存器) +the_tails (vector): the_tails (向量) +thread: 线程 +thunk: thunk (延迟计算对象) +thunk (call-by-name): thunk (传名调用) +thunk (call-by-need): thunk (传需求调用) +thunk (forcing): thunk (强制求值) +thunk (implementation of): thunk (实现) +thunk (origin of name): thunk (名称来源) +time: 时间 +time (assignment and): 时间 (与赋值) +time (communication and): 时间 (与通信) +time (in concurrent systems): 时间 (在并发系统中) +time (functional programming and): 时间 (与函数式编程) +time (in nondeterministic computing): 时间 (在非确定性计算中) +time (purpose of): 时间 (目的) +timed_prime_test: timed_prime_test (计时素性测试函数) +time segment, in agenda: 时间段 (在议程中) +time slicing: 时间分片 +timing diagram: 时序图 +TK!Solver: TK!Solver (软件名) +tower of types: 类型塔 +tracing: 跟踪 +tracing (instruction execution): 跟踪 (指令执行) +tracing (register assignment): 跟踪 (寄存器赋值) +transform_painter: transform_painter (变换画家函数) +transparency, referential: 引用透明性 +transpose a matrix: 转置矩阵 +tree: 树 +tree (binary): 树 (二叉) +tree (B-tree): 树 (B树) +tree (combination viewed as): 树 (组合式视为) +tree (counting leaves of): 树 (计算叶节点数) +tree (enumerating leaves of): 树 (枚举叶节点) +tree (fringe of): 树 (叶节点列表) +tree (Huffman): 树 (霍夫曼) +tree (lazy): 树 (惰性) +tree (mapping over): 树 (映射) +tree (rational): 树 (有理/无限) +tree (red-black): 树 (红黑) +tree (represented as pairs): 树 (用序对表示) +tree (reversing at all levels): 树 (各层反转) +tree accumulation: 树累积 +tree_map: tree_map (树映射函数) +tree-recursive process: 树形递归过程 +tree-recursive process (order of growth): 树形递归过程 (增长阶) +tree_to_list_...: tree_to_list_... (树转列表系列函数) +trigonometric relations: 三角关系 +true (keyword): true (关键字) +truncation error: 截断误差 +truthiness: 真值性 (JavaScript 中的真值概念) +truth maintenance: 真值维护 +Turing, Alan M.: Turing, Alan M. (人名) +Turing machine: 图灵机 +Turner, David: Turner, David (人名) +type in query system: type (查询系统中的类型谓词) +type in register machine: type (寄存器机指令类型) +type(s): 类型 +type(s) (cross-type operations): 类型 (跨类型操作) +type(s) (dispatching on): 类型 (基于类型分派) +type(s) (hierarchy in symbolic algebra): 类型 (符号代数中的层次) +type(s) (hierarchy of): 类型 (层次结构) +type(s) (lowering): 类型 (降低) +type(s) (multiple subtype and supertype): 类型 (多重子类型和超类型) +type(s) (polymorphic): 类型 (多态) +type(s) (raising): 类型 (提升) +type(s) (subtype): 类型 (子类型) +type(s) (supertype): 类型 (超类型) +type(s) (tower of): 类型 (塔) +typed pointer: 带类型指针 +type field: 类型字段 +type-inferencing mechanism: 类型推断机制 +type_tag: type_tag (类型标签选择器) +type_tag (using JavaScript data types): type_tag (使用 JavaScript 数据类型) +type tag: 类型标签 +type tag (two-level): 类型标签 (两级) + +U + +unary operator: 一元运算符 +unbound name: 未绑定名称 / 自由变量 +undefined (predeclared name): undefined (预定义名称/值) +unev register: unev 寄存器 (未求值表达式寄存器) +unification: 合一 +unification (discovery of algorithm): 合一 (算法发现) +unification (implementation): 合一 (实现) +unification (pattern matching vs.): 合一 (与模式匹配比较) +unify_match: unify_match (合一匹配函数) +union_set: union_set (集合并集函数) +union_set (binary-tree representation): union_set (二叉树表示) +union_set (ordered-list representation): union_set (有序列表表示) +union_set (unordered-list representation): union_set (无序列表表示) +unique (query language): unique (查询语言中的去重) +unique_pairs: unique_pairs (唯一配对函数) +unit square: 单位正方形 +univariate polynomial: 单变量多项式 +universal machine: 通用机 +universal machine (explicit-control evaluator as): 通用机 (显式控制求值器作为) +universal machine (general-purpose computer as): 通用机 (通用计算机作为) +University of Edinburgh: 爱丁堡大学 +University of Marseille: 马赛大学 +UNIX: UNIX (操作系统) +UNIX (epoch): UNIX (纪元时间) +unknown_component_type: unknown_component_type (未知组件类型错误) +unknown_function_type: unknown_function_type (未知函数类型错误) +unordered-list representation of sets: 集合的无序列表表示 +unparse: unparse (反解析函数) +unparse (as inverse of parse): unparse (作为 parse 的逆) +unparse (in query interpreter): unparse (在查询解释器中) +update_insts: update_insts (更新指令列表函数) +upper_bound: upper_bound (上界选择器 - 区间) +up_split: up_split (上分裂函数 - 图片语言) +upward compatibility: 向上兼容性 +user_print: user_print (用户打印函数) +user_print (modified for compiled code): user_print (为编译代码修改) +user_read: user_read (用户读取函数) + +V + +V operation on semaphore: V 操作 (信号量) +val register: val 寄存器 (值寄存器) +value: 值 +value (of an expression): 值 (表达式的) +value (of a program): 值 (程序的) +value_fun: value_fun (值函数选择器 - 寄存器机) +variable: variable (变量检查/构造器 - 代数/多项式) +variable: 变量 +variable (assignment to): 变量 (赋值) +variable (declaration): 变量 (声明) +variable (declaration, parsing of): 变量 (声明解析) +variable (parameter as): 变量 (参数作为) +variable-length code: 变长编码 +vector (data structure): 向量 (数据结构) / 数组 +vector (data structure, for arguments of apply): 向量 (用于 apply 参数) +vector (data structure, used in spread and rest parameter syntax): 向量 (用于展开和剩余参数语法) +vector (mathematical): 向量 (数学) +vector (mathematical, operations on): 向量 (数学, 操作) +vector (mathematical, in picture-language frame): 向量 (数学, 在图片语言框架中) +vector (mathematical, represented as pair): 向量 (数学, 表示为序对) +vector (mathematical, represented as sequence): 向量 (数学, 表示为序列) +vector_ref (primitive function): vector_ref (向量引用原语函数) +vector_set (primitive function): vector_set (向量设置原语函数) +verbs: verbs (动词列表) +very high-level language: 超高级语言 + +W + +Wadler, Philip: Wadler, Philip (人名) +Wadsworth, Christopher: Wadsworth, Christopher (人名) +Wagner, Eric G.: Wagner, Eric G. (人名) +Walker, Francis Amasa: Walker, Francis Amasa (人名) +walking down a list with tail: 使用 tail 遍历列表 +Wallis, John: Wallis, John (人名) +Wand, Mitchell: Wand, Mitchell (人名) +Waters, Richard C.: Waters, Richard C. (人名) +web browser, interpreting JavaScript: Web 浏览器 (解释 JavaScript) +weight: weight (权重选择器) +weight_leaf: weight_leaf (叶节点权重选择器) +Weyl, Hermann: Weyl, Hermann (人名) +"what is" vs. "how to" description: "是什么" vs. "如何做" 的描述 +wheel (rule): wheel (规则: 轮子?) +while (keyword): while (关键字) +while loop: while 循环 +while loop (implementing in analyzing evaluator): while 循环 (在分析求值器中实现) +while loop (implementing in metacircular evaluator): while 循环 (在元循环求值器中实现) +whitespace characters: 空白字符 +width: width (宽度选择器 - 区间) +width of an interval: 区间宽度 +Wilde, Oscar (Perlis's paraphrase of): Wilde, Oscar (Perlis 的化用) +Wiles, Andrew: Wiles, Andrew (人名) +Winograd, Terry: Winograd, Terry (人名) +Winston, Patrick Henry: Winston, Patrick Henry (人名) +wire, in digital circuit: 导线 (在数字电路中) +Wise, David S.: Wise, David S. (人名) +wishful thinking: 愿望思维 / 乐观假设 +withdraw: withdraw (取款函数) +withdraw (problems in concurrent system): withdraw (在并发系统中的问题) +world line of a particle: 世界线 (粒子的) +Wright, E. M.: Wright, E. M. (人名) +Wright, Jesse B.: Wright, Jesse B. (人名) +Wrigstad, Tobias, daughter of: Wrigstad, Tobias 的女儿 + +X + +xcor_vect: xcor_vect (向量 X 坐标选择器) +Xerox Palo Alto Research Center: 施乐帕洛阿尔托研究中心 (Xerox PARC) + +Y + +Y operator: Y 组合子 / Y 算子 +Yang Hui: 杨辉 (人名) +ycor_vect: ycor_vect (向量 Y 坐标选择器) +Yochelson, Jerome C.: Yochelson, Jerome C. (人名) + +Z + +Zabih, Ramin: Zabih, Ramin (人名) +zero crossings of a signal: 信号过零点 +zero test (generic): 零值测试 (通用) +zero test (generic, for polynomials): 零值测试 (通用, 用于多项式) +Zilles, Stephen N.: Zilles, Stephen N. (人名) +Zippel, Richard E.: Zippel, Richard E. (人名) \ No newline at end of file diff --git a/i18n/.env.example b/i18n/.env.example new file mode 100644 index 000000000..4e6029a71 --- /dev/null +++ b/i18n/.env.example @@ -0,0 +1,8 @@ +API_KEY= +AI_MODEL=gpt-4o +# maximum number of characters to be sent for translation per batch +MAX_LEN=2000 +#Optional +AI_BASEURL= +# maximum number of concurent translations that are allowed +MAX_TRANSLATION_NO=10 \ No newline at end of file diff --git a/xml/cn/chapter1/chapter1.xml b/xml/cn/chapter1/chapter1.xml new file mode 100644 index 000000000..d1f2806e6 --- /dev/null +++ b/xml/cn/chapter1/chapter1.xml @@ -0,0 +1,407 @@ + + + 创建抽象 + + 过程 + 函数 + + + + + % 1997年秋季 添加 tex '\\label' 用于手动引用。 + % 1997年8月26日 修复第三版的排版错误 -- 第45页,第70页 + % + % 1996年4月15日 修复与之前索引比较时发现的排版错误 -- 必须重新打印第60页 + % 1996年4月13日-4月15日 索引 [在章节最终打印后] + % 1996年4月11日 根据最终检查和拼写检查进行修正和调整 + % 1996年4月9日 根据与之前索引的对比进行微小修正 + % 1996年4月6日-... Julie 索引修正 (以及4月8日更改'ns') + % 1996年4月5日 Hal 索引 + % 1996年3月29日, 4月3日 Julie: 索引修正 + % 1996年3月18日 继续页码,其他索引的细微更改 + % 1996年3月14日 & 3月16日 已分页至第64页 (查找 \\pagekludge) + % 1996年3月10日-3月14日 细微更改/修正;索引;移动练习 + % 1996年3月9日 修正某些练习部分的空格 + % 1996年3月6日 修正排版错误 + % 1996年3月5日 细微排版修正 + % 1996年3月4日 编辑一等过程的脚注以适应 PSZ;修复小错误 + % 1996年2月26日至3月1日 修复一些坏的换行 + % 1996年2月24日 在 {lisp} 后冲洗 \\noindent,通过关闭空格 % + % 1996年2月24日 修复 GCD-简化方程的格式 + % 1996年2月23日 使用新的 smallequation 宏修复排版 + % 1996年2月22日 epigraph的新规范 + % 1996年2月21日 修复绝对值定义的格式 + % 1996年2月20日 修复箭头的绘制方式 + % 1996年2月19日 更改 exp-iter -> expt-iter,fast-exp -> fast-expt + % 1996年2月19日 修复引用中的 () + % 1996年2月19日 修复 et al. + + + + 思维的行为,其中它对简单思想施加其能力, + 主要有这三种:1. 将几个简单思想结合成一个复合思想,从而形成所有复杂思想。 2. 第二种是 + 将两个思想,无论是简单还是复杂,放在一起,并将它们相邻摆放,以便同时观察它们,而不将其合并为一个,从中获取所有关系的思想。 + 3. 第三种是将它们与在其实际存在中伴随它们的所有其他思想分开:这被称为抽象,从而形成所有 + 一般思想。 + + 约翰·洛克 + 关于人类理解的论文 + 洛克,约翰 + 1690 + + + + + + + + 我们即将研究一个 + 计算过程。 + 过程 + 计算过程 + 计算过程是栖息在计算机中的抽象存在。 + 随着它们的发展,过程操作其他称为 + 数据的抽象事物。 + 数据 + 过程的发展由一组称为 + 程序 + 程序的规则模式所引导。 + 人们创建程序来指导过程。 + 实际上,我们用我们的咒语召唤计算机的灵魂。 + + + + 计算过程确实很像魔术师对灵魂的想法。 它无法被看到或触摸。 它根本不由物质构成。 然而,它是非常真实的。 它可以执行智力工作。 它可以回答问题。 它可以通过在银行发放资金或通过控制工厂中的机器人臂来影响世界。 我们用来召唤过程的程序就像魔术师的咒语。 它们是由神秘和深奥的 + 编程语言 + 编程语言 + 中的符号表达式巧妙地组合而成,规定了我们希望我们的过程执行的任务。 + + + + 在一台正常工作的计算机中,计算过程精确且准确地执行程序。因此,像魔术师的学徒一样,初学的程序员必须学习理解和预见他们施法的后果。即使是小错误 + + + (通常称为 错误 错误故障 故障) + + + (通常称为 错误 错误) + + + 在程序中也可能带来复杂且意想不到的后果。 + + + + 幸运的是,学习编程远比学习魔法要安全得多,因为我们接触的“灵魂”以安全的方式被方便地包含在内。然而,现实世界中的编程需要小心、专业知识和智慧。例如,计算机辅助设计程序中的一个小错误可能导致飞机或大坝的灾难性坍塌,或工业机器人自我毁灭。 + + + + 大师级的软件工程师能够组织程序,以便他们可以合理地确定生成的过程将执行预期的任务。 他们能够预见系统的行为。 他们知道如何构建程序,以便不可预见的问题不会导致灾难性的后果,并且当问题出现时,他们能够 + 调试 + 调试 + 他们的程序。 设计良好的计算系统,像设计良好的汽车或核反应堆一样,采用模块化的方式构建,以便部件可以单独构造、替换和调试。 + + + + + + 在 Lisp 中编程 + + + + + 在 JavaScript 中编程 + + + + + + + + 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. + Lisphistory of + Lisp was invented in the late + 1950s as a formalism for reasoning about the use of certain kinds of + logical expressions, called + recursion equations, as a model for + computation. The language was conceived by + McCarthy, John + John McCarthy and is based + on his paper Recursive Functions of Symbolic Expressions and Their + Computation by Machine (McCarthy 1960). + + + + + 我们需要一种适合描述过程的语言,为此我们将使用编程语言 JavaScript。 就像我们日常思维通常用我们的自然语言(如英语、瑞典语或汉语)表达,量化现象的描述则用数学符号表示一样,我们的过程性思维将用 JavaScript 表达。 + JavaScript历史 + JavaScript 于 1995 年开发,作为一种编程语言,用于通过嵌入网页的脚本控制万维网浏览器的行为。 + 该语言由 + Eich, Brendan + Mocha + Brendan Eich 构想,最初命名为 Mocha,后来改名为 LiveScript,最终更名为 JavaScript。 + Oracle Corporation + 名称 JavaScript 是 Oracle Corporation 的商标。 + + + + + + + + Despite its inception as a mathematical formalism, Lisp is a practical + programming language. A Lisp + interpreter + is a machine that + carries out processes described in the Lisp language. The first Lisp + interpreter was implemented by + McCarthy, John + McCarthy with the help of colleagues + and students in the Artificial Intelligence Group of the + MITResearch Laboratory of Electronics + MIT Research + Laboratory of Electronics and in the MIT Computation + Center.The + Lisp 1 Programmers Manual + appeared in + 1960, and the + Lisp 1.5 Programmers Manual + McCarthy, John + (McCarthy 1965) + was published in 1962. The early history of Lisp is described in + McCarthy 1978. + + Lisp, whose name is an acronym for + Lispacronym for LISt Processing + LISt Processing, + was designed to provide symbol-manipulating capabilities for + attacking programming problems such as the symbolic differentiation + and integration of algebraic expressions. It included for this + purpose new data objects known as atoms and lists, which most + strikingly set it apart from all other languages of the period. + + + + + 尽管 JavaScript 最初是一种用于脚本编写网页的语言,但 JavaScript + 解释器 + 是一种通用编程语言。 JavaScript + 解释器 是执行用 JavaScript 语言描述的过程的机器。 + Eich, Brendan + 第一个 JavaScript 解释器是由 Eich 在网景通讯公司为网景导航器网页浏览器实现的。 + Netscape Communications Corporation + Netscape Navigator + JavaScript 从 + Self + SchemeJavaScript作为 JavaScript 先驱 + Scheme 和 Self 编程语言继承了其核心特性。Scheme 是 Lisp 的一种方言,而 + Lisp作为 JavaScript 祖先 + 被用作本书原始版本的编程语言。 从 Scheme,JavaScript 继承了其最基本的设计原则,如 + 词法作用域的一等公民函数和动态类型。 + + + + + + + + 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. Lisps + informal evolution has continued through the years, and the community of + Lisp users has traditionally resisted attempts to promulgate any + official definition of the language. This evolution, + together with the flexibility and elegance of the initial conception, + has enabled Lisp, which is the second oldest language in widespread use + today (only + Fortran + Fortran is older), to continually adapt to encompass the + most modern ideas about program design. Thus, Lisp is by now a family + of dialects, which, while sharing most of the original features, may + differ from one another in significant ways. The dialect of Lisp used + in this book is called + Scheme + Lisp dialectsScheme + Scheme.The two dialects in which most + major Lisp programs of the 1970s were written are + MacLisp + Lisp dialectsMacLisp + MacLisp + Moon, David A. + (Moon 1978; + Pitman, Kent M. + Pitman 1983), developed at the + MITProject MAC + MIT Project MAC, and + Interlisp + Lisp dialectsInterlisp + Interlisp + Teitelman, Warren + (Teitelman 1974), developed at + Bolt Beranek and Newman Inc. + Bolt Beranek and Newman Inc.and the + Xerox Palo Alto Research Center + Xerox Palo Alto Research Center. + Portable Standard Lisp + Lisp dialectsPortable Standard Lisp + Portable Standard Lisp + Hearn, Anthony C. + (Hearn 1969; + Griss, Martin Lewis + Griss 1981) + was a Lisp dialect designed to be easily portable + between different machines. MacLisp spawned a number of subdialects, + such as + Franz Lisp + Lisp dialectsFranz Lisp + Franz Lisp, which was developed at the + University of California at Berkeley + University of + California at Berkeley, and + Zetalisp + Lisp dialectsZetalisp + Zetalisp (Moon 1981), which was based on a + special-purpose processor designed at the + MITArtificial Intelligence Laboratory + MIT Artificial Intelligence + Laboratory to run Lisp very efficiently. The Lisp dialect used in + this book, called + Schemehistory of + Scheme (Steele 1975), was invented in 1975 by + Steele, Guy Lewis Jr. + Sussman, Gerald Jay + Guy Lewis Steele Jr.and Gerald Jay Sussman of the MIT Artificial + Intelligence Laboratory and later reimplemented for instructional use + at MIT. Scheme became an IEEE standard in 1990 + (IEEE 1990). The + Common Lisp + Lisp dialectsCommon Lisp + Common Lisp dialect (Steele 1982, + Steele 1990) was developed by the + Lisp community to combine features from the earlier Lisp dialects + to make an industrial standard for Lisp. Common Lisp became an ANSI + standard in 1994 (ANSI 1994). + + + + + JavaScript 仅在表面上与其命名的 Java 语言相似;Java 和 JavaScript 都使用语言 C 的块结构。与通常采用编译方式转化为低级语言的 Java 和 C 相对,JavaScript 程序最初是由网页浏览器 + 解释的。 + 网页浏览器,解释 JavaScript + 在网景导航器之后,其他网页浏览器也为该语言提供了解释器,包括微软的 Internet Explorer,其 JavaScript 版本被称为 + JScript。JavaScript 在控制网页浏览器方面的普及促成了一项标准化工作,最终形成了 + ECMAScript。 + ECMAScript + ECMAScript 标准的第一版由 Guy Lewis Steele Jr. 主导,并于 1997 年 6 月完成 + (ECMA 1997)。第六版,被称为 ECMAScript 2015,由 Allen Wirfs-Brock 主导,并在 2015 年 6 月获得 ECMA 大会的通过 + (ECMA 2015)。 + + + + + + + + Because of its experimental character and its emphasis on symbol + manipulation, + LispFortran vs. + Lispefficiency of + efficiencyof Lisp + Lisp was at first very inefficient for numerical + computations, at least in comparison with Fortran. Over the years, + however, Lisp compilers have been developed that translate programs + into machine code that can perform numerical computations reasonably + efficiently. And for special applications, Lisp has been used with + great effectiveness.One such special application was a + breakthrough computation of scientific importancean integration of + the motion of the + Solar Systems chaotic dynamics + chaos in the Solar System + Solar System that extended previous results by + nearly two orders of magnitude, and demonstrated that the dynamics of + the Solar System is chaotic. This computation was made possible by + new integration algorithms, a special-purpose compiler, and a + special-purpose computer all implemented with the aid of software + tools written in Lisp + Abelson, Harold + (Abelson et al.1992; + Sussman, Gerald Jay + Wisdom, Jack + Sussman and Wisdom 1992). + + Although Lisp has not yet overcome its old reputation + as hopelessly inefficient, Lisp is now used in many applications where + efficiency is not the central concern. + Lisphistory of + For example, Lisp has become + a language of choice for operating-system shell languages and for + extension languages for editors and computer-aided design systems. + + + + + 嵌入 JavaScript 程序到网页中的做法促使网页浏览器的开发者实现 JavaScript 解释器。随着这些程序变得愈加复杂,解释器在执行它们时变得更加高效,最终使用了诸如即时(JIT)编译之类的复杂实现技术。 + 截至撰写本文时(2021 年),大多数 JavaScript 程序嵌入在网页中,并由浏览器解释,但 JavaScript 正日益作为一种通用编程语言使用,利用像 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 + Lispunique features of + unique features that make it an excellent medium for + studying important programming constructs and data structures and for + relating them to the linguistic features that support them. The most + significant of these features is the fact that Lisp descriptions of + processes, called + procedure + proceduredataas data + 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, Lisps 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. + + + + + + 在这里,原作者正确地指出了 Scheme 的同构性,这使得第 4 章和第 5 章中的元编程特别容易。 JavaScript 的改编受益于原版中已包含的第 4.2 节的抽象层。凭借这一抽象层以及一个附加的解析原语第 4 章和第 5 章可以在不作根本性更改的情况下进行改编。 + + + 然而,网页浏览器执行 JavaScript 程序的能力使其成为计算机程序书籍在线版本的理想语言。在网页上点击东西来执行程序自然地体现在 JavaScript 中毕竟这就是 JavaScript 设计的目的!更根本地, + + ECMAScript 2015 具备一系列特性,使其成为学习重要编程结构和数据结构的优秀媒介,并将它们与支持它们的语言特性联系起来。其 + 词法作用域的一等公民函数及其通过 lambda 表达式的语法支持为功能抽象提供了直接而简洁的访问,动态类型允许在整本书中保持与 Scheme 原版接近。除此之外,使用 JavaScript 编程非常有趣。 + + + + + + + + &section1.1; + + + &section1.2; + + + &section1.3; + + diff --git a/xml/cn/chapter1/section1/section1.xml b/xml/cn/chapter1/section1/section1.xml index e2f436f4a..04a40279d 100644 --- a/xml/cn/chapter1/section1/section1.xml +++ b/xml/cn/chapter1/section1/section1.xml @@ -1,188 +1,114 @@
- 编程元素编程元素 - 编程元素 + 编程要素 - 编程元素编程元素 - - - 编程元素 - - 编程元素 - - - 编程元素 - - - - - 一个强大的编程语言不仅仅是用于指示计算机执行任务的工具。该语言还作为一个框架,用于组织我们关于过程的想法。因此,当我们描述一种语言时,我们应特别关注该语言提供的将简单想法结合成更复杂想法的方法。每一种强大的语言都有三种实现此目的的机制: -
    -
  • - 原始表达式, - 原始表达式 - 代表语言所关注的最简单的实体, -
  • -
  • - 组合方法, 通过 - 组合方法 - 组合,方法 - 从简单的元素构建复合元素,以及 -
  • -
  • - 抽象方法, - 抽象方法 - 通过它可以命名和操作复合元素作为单位。 -
  • -
-
编程元素 - + 编程要素 - 编程元素 + - - - - 一个强大的编程语言不仅仅是用于指示计算机执行任务的工具。该语言还作为一个框架,用于组织我们关于过程的想法。因此,当我们描述一种语言时,我们应特别关注该语言提供的将简单想法结合成更复杂想法的方法。每一种强大的语言都有三种实现此目的的机制: + + 一种强大的编程语言不仅仅是指示计算机执行任务的手段。该语言也是一个框架,在其中我们组织关于过程的想法。因此,当我们描述一种语言时,我们应特别注意该语言提供的将简单思想组合成更复杂思想的方法。每种强大的语言都有三种机制来实现这一点:
  • - 原始表达式, - 原始表达式 - 代表语言所关注的最简单的实体, + 原语表达式, + 原语表达式 + 它们表示语言所关心的最简单实体,
  • - 组合方法, 通过 - 组合方法 - 组合,方法 - 从简单的元素构建复合元素,以及 + 组合的方法, 通过 + 组合的方法 + 组合, 方法 + 复合元素是由更简单的元素构建而成的,
  • - 抽象方法, - 抽象方法 - 通过它可以命名和操作复合元素作为单位。 + 抽象的方法, + 抽象的方法 + 通过它复合元素可以作为单位被命名和操作。
-
+
- - 在编程中,我们处理两种类型的元素: + + 在编程中,我们处理两种元素: - + 过程 过程 - + - + <函数></函数> 函数 - + 数据 - 数据。(稍后我们会发现它们实际上并不那么不同。)非正式地,数据是我们想要操作的东西,而 + 数据。(稍后我们将发现它们实际上并不那么不同。)非正式地说,数据是我们希望操作的东西,而 - 过程 + 过程 函数 - 是操作数据规则的描述。因此,任何强大的编程语言都应该能够描述原始数据和原始 + 是操作数据规则的描述。因此,任何强大的编程语言都应该能够描述 + 原语数据和原语 - 过程 + 过程 函数 - 并且应该拥有组合和抽象 + 并且应该有组合和抽象 - 过程 + 过程 函数 和数据的方法。 - The Elements of Programming - + - programming 元素 - - - - - 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: -
    -
  • - 原始表达式, - 原始表达式 - 代表语言所关注的最简单的实体, -
  • -
  • - 组合方法, 通过 - 组合方法 - 组合,方法 - 从简单的元素构建复合元素,以及 -
  • -
  • - 抽象方法, - 抽象方法 - 通过它可以命名和操作复合元素作为单位。 -
  • -
-
- - - In programming, we deal with two kinds of elements: - - - 过程 - 过程 - - - - 函数 - - - 和 - 数据 - 数据。(稍后我们会发现它们实际上并不那么不同。)非正式地,数据是我们想要操作的东西,而 - - 过程 - 函数 - - 是操作数据规则的描述。因此,任何强大的编程语言都应该能够描述原始数据和原始 - - 过程 - 函数 - - 并且应该拥有组合和抽象 - - 过程 - 函数 - - 和数据的方法。 - - - - In this chapter we will deal only with simple - 数值数据 - 数据数值 - 数值数据,以便我们可以专注于构建 - 过程函数的规则。对于数字的 - - 特征描述为简单数据是一个完全的伪装。实际上,数字处理是任何编程语言中最棘手和最令人困惑的方面之一。涉及到的一些典型问题如下: + + 在本章中,我们只处理简单的 + 数值数据 + 数值数据 + 数值数据,以便我们可以专注于构建规则 +procedures 函数 . + 将数字特征化为简单数据是一种赤裸裸的虚张声势。事实上,对数字的处理是任何编程语言中最棘手和最令人困惑的方面之一。相关的典型问题包括: 整数 实数 - 整数与实数 - 一些计算机系统区分出整数,例如2,与实数,例如2.71。实数2.00与整数2不同吗?用于整数的算术运算与用于实数的运算相同吗?6除以2得3,还是3.0?我们可以表示多大的数字?我们可以表示多少位小数?整数的范围与实数的范围相同吗? + 数字整数与实数 + 一些计算机系统区分整数(如 2)与实数(如 2.71)。实数 2.00 是否与整数 2 不同?用于整数的算术运算是否与用于实数的运算相同?6 除以 2 产生 3 还是 3.0?我们可以表示多大的数字?我们可以表示多少位小数的精度?整数的范围是否与实数的范围相同? 数值分析 舍入误差 截断误差 - 当然,除了这些问题之外,还有一系列关于舍入和截断误差的问题即整个数值分析科学。由于本书的重点是大型程序设计,而不是数值技术,我们将忽略这些问题。本章中的数值示例将展示通常在使用保持非整数操作中的有限小数位精度的算术运算时观察到的舍入行为。 - - 在后面的章节中,我们将看到这些相同的规则也允许我们构建 + 当然,除了这些问题之外,还有一系列与舍入和截断误差有关的问题——数值分析的整个科学。由于本书的重点是大规模程序设计,而不是数值技术,我们将忽略这些问题。本章中的数值示例将展示使用保持有限的十进制精度进行非整数运算时通常观察到的舍入行为。 + + 在后面的章节中,我们将看到这些相同的规则允许我们构建 + - 过程 + 过程 函数 - 来操作复合数据。 - 编程元素 -
\ No newline at end of file + 以操作复合数据。 + 编程要素 + + + + &subsection1.1.1; + + + &subsection1.1.2; + + + &subsection1.1.3; + + + &subsection1.1.4; + + + &subsection1.1.5; + + + &subsection1.1.6; + + + &subsection1.1.7; + + + &subsection1.1.8; + + diff --git a/xml/cn/chapter1/section1/subsection1.xml b/xml/cn/chapter1/section1/subsection1.xml new file mode 100644 index 000000000..0e4eee2ef --- /dev/null +++ b/xml/cn/chapter1/section1/subsection1.xml @@ -0,0 +1,500 @@ + + 表达式 + + + + 开始编程的一种简单方法是检查与解释器的 + + Scheme 方言的 Lisp. + JavaScript 语言. + + + + 想象你正坐在一个 + 计算机终端上。 + + + 你输入 + + 一个表达式, + 一个语句, + + 然后解释器通过显示 + 其求值该 + + 表达式的结果. + + 语句 + 语句. + + + + + + + + 在本版本中,鼠标点击具有深色背景的 + JavaScript 语句的方式编程为显示 + JavaScript 解释器,该解释器可以求值该 + 语句并显示结果值。 + 顺便说一下,使鼠标点击 + JavaScript 语句显示解释器的程序本身 + 是用 JavaScript 编写的;它称为鼠标点击的脚本。这样的脚本在 + JavaScript 的最初设计中是一个核心目标。 + + + + + + + + 数字在 Lisp 中Lisp + 原语表达式数字 + 你可能输入的一种原语表达式是一个数字。 + + + 你可能输入的一种语句是一个 + 表达式语句,它由一个 + 表达式后接一个分号组成。 + 数字在 JavaScript 中JavaScript + 原语表达式数字 + 表达式语句 + 表达式 + 分号 (;)结束语句 + 一种原语表达式是一个数字。 + + + (更准确地说,你输入的表达式由表示数字的十进制数构成。) + + 如果你提供 + + Lisp 与一个数字 + + JavaScript 与程序 + + + + +486 + + +486; + + + 解释器将通过打印来响应在本书中, + 我们区分 + 本书中的表示法斜体字符表示解释器响应 + 用户输入的文本和解释器打印的任何文本,后者用斜体字符显示。 + + +486 + + + + + 如果你要求我们的脚本求值表达式语句 + + +486; + + + 通过点击它,它将通过显示一个 JavaScript 解释器来响应, + 并提供一个选项, + 通过按下 运行 按钮来求值该语句。 + 点击原语表达式语句,看看会发生什么! + + + + + + Expressions representing numbers may be combined with an + compound expression + combination + primitive expressionname of primitive procedure + expression representing a + arithmeticprimitive procedures for + primitive procedures (those marked ns are not in the IEEE Scheme standard)+{\tt +} + primitive procedures (those marked ns are not in the IEEE Scheme standard)*{\tt *} + primitive procedure (such as + + or *) to + form a compound expression that represents the application of the + procedure to those numbers. For example, + + +(+ 137 349) + + +486 + + + + primitive procedures (those marked ns are not in the IEEE Scheme standard)-{\tt -} + +(- 1000 334) + + +666 + + + + + +(* 5 99) + + +495 + + + + primitive procedures (those marked ns are not in the IEEE Scheme standard)/{\tt /} + +(/ 10 5) + + +2 + + + + +(+ 2.7 10) + + +12.7 + + + + + + + 表示数字的表达式可以与运算符组合, + (例如 + +作为数值加法运算符 + + (用于数值加法) + 如 + + 算术运算符 + -1* (乘法运算符) + -1* (乘法) + 或*) 形成一个 + 复合表达式 + 运算符组合 + 的复合表达式,该表达式表示对应的原语 + 函数对这些数字的应用。例如, + + 通过点击以下任一表达式语句进行求值: + + + +137 + 349; + + +486 + + + + +1000 - 334; + + +666 + + + + +5 * 99; + + +495 + + + + / (除法运算符) + / (除法) + +10 / 4; + + +2.5 + + + + +2.7 + 10; + + +12.7 + + + + + + + + + 这些表达式通过 + 括号限定组合 + 限定表达式列表 + 在括号内,以表示 + 过程应用表示组合 + 过程应用, + 被称为 组合。列表中的最左侧 + 元素被称为 + 组合的运算符 + 运算符,而其他 + 元素被称为 + 组合的操作数 + 操作数。组合的 + 组合的值 + 通过将运算符指定的过程应用于 + 参数 + 参数 (其值为操作数)得到。 + + + + + 诸如此类的表达式,其中包含其他表达式 + 作为组成部分,被称为 组合。 + 组合 + 由一个 + 组合的运算符 + 运算符组合 + 运算符 符号在中间构成,并且 + 组合的操作数 + 操作数 表达式位于其左侧和右侧的, + 被称为 + 运算符组合。 + 表达式的值 + 运算符组合的值是 + 通过将运算符指定的函数应用于 + 参数(其值为操作数)得到的。 + + + + + + + The convention of placing the operator to the left of the operands is + known as + prefix notation + 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 + procedurearbitrary number of arguments + argument(s)arbitrary number of + procedures that may take an arbitrary + number of arguments, as in the following examples: + + +(+ 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. + + + A second advantage of prefix notation is that it extends in a + straightforward way to allow combinations to be + nested combinations + nested, that is, to have combinations whose elements are + themselves combinations: + + +(+ (* 3 5) (- 10 6)) + + +19 + + + + + + +将运算符放置在操作数之间的惯例称为 +中缀运算符 +中缀表示法 +中缀表示法。它遵循您在学校和日常生活中最有可能熟悉的数学表示法。 +与数学一样,运算符组合可以是嵌套的,也就是说,它们可以有 +嵌套运算符组合 +本身是运算符组合的操作数: + + +(3 * 5) + (10 - 6); + + +19 + + +如常, +括号用于组合运算符 +括号用于组合运算符,以避免歧义。当省略括号时,JavaScript 也遵循通常的约定:乘法和除法的优先级高于加法和减法。例如, + + +3 * 5 + 10 / 2; + + +代表 + + +(3 * 5) + (10 / 2); + + +我们说 * 和 +/ 的 +优先级运算符的运算符 +优先级较高 +于 + 和 +-。加法和 +减法的顺序从左到右阅读,乘法和除法的顺序也是如此。因此, + + -as numeric subtraction operator + - (numeric subtraction operator) + -6 + +1 - 5 / 2 * 4 + 3; + + +我们说 * 和 +/ 的 +优先级运算符的运算符 +优先级较高 +于 + 和 +-。加法和 +减法的顺序从左到右阅读,乘法和除法的顺序也是如此。因此, + + +(1 - ((5 / 2) * 4)) + 3; + + +我们说运算符 ++、 +-、 +* 和 +/ 是 +结合性运算符的运算符 +左结合 +左结合。 + + + + + + + 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 + pretty-printing, in which each long combination is written so + that the operands are aligned vertically. The resulting + indentation + nested combinations + combination + indentations + display clearly the structure of the expression.Lisp systems + typically provide + formatting input expressions + typing input expressions + features to aid the user in formatting expressions. Two especially + useful features are one that automatically indents to the proper + pretty-print position whenever a new line is started and one that + highlights the matching left parenthesis whenever a right parenthesis + is typed. + + + + + 这种嵌套的深度和 JavaScript 解释器可以求值的整体复杂度原则上没有限制。困惑的往往是我们人类,对于仍然相对简单的表达式,如 + + 57 + +3 * 2 * (3 - 5 + 4) + 27 / 6 * 10; + + + 解释器会迅速求值得到 57。我们可以通过将表达式写成如下形式来帮助自己 + + +3 * 2 * (3 - 5 + 4) ++ +27 / 6 * 10; + + + 从视觉上分离表达式的主要组成部分。 + + + + +即使对于复杂的表达式,解释器始终在相同的基本循环中运行:它从终端读取 + + 一个表达式, + 用户输入的一个语句, + +求值 + + 表达式, + 语句, + +并打印结果。这种操作模式通常用来表示解释器在 + + + 读取-求值-打印循环 + 解释器读取-求值-打印循环 + 读取-求值-打印循环。 + + + 读取-求值-打印循环 + 解释器读取-求值-打印循环 + 读取-求值-打印循环。 + + +特别要注意的是,不必明确指示解释器打印 + + + 表达式。Lisp 遵循每个 + Perlis, Alan J.双关语 + Wilde, Oscar (Perlis 的化用) + 表达式的值 + Lisp效率 + 表达式都有值的惯例。这一惯例,加上 Lisp 一直以来作为低效语言的声誉,成为了 Alan Perlis(化用 Oscar Wilde)所说的 + Lisp 程序员知道一切的价值,但不知道任何的成本。 + + + 语句。JavaScript 遵循每个 + Perlis, Alan J.双关语 + Wilde, Oscar (Perlis 的化用) + 表达式的值 + Lisp效率 + 语句都有值的惯例(见练习)。这一惯例,加上 JavaScript 程序员不太关心效率的声誉,促使我们化用 Alan Perlis 对 Lisp 程序员的双关语(他自己也是在化用 Oscar Wilde):JavaScript 程序员知道一切的价值,但不知道任何的成本。 + + + + diff --git a/xml/cn/chapter1/section1/subsection2.xml b/xml/cn/chapter1/section1/subsection2.xml index fed21578f..71296afa7 100644 --- a/xml/cn/chapter1/section1/subsection2.xml +++ b/xml/cn/chapter1/section1/subsection2.xml @@ -1,812 +1,56 @@ - 命名与环境命名与环境 - 命名与环境 + 命名与环境 - - 编程语言的一个关键方面是它提供的使用 - 命名计算对象 - 名称来引用计算 - - 对象。 - 对象,我们的第一种方式是常量。 - - - 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 - 我们说 - 原始表达式常量名称 - 名称识别一个 - 常量(在JavaScript中) - - 变量 - 常量 - - 其中的 - - - 变量的值 - - - 常量(在JavaScript中)的值 - - - 是对象。 - Naming and the Environment - - - - A critical aspect of a programming language is the means it provides - for using - 命名计算对象 - 名称来引用计算 - - 对象。 - 对象,我们的第一种方式是常量。 - - - 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 - 我们说 - 原始表达式常量名称 - 名称识别一个 - 常量(在JavaScript中) - - 变量 - 常量 - - 其中的 - - - 变量的值 - - - 常量(在JavaScript中)的值 - - - 是对象。 - - - - - 在Lisp的Scheme方言中,我们用 - define - 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} - define命名。 - - - 在JavaScript中,我们用 - 常量声明 - 声明常量常量(const) - 语法形式常量声明 - const(关键字) - 关键字constconst - 分号(;)语句结束 - 常量声明来命名。 - - - - var_size - -(define size 2) - - -const size = 2; - - - causes the interpreter to associate the value 2 with the - name size. - - - - 本书中,我们没有 - 定义 - 未指定值定义define - 显示解释器对 - 计算定义的响应,因为这是依赖于具体实现的。 - - - 本书中,我们没有显示解释器对 - 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 - - - 一旦名称 size - has been associated with the number 2, we can - refer to the value 2 by name: - - size_use_1 - var_size - 2 - -size - - -2 - - -size; - - -2 - - - - size_use_2 - var_size - 10 - -(* 5 size) - - -10 - - -5 * size; - - -10 - - - Naming and the Environment - - - - A critical aspect of a programming language is the means it provides - for using - 命名计算对象 - 名称来引用计算 - - 对象。 - 对象,我们的第一种方式是常量。 - - - 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 - 我们说 - 原始表达式常量名称 - 名称识别一个 - 常量(在JavaScript中) - - 变量 - 常量 - - 其中的 - - - 变量的值 - - - 常量(在JavaScript中)的值 - - - 是对象。 - - - - - 在Lisp的Scheme方言中,我们用 - define - 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} - define命名。 - - - 在JavaScript中,我们用 - 常量声明 - 声明常量常量(const) - 语法形式常量声明 - const(关键字) - 关键字constconst - 分号(;)语句结束 - 常量声明来命名。 - - - - var_size - -(define size 2) - - -const size = 2; - - - causes the interpreter to associate the value 2 with the - name size. - - - - 本书中,我们没有 - 定义 - 未指定值定义define - 显示解释器对 - 计算定义的响应,因为这是依赖于具体实现的。 - - - 本书中,我们没有显示解释器对 - 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 - - - 一旦名称 size - has been associated with the number 2, we can - refer to the value 2 by name: - - size_use_1 - var_size - 2 - -size - - -2 - - -size; - - -2 - - - - size_use_2 - var_size - 10 - -(* 5 size) - - -10 - - -5 * size; - - -10 - - - - - - - - JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 - - var_size - -5 * size; - - - 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: - - 10 - -const size = 2; -5 * size; - - - - - - Naming and the Environment - - - - A critical aspect of a programming language is the means it provides - for using - 命名计算对象 - 名称来引用计算 - - 对象。 - 对象,我们的第一种方式是常量。 - - - 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 - 我们说 - 原始表达式常量名称 - 名称识别一个 - 常量(在JavaScript中) - - 变量 - 常量 - - 其中的 - - - 变量的值 - - - 常量(在JavaScript中)的值 - - - 是对象。 - - - - - 在Lisp的Scheme方言中,我们用 - define - 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} - define命名。 - - - 在JavaScript中,我们用 - 常量声明 - 声明常量常量(const) - 语法形式常量声明 - const(关键字) - 关键字constconst - 分号(;)语句结束 - 常量声明来命名。 - - - - var_size - -(define size 2) - - -const size = 2; - - - causes the interpreter to associate the value 2 with the - name size. - - - - 本书中,我们没有 - 定义 - 未指定值定义define - 显示解释器对 - 计算定义的响应,因为这是依赖于具体实现的。 - - - 本书中,我们没有显示解释器对 - 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 - - - 一旦名称 size - has been associated with the number 2, we can - refer to the value 2 by name: - - size_use_1 - var_size - 2 - -size - - -2 - - -size; - - -2 - - - - size_use_2 - var_size - 10 - -(* 5 size) - - -10 - - -5 * size; - - -10 - - - - - - - - JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 - - var_size - -5 * size; - - - 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: - - 10 - -const size = 2; -5 * size; - - - - - - - - Here are further examples of the use of - - - define: - - - const: - - - - - pi - -(define pi 3.14159) - - -const pi = 3.14159; - - - - radius - -(define radius 10) - - -const radius = 10; - - - - pi_radius_radius - 314.159 - pi - radius - -(* pi (* radius radius)) - - -314.159 - - -pi * radius * radius; - - -314.159 - - - - circumference_definition - pi - radius - -(define circumference (* 2 pi radius)) - - -const circumference = 2 * pi * radius; - - - - 62.8318 - circumference_use - circumference_definition - -circumference - - -62.8318 - - -circumference; - - -62.8318 - - - Naming and the Environment - - - - A critical aspect of a programming language is the means it provides - for using - 命名计算对象 + + 编程语言的一个关键方面是它提供的手段,用于使用 + 命名计算对象的 名称来引用计算 - 对象。 - 对象,我们的第一种方式是常量。 + 对象。 + 对象,我们的第一个这样的手段是常量 - 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 + 第二个这样的手段是变量,在语法上与JavaScript中的常量有区别,并在第3章中介绍。 我们说 - 原始表达式常量名称 - 名称识别一个 + 原语表达式常量的名称 + 名称标识一个 常量(在JavaScript中) - 变量 + 变量 常量 - 其中的 + 其 - + 变量的值 - + 常量(在JavaScript中)的值 是对象。 - - - - - 在Lisp的Scheme方言中,我们用 - define - 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} - define命名。 - - - 在JavaScript中,我们用 - 常量声明 - 声明常量常量(const) - 语法形式常量声明 - const(关键字) - 关键字constconst - 分号(;)语句结束 - 常量声明来命名。 - - - - var_size - -(define size 2) - - -const size = 2; - - - causes the interpreter to associate the value 2 with the - name size. - - - - 本书中,我们没有 - 定义 - 未指定值定义define - 显示解释器对 - 计算定义的响应,因为这是依赖于具体实现的。 - - - 本书中,我们没有显示解释器对 - 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 - - - 一旦名称 size - has been associated with the number 2, we can - refer to the value 2 by name: - - size_use_1 - var_size - 2 - -size - - -2 - - -size; - - -2 - - - - size_use_2 - var_size - 10 - -(* 5 size) - - -10 - - -5 * size; - - -10 - - - - - - - - JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 - - var_size - -5 * size; - - - 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: - - 10 - -const size = 2; -5 * size; - - - - - - + - Here are further examples of the use of - - define: - - - const: - - - - - pi - -(define pi 3.14159) - - -const pi = 3.14159; - - - - radius - -(define radius 10) - - -const radius = 10; - - - - pi_radius_radius - 314.159 - pi - radius - -(* pi (* radius radius)) - - -314.159 - - -pi * radius * radius; - - -314.159 - - - - circumference_definition - pi - radius - -(define circumference (* 2 pi radius)) - - -const circumference = 2 * pi * radius; - - - - 62.8318 - circumference_use - circumference_definition - -circumference - - -62.8318 - - -circumference; - - -62.8318 - - - - - - - 抽象方式definedefine - Define - - - 常量 - 抽象方式常量声明为 - 声明 - - - 是我们语言 - 最简单的抽象方式,因为它允许我们使用简单的名字来引用复合操作的结果,例如 - circumference computed above. - In general, computational objects may have very complex - structures, and it would be extremely inconvenient to have to remember - and repeat their details each time we want to use them. Indeed, - complex programs are constructed by building, step by step, - computational objects of increasing complexity. The - interpreter makes this step-by-step program construction particularly - convenient because name-object associations can be created - incrementally in successive interactions. This feature encourages the - 程序的增量开发 - 程序增量开发 - 程序的增量开发和测试在很大程度上 - 负责 - 程序结构 - - - Lisp - - - JavaScript - - - 程序通常由大量相对简单的 - - - 过程。 - - - 函数。 - - - Naming and the Environment - - - - A critical aspect of a programming language is the means it provides - for using - 命名计算对象 - 名称来引用计算 - - 对象。 - 对象,我们的第一种方式是常量。 - - - 第二种方式是变量,在JavaScript中语法上与常量不同,并在第三章介绍。 - 我们说 - 原始表达式常量名称 - 名称识别一个 - 常量(在JavaScript中) - - 变量 - 常量 - - 其中的 - - - 变量的值 - - - 常量(在JavaScript中)的值 - - - 是对象。 - - - - - 在Lisp的Scheme方言中,我们用 - define - 特殊形式(那些标记为ns的不在IEEE Scheme标准中)define{\tt define} - define命名。 - - - 在JavaScript中,我们用 + + 在 Lisp 的 Scheme 方言中,我们使用 + 定义 + 特殊形式(标记ns的并不在 IEEE Scheme 标准中)定义{\tt define} + 定义。 + + + 在 JavaScript 中,我们使用 常量声明 - 声明常量常量(const) + 声明常量的常量 (const) 语法形式常量声明 const(关键字) 关键字constconst - 分号(;)语句结束 - 常量声明来命名。 + 分号 (;)结束语句 + 常量声明 - + var_size (define size 2) @@ -814,27 +58,27 @@ circumference; const size = 2; - - causes the interpreter to associate the value 2 with the - name size. - + + 使解释器将值 2 与名称关联 +size. + - - 本书中,我们没有 - 定义 - 未指定值定义define + + 在这本书中,我们不 + 定义值的 + 未指定值定义定义 显示解释器对 - 计算定义的响应,因为这是依赖于具体实现的。 - + 评估定义的响应,因为这高度依赖于实现。 + - 本书中,我们没有显示解释器对 - 计算以声明结束的程序的响应,因为这可能依赖于前面的语句。详情请参见习题。 + 在这本书中,我们不显示解释器对 + 评估以声明结束的程序的响应,因为这可能依赖 + 于之前的语句。有关详细信息,请参见练习 - 一旦名称 size - has been associated with the number 2, we can - refer to the value 2 by name: - + 一旦名称 size + 已与数字 2 关联后,我们可以通过名称引用值 2: + size_use_1 var_size 2 @@ -869,66 +113,71 @@ size; - + - JavaScript解释器需要在变量名size被用于表达式之前执行常量声明。为了简洁,在本在线书中,要求在新语句之前计算的语句被省略。然而,要查看和运行程序,您可以点击它。程序将显示在新的浏览器标签页中,并带有显示依赖项选项。因此,点击 - + JavaScript 解释器需要在名称 size 可用于表达式之前, + 执行 常量声明 以声明 size。 + 在这本在线书中,为了简洁,省略了需要在新语句之前评估的语句。 + 然而,您可以单击它以查看和操作程序。 + 然后程序将在新的浏览器标签中出现,并提供选项 显示依赖项。 + 因此,点击 + var_size 5 * size; - - 时,将出现一个新标签页,包含程序,并在点击显示依赖项后,您将看到: - - 10 + + 后将出现一个新标签,包含程序,点击 显示依赖项 后,您将看到: + + 10 const size = 2; 5 * size; - + - - - Here are further examples of the use of - - - define: - + + + 下面是使用 + + + 定义: + - const: + const: - + pi - -(define pi 3.14159) - + +(定义 pi 3.14159) + const pi = 3.14159; - - + + radius - -(define radius 10) - + +(定义 radius 10) + const radius = 10; - - + + pi_radius_radius 314.159 pi radius - + (* pi (* radius radius)) - + 314.159 @@ -938,25 +187,25 @@ pi * radius * radius; 314.159 - - + + circumference_definition pi radius - -(define circumference (* 2 pi radius)) - + +(定义 circumference (* 2 pi radius)) + const circumference = 2 * pi * radius; - - + + 62.8318 circumference_use circumference_definition - + circumference - + 62.8318 @@ -966,71 +215,62 @@ circumference; 62.8318 - - - + + + - - 抽象方式definedefine - Define - + + 抽象的方法定义定义 + 定义 + 常量 - 抽象方式常量声明为 + 抽象的方法常量声明作为 声明 - 是我们语言 - 最简单的抽象方式,因为它允许我们使用简单的名字来引用复合操作的结果,例如 - circumference computed above. - In general, computational objects may have very complex - structures, and it would be extremely inconvenient to have to remember - and repeat their details each time we want to use them. Indeed, - complex programs are constructed by building, step by step, - computational objects of increasing complexity. The - interpreter makes this step-by-step program construction particularly - convenient because name-object associations can be created - incrementally in successive interactions. This feature encourages the - 程序的增量开发 - 程序增量开发 - 程序的增量开发和测试在很大程度上 - 负责 + 是我们语言的最简单抽象手段,因为它使我们能够使用简单的名称来引用复合操作的结果,例如上面计算的 + 周长。 + 通常,计算对象可能具有非常复杂的结构,每次想要使用它们时,需要记住和重复其细节将极为不便。实际上,复杂程序的构建是逐步构建逐渐复杂的计算对象。 + 解释器使这种逐步的程序构建特别方便,因为名称与对象的关联可以在后续交互中逐步创建。 + 这一特性鼓励 + 程序的增量开发 + 程序的增量开发 + 的增量开发和测试,主要原因在于 程序结构 - + Lisp - + JavaScript 程序通常由大量相对简单的 - - 过程。 - + + 过程构成。 + - 函数。 + 函数构成。 - - - It should be clear that the possibility of associating values with - names and later retrieving them means that the interpreter must - maintain some sort of memory that keeps track of the name-object - pairs. This memory is called the - 环境 + + + 很明显,值与名称的关联及其后来的检索意味着解释器必须维持某种内存来跟踪名称-对象对。这种内存被称为 + 环境 环境 - (更准确地说是 + (更准确地说是 - + 全局环境 全局环境, - + 程序环境 程序环境 - 因为我们稍后会看到,一个计算可能涉及多个不同的环境)。Chapter将展示这个环境概念对于理解解释器如何工作至关重要。Chapter将使用环境来实现解释器。 - \ No newline at end of file + 因为我们稍后会看到,一个计算可能涉及多个不同的环境)。章将展示这种环境的概念在理解解释器如何工作中是至关重要的。第章将使用环境来实现解释器。 + + diff --git a/xml/cn/chapter1/section1/subsection3.xml b/xml/cn/chapter1/section1/subsection3.xml index be8788e08..2485e22ed 100644 --- a/xml/cn/chapter1/section1/subsection3.xml +++ b/xml/cn/chapter1/section1/subsection3.xml @@ -1,215 +1,77 @@ - + - - 组合的评估 - 评估组合的 - + + combinationevaluation of + evaluationof a combination + - 操作符组合的评估 - 评估操作符组合的 - - - - - 组合的评估 - 评估组合的 - - - 操作符组合的评估 - 评估操作符组合的 - - - - 评估 - - 组合 - 操作符组合 - - - - - 组合的评估 - 评估组合的 - - - 操作符组合的评估 - 评估操作符组合的 + operator combinationevaluation of + evaluationof operator combination - 评估 + Evaluating - 组合 - 操作符组合 + Combinations + Operator Combinations - - - - 组合的评估 - 评估组合的 - - - 操作符组合的评估 - 评估操作符组合的 - - - - Evaluating - - 组合 - 操作符组合 - - - - + One of our goals in this chapter is to isolate issues about thinking procedurally. As a case in point, let us consider that, in evaluating - - - 操作符 - - - 组合, 解释器本身在遵循一个过程。 - - 由于关键字 - function很重要, 我们通常将对"过程/过程式"的引用替换为 - 对"函数/函数式"的引用。上述句子是个例外; - 术语"过程性思维"和"过程"或许在此JavaScript版中仍旧适用。 - -
    -
  • - 要评估 - - - 一个组合, - - - 一个操作符组合, - - - 请执行以下操作: -
      -
    1. 评估 - - - 子表达式 - - - 操作数表达式 - - - 组合的。
    2. -
    3. - - - 应用 - 该过程 - 就是左边第一个 - 子表达式(操作符)的值,并应用于其它子表达式(操作数)的值。 - - - 应用该函数,该函数由操作符表示,并应用于 - 操作数的值。 - - -
    4. -
    -
  • -
- - Scheme版本不区分操作符和应用组合。然而,由于中缀表示法, - JavaScript版本需要为这两者描述稍有不同的规则。本节包含了操作符 - 组合的规则,而1.1.5节为函数应用引入了新的规则。 - - 即使是这个简单的规则也说明了一些关于 - 流程的重要点。首先可以看到,第一步规定 - 为实现对组合的评估过程,我们必须首先对组合的 - 每个操作数执行评估过程。因此,评估规则是 - 递归 - 递归性质的; - 也就是说,其步骤之一包括需要调用规则 - 本身。可能看起来奇怪的是评估 - 规则在第一步中说,应该评估组合的最左边 - 元素,因为此时该元素只能是一个操作符 - 例如+* - 代表内置基本过程如加法或 - 乘法。我们稍后会看到可以使用 - 其操作符本身是复合表达式的组合这点很有用。 - -
- - - 组合的评估 - 评估组合的 - - - 操作符组合的评估 - 评估操作符组合的 - - - - Evaluating - - 组合 - 操作符组合 - - - - - - One of our goals in this chapter is to isolate issues about thinking - procedurally. As a case in point, let us consider that, in evaluating - + - 操作符 + operator - 组合,解释器本身在遵循一个过程。 + combinations, the interpreter is itself following a procedure. - 由于关键字 - function的重要性,我们通常将对"过程/过程式"的引用替换为 - 对"函数/函数式"的引用。上述句子是一个例外; - 术语"过程性思考"和"过程"可能在这里的JavaScript版本中仍然适用。 + Due to the prominence of the keyword + function, we are generally replacing + references to "procedure/procedural" with references to + "function/functional". The above sentences are an exception; + the terms "thinking procedurally" and "procedure" are perhaps still + adequate for the JavaScript edition here.
  • - 要评估 + To evaluate - - 一个组合, - + + a combination, + - 一个操作符组合, + an operator combination, - 请执行以下操作: + do the following:
      -
    1. 评估 +
    2. Evaluate the - - 子表达式 - + + subexpressions + - 操作数表达式 + operand expressions - 组合的。
    3. + of the combination.
    4. - - 应用 - 该过程 - 就是最左边 - 子表达式(操作符)的值,应用于其他子表达式(操作数)的值。 - + + Apply the + procedure + that is the value of the leftmost + subexpression (the operator) to the arguments that are the + values of the other subexpressions (the operands). + - 应用由 - 操作符表示的函数,将其应用于 - 操作数的值。 + Apply the function that is denoted by + the operator to the arguments that are the values of + the operands.
    5. @@ -217,38 +79,40 @@
- Scheme版本不区分操作符和 - 应用组合。然而,由于中缀表示法, - JavaScript版本需要为这两者描述稍微不同的 - 规则。本节包含操作符 - 组合的规则,而1.1.5节引入了函数 - 应用的新规则。 + The Scheme version does not distinguish between operator and + application combinations. However, due to the infix notation, + the JavaScript version needs to describe slightly different + rules for those two. This section contains the rule for operator + combination, and section 1.1.5 introduces a new rule for function + application. - 即使是这个简单的规则也说明了一些关于 - 过程的重要点。首先可以观察到,第一步规定 - 为了完成组合的评估过程,我们必须首先对 - 组合的每个操作数进行评估过程。因此,评估规则是 - 递归 - 递归的性质; - 也就是说,它包括的其中一个步骤需要调用规则 - 本身。评估规则可能看起来很奇怪, - 因为在第一步中,它说我们应该评估一个组合的最左边 - 元素,因为在这一点上那只能是一个操作符 - 例如+* - 代表内建基本过程如加法或 - 乘法。稍后我们将看到可以与 - 操作符本身是复合表达式的组合进行工作是有用的。 - -
+ Even this simple rule illustrates some important points about + processes in general. First, observe that the first step dictates + that in order to accomplish the evaluation process for a + combination we must first perform the evaluation process on each + operand of the combination. Thus, the evaluation rule is + recursion + recursive in nature; + that is, it includes, as one of its steps, the need to invoke the rule + itself.It may seem strange that the evaluation + rule says, as part of the first step, that we should evaluate the leftmost + element of a combination, since at this point that can only be an operator + such as + or * + representing a built-in primitive procedure such as addition or + multiplication. We will see later that it is useful to be able to work with + combinations whose operators are themselves compound expressions. + + Notice how succinctly the idea of recursion can be used to express - 递归表示复杂过程 - 在深入嵌套的组合情况下,否则将被视为相当复杂的过程。例如,评估 - - + recursionexpressing complicated process + what, in the case of a deeply nested combination, would otherwise be + viewed as a rather complicated process. For example, evaluating + + (* (+ 2 (* 4 6)) (+ 3 5 7)) - + (2 + 4 * 6) * (3 + 12); @@ -256,205 +120,296 @@ requires that the evaluation rule be applied to four different combinations. We can obtain a picture of this process by representing the combination in the form of a - 操作符组合作为树 - 被视为组合 - 树,如图 + operator combinationtreeas a tree + treecombination viewed as + tree, as shown in - - 图. - + + figure. + - 图. + figure. - 每个组合由一个 - 树的节点 - 节点表示,其 - 树的分支 - 分支对应于操作符和 - 从中分支出的组合的操作数。 - 该 - 树的终端节点 - 终端节点(即没有 - 从中分支出的节点)表示操作符或数字。 - 从树的角度来看评估,我们可以想象操作数的 - 值从终端节点开始向上流动,然后在更高的层级进行组合。一般来说,我们 - 将看到递归是一种处理 - 层次结构、树状对象的非常有力的技术。事实上,向上渗透值形式的评估规则是 - 一种被称为 - 树积累 - 树积累的一般过程的示例。 + Each combination is represented by a + node of a tree + node with + branch of a tree + branches corresponding to the operator and the + operands of the combination stemming from it. + The + terminal node of a tree + terminal nodes (that is, nodes with + no branches stemming from them) represent either operators or numbers. + Viewing evaluation in terms of the tree, we can imagine that the + values of the operands percolate upward, starting from the terminal + nodes and then combining at higher and higher levels. In general, we + shall see that recursion is a very powerful technique for dealing with + hierarchical, treelike objects. In fact, the percolate values + upward form of the evaluation rule is an example of a general kind + of process known as + tree accumulation + tree accumulation. - -
+ +
- 树表示法,显示每个子组合的值。 + Tree representation, showing the value of each subcombination.
- +
-
+
- 树表示法,显示每个子表达式的值。 + Tree representation, showing the value of each subexpression.
- - - - 组合的评估 - 评估组合的 - - - 操作符组合的评估 - 评估操作符组合的 - - - - Evaluating - - 组合 - 操作符组合 - - - - - - One of our goals in this chapter is to isolate issues about thinking - procedurally. As a case in point, let us consider that, in evaluating - - - - Notice how succinctly the idea of recursion can be used to express - - - -(* (+ 2 (* 4 6)) - (+ 3 5 7)) - - -(2 + 4 * 6) * (3 + 12); - - - requires that the evaluation rule be applied to four different - combinations. We can obtain a picture of this process by - representing the combination in the form of a - - + Next, observe that the repeated application of the first step brings us to the point where we need to evaluate, not combinations, but primitive expressions such as - - - - - - - Evaluating - - - - - - One of our goals in this chapter is to isolate issues about thinking - procedurally. As a case in point, let us consider that, in evaluating - - - - Notice how succinctly the idea of recursion can be used to express - - - -(* (+ 2 (* 4 6)) - (+ 3 5 7)) - + + + numerals, built-in operators, or other names. + -(2 + 4 * 6) * (3 + 12); + numerals or names. - - requires that the evaluation rule be applied to four different - combinations. We can obtain a picture of this process by - representing the combination in the form of a - - - - - - Next, observe that the repeated application of the first step brings - us to the point where we need to evaluate, not combinations, but - primitive expressions such as - - - - - Notice that the - evaluation rule given above does not handle - -x and the other of which is - 3, since the purpose of the - -x with a value. - (That is, - - - - - - Evaluating - - - - - - One of our goals in this chapter is to isolate issues about thinking - procedurally. As a case in point, let us consider that, in evaluating - - - - Notice how succinctly the idea of recursion can be used to express - - - -(* (+ 2 (* 4 6)) - (+ 3 5 7)) - + + We take care of the primitive cases + primitive expressionevaluation of + evaluationof primitive expression + by stipulating that +
    +
  • + the values of numerals are the numbers that they name, + and +
  • + + +
  • + the values of built-in operators are the machine + instruction sequences that carry out the corresponding operations, + and +
  • + +
    + + Operators are not values in JavaScript, so this item does not apply + in the JavaScript version. + +
  • + the values of + + + other + + + names are the objects associated + with those names in the environment. +
  • +
+ + + We may regard the second rule as a special case of the third one by + stipulating that symbols such as + + and * are also included + in the global environment, and are associated with the sequences of + machine instructions that are their values. + + + The key point to + notice is the role of the + environmentcontextas context for evaluation + environment in determining the meaning of + the + + + symbols + -(2 + 4 * 6) * (3 + 12); + names -
- requires that the evaluation rule be applied to four different - combinations. We can obtain a picture of this process by - representing the combination in the form of a - -
- - - - Next, observe that the repeated application of the first step brings - us to the point where we need to evaluate, not combinations, but - primitive expressions such as - - - + + in expressions. In an interactive language such as + + + Lisp, + + + JavaScript, + + + it is meaningless to speak of the value of an expression such as + + + (+ x 1) + + + x + 1 + + + without specifying any information about the environment + that would provide a meaning for the + + + symbol x (or even for the + symbol +). + + + name x. + + + As we shall see in chapter 3, the general notion of + the environment as providing a context in which evaluation takes place + will play an important role in our understanding of program execution. + + + combinationevaluation of + evaluationof a combination + + + operator combinationevaluation of + evaluationof operator combination + + + Notice that the evaluation rule given above does not handle - -x and the other of which is + + + definewhy a special form + definitions. + + + constant declarationwhy a syntactic form + declarations. + + + For instance, evaluating + + + (define x 3) + + + const x = 3; + + + does not apply + + + define + + + an equality operator = + + + to two arguments, one + of which is the value of the + + + symbol + + + name + + + x and the other of which is 3, since the purpose of the - -x with a value. + + + define + + + declaration + + + is precisely to associate + x with a value. (That is, - - + + + (define x 3) + + + const x = 3; + + + is not + a combination.) + - - - - \ No newline at end of file + + + Such exceptions to the general evaluation rule are called + special form + special forms. + Define + is the only example of a special form that we + have seen so far, but we will meet others shortly. + evaluationof special forms + Each special form + has its own evaluation rule. The various kinds of expressions (each + with its associated evaluation rule) constitute the + syntaxprogrammingof a programming language + syntax of the + programming language. In comparison with most other programming + languages, Lisp has a very simple syntax; that is, the evaluation rule + for expressions can be described by a simple general rule together + with specialized rules for a small number of special + forms. + Special syntactic forms that are simply convenient + alternative surface structures for things that can be written in more + uniform ways are sometimes called syntactic sugar, to use a + Perlis, Alan J.quips + Landin, Peter + syntactic sugar + semicolon + cancer of the semicolon + Pascal + LispPascal vs. + phrase coined by Peter Landin. In comparison with users of other + languages, Lisp programmers, as a rule, are less concerned with + matters of syntax. (By contrast, examine any Pascal manual and notice + how much of it is devoted to descriptions of syntax.) This disdain + for syntax is due partly to the flexibility of Lisp, which makes it + easy to change surface syntax, and partly to the observation that many + convenient syntactic constructs, which make the language + less uniform, end up causing more trouble than they are worth when + programs become large and complex. In the words of Alan Perlis, + Syntactic sugar causes cancer of the semicolon. + + + + + The letters in const are + rendered in bold to indicate that it + + + The word const + + is a + keyword + keyword in JavaScript. Keywords carry a + particular meaning, and thus cannot be used as names. A keyword or a + combination of keywords in a statement instructs the JavaScript + interpreter to treat the statement in a special way. Each such + syntactic form + syntactic form has its own evaluation rule. The + various kinds of statements and expressions (each with its associated + evaluation rule) constitute the + syntaxprogrammingof a programming language + syntax of the programming language. + + + + + diff --git a/xml/cn/chapter1/section1/subsection4.xml b/xml/cn/chapter1/section1/subsection4.xml index 2b878ac14..9be1c95bf 100644 --- a/xml/cn/chapter1/section1/subsection4.xml +++ b/xml/cn/chapter1/section1/subsection4.xml @@ -1,1890 +1,79 @@ - + - 复合 过程 - 函数 - - - 复合 过程 - 函数 - - - - 我们在 - - Lisp - JavaScript - - 中识别了一些任何强大编程语言中必须出现的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - 复合 过程 - 函数 - - - - 我们在 - - Lisp - JavaScript - - 中识别了一些任何强大编程语言中必须出现的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - 我们首先研究如何表达 - 平方 - 的概念。我们可能会说: - - - 要平方某个东西,将其乘以自身。 - - - 要平方某个东西,将其乘以自身。 - - - - 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 - - 这在我们的语言中表示为 - - square - function (关键字) - 关键字functionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - 复合 过程 - 函数 - - - - 我们在 - - Lisp - JavaScript - - 中识别了一些任何强大编程语言中必须出现的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - 我们首先研究如何表达 - 平方 - 的概念。我们可能会说: - - - 要平方某个东西,将其乘以自身。 - - - 要平方某个东西,将其乘以自身。 - - - - 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 - - 这在我们的语言中表示为 - - square - function (关键字) - 关键字functionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - - Compound 过程 - 函数 - - - - We have identified in - - Lisp - JavaScript - - 一些必须出现在任何强大编程语言中的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - We begin by examining how to express the idea of - 平方。 - 我们可能会说, - - - 要平方某个东西,将其乘以自身。 - - - 要平方某个东西,将其乘以自身。 - - - - 在这里,为了更好地匹配JavaScript中的中缀表示法,Scheme和JavaScript的表达略有不同。 - - 这在我们的语言中表示为 - - square - function (keyword) - keywordsfunctionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - - We can understand this in the following way: - - - -(define (square x) (* x x)) -;; ^ ^ ^ ^ ^ ^ -;; 要 平方 某个东西, 将其 乘以 自身。 - - -function square( x ) { return x * x; } -// ^ ^ ^ ^ ^ ^ ^ -// 要 平方 某个东西, 将其 乘以 自身。 - - - - - - - \begin{flushleft}\normalcodesize - \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} - \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ - $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] - \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ - \end{tabular} - \end{flushleft} - - - 我们这里有一个 - - - 复合过程 - 过程复合 - 复合过程, - - - 复合函数 - 复合 - 复合函数, - - - 它被赋予了一个名称 square. The - - 过程 - 函数 - - 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, - which plays the same role that a pronoun plays in natural language. - - - 过程命名使用 define 进行命名 - 过程define 创建 - 过程命名 - - - 函数命名of functions - 使用函数声明命名 - 使用函数声明创建 - - - 评估 - - - 定义 - - - 声明 - - - 创建这个复合 - - - 过程 - - - 函数 - - - 并将其与名称关联 - 语法形式函数声明 - - 函数声明 - 声明函数函数的 (function) - square. 注意,这里结合了两种不同的操作:我们正在创建 - - - 过程, - - - 函数, - - - 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 - - - 过程 - - - 函数 - - - 而不命名它们,以及给已创建的 - - - 过程 - - - 函数 - - - 命名。在节中我们将看到如何做到这一点。 - - - Compound 过程 - 函数 - - - - We have identified in - - Lisp - JavaScript - - 一些必须出现在任何强大编程语言中的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - We begin by examining how to express the idea of - 平方。 - 我们可能会说, - - - 要平方某个东西,将其乘以自身。 - - - 要平方某个东西,将其乘以自身。 - - - - 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 - - 这在我们的语言中表示为 - - square - function (keyword) - keywordsfunctionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - - We can understand this in the following way: - - - -(define (square x) (* x x)) -;; ^ ^ ^ ^ ^ ^ -;; 要 平方 某个东西, 将其 乘以 自身。 - - -function square( x ) { return x * x; } -// ^ ^ ^ ^ ^ ^ ^ -// 要 平方 某个东西, 将其 乘以 自身。 - - - - - - - \begin{flushleft}\normalcodesize - \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} - \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ - $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] - \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ - \end{tabular} - \end{flushleft} - - - 我们这里有一个 - - - 复合过程 - 过程复合 - 复合过程, - - - 复合函数 - 复合 - 复合函数, - - - 它被赋予了一个名称 square. The - - 过程 - 函数 - - 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, - which plays the same role that a pronoun plays in natural language. - - - 过程命名使用 define 进行命名 - 过程define 创建 - 过程命名 - - - 函数命名of functions - 使用函数声明命名 - 使用函数声明创建 - - - 评估 - - - 定义 - - - 声明 - - - 创建这个复合 - - - 过程 - - - 函数 - - - 并将其与名称关联 - 语法形式函数声明 - - 函数声明 - 声明函数函数的 (function) - square. 注意,这里结合了两种不同的操作:我们正在创建 - - - 过程, - - - 函数, - - - 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 - - - 过程 - - - 函数 - - - 而不命名它们,以及给已创建的 - - - 过程 - - - 函数 - - - 命名。在节中我们将看到如何做到这一点。 - - - - - 过程定义的一般形式 - - - 函数声明的最简单形式 - - - 是 - - -(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) - - -function name(parameters) { return expression; } - - - The - - - - 过程名称过程名称 - 过程的名称 - $\langle \textit{name}\rangle$ - - - 函数名称函数名称 - 的名称 - 名称 - - - 是与环境中的 - - - 过程 - - - 函数 - - - 定义关联的符号。在整本书中,我们将 - 本书中的符号表达式语法中的斜体符号 - 语法表达式的描述 - 通过使用斜体符号来描述表达式的一般语法 以尖括号 - 包围例如, - - - $\langle \textit{name}\rangle$表示 - - - 名称表示 - - - 表达式中需要被填入的,在实际使用这样的表达式时。 - The - - - 形式参数过程的 - 形式参数 - - - 的参数 - 参数 - - - - - $\langle \textit{形式参数}\rangle$ - - - 参数 - - - 是在 - - - 过程 - - - 函数 - - - 的主体中用于引用所对应论据的名称。 - - - 过程。 - - - 函数。 - - - - - 过程的 - 主体过程的 - 过程主体 - $\langle \textit{body} \rangle$ - 是一个表达式 - 当形式参数被实际参数替换,即过程被应用到的实际参数时,该表达式将产生过程应用的值。更 - 表达式序列过程在过程主体中 - 一般来说,过程的主体可以是一个表达式序列。在这种情况下,解释器依次评估序列中的每个表达式,并将最后一个表达式的值作为过程应用的值返回。 - $\langle \textit{name} \rangle$ - 和 - $\langle \textit{formal parameters} \rangle$ - 被括在一起 - 括号过程在过程定义中 - 过程定义的 - 括号内,就像在过程的实际调用中一样。 - - - 参数 - 被括在一起 - 括号函数在函数声明中 - 括号函数在函数声明中 - 括号内并用逗号分隔,就像在函数声明的应用中一样。 - 返回语句 - 返回值 - return (关键字) - 语法结构返回语句 - 关键字returnreturn - 在最简单的形式中, - 的主体 - 函数的主体 - 主体是一个单一的函数声明 - 返回语句更 - 语句序列函数在函数主体中 - 一般来说,函数的主体可以是一系列语句。在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 - 它由关键字 - return - 后跟返回表达式组成 - 当形式参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句一样, - 返回语句 - 分号 (;)结束语句 - 声明 - 以分号结束。 - - - - - Compound 过程 - 函数 - - - - We have identified in - - Lisp - JavaScript - - 一些必须出现在任何强大编程语言中的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - We begin by examining how to express the idea of - 平方。 - 我们可能会说, - - - 要平方某个东西,将其乘以自身。 - - - 要平方某个东西,将其乘以自身。 - - - - 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 - - 这在我们的语言中表示为 - - square - function (keyword) - keywordsfunctionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - - We can understand this in the following way: - - - -(define (square x) (* x x)) -;; ^ ^ ^ ^ ^ ^ -;; 要 平方 某个东西, 将其 乘以 自身。 - - -function square( x ) { return x * x; } -// ^ ^ ^ ^ ^ ^ ^ -// 要 平方 某个东西, 将其 乘以 自身。 - - - - - - - \begin{flushleft}\normalcodesize - \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} - \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ - $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] - \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ - \end{tabular} - \end{flushleft} - - - 我们这里有一个 - - - 复合过程 - 过程复合 - 复合过程, - - - 复合函数 - 复合 - 复合函数, - - - 它被赋予了一个名称 square. The - - 过程 - 函数 - - 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, - which plays the same role that a pronoun plays in natural language. - - - 过程命名的命名 - 过程使用define命名 - 过程使用define创建 - - - 函数命名的命名 - 使用函数声明命名 - 使用函数声明创建 - - - 评估 - - - 定义 - - - 声明 - - - 创建这个复合 - - - 过程 - - - 函数 - - - 并将其与名称关联 - 语法形式函数声明 - - 函数声明 - 声明函数函数的声明 (function) - square. 注意,这里结合了两种不同的操作:我们正在创建 - - - 过程, - - - 函数, - - - 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 - - - 过程 - - - 函数 - - - 而不命名它们,以及给已创建的 - - - 过程 - - - 函数 - - - 命名。在节中我们将看到如何做到这一点。 - - - - - 过程定义的一般形式 - - - 函数声明的最简单形式 - - - 是 - - -(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) - - -function name(parameters) { return expression; } - - - The - - - - 过程名称过程的名称 - 过程的名称 - $\langle \textit{name}\rangle$ - - - 函数名称函数的名称 - 的名称 - 名称 - - - 是要与环境中的 - - - 过程 - - - 函数 - - - 定义相关联的符号。在整本书中,我们将 - 本书中的符号表达式语法中的斜体符号 - 语法表达式的语法描述 - 通过使用斜体符号来描述表达式的一般语法 用尖括号 - 分隔例如, - - - $\langle \textit{name}\rangle$表示 - - - 名称表示 - - - 表达式中需要被填入的,在实际使用这样的表达式时。 - - - 过程形式参数 - 形式参数 - - - 的参数 - 参数 - - - - - $\langle \textit{formal parameters}\rangle$ - - - 参数 - - - 是过程的主体中用来引用 - 对应的实参的名称。 - - - 过程。 - - - 函数。 - - - - - 过程的 - 主体的过程 - 过程主体 - $\langle \textit{body} \rangle$ - 是一种表达式 - 当形式参数被 - 过程 - 应用的实际参数替换时,将产生 - 的值。更 - 表达式序列过程在过程主体中 - 通常,过程的主体可以是一个表达式序列。 - 在这种情况下,解释器依次评估序列中的每个表达式,并将最终表达式的值作为过程应用的值返回。 - $\langle \textit{name} \rangle$ - 和 - $\langle \textit{formal parameters} \rangle$ - 在 - 括号过程在过程定义中 - 过程定义的 - 括号内分组,就像在实际调用要定义的过程时一样。 - - - 参数 - 在 - 括号函数在函数声明中 - 括号函数在函数声明中 - 括号内分组并用逗号分隔,就像在函数声明的应用中一样。 - 返回语句 - 返回值 - return (关键字) - 语法形式返回语句 - 关键字returnreturn - 在最简单的形式中, - 主体的 - 函数主体 - 主体 是一个单一的函数声明 - 返回语句更 - 语句序列函数在函数主体中 - 一般来说,函数的主体可以是一系列语句。 - 在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 - 它由关键字 - return - 后跟返回表达式组成 - 当 - - 形式 - - 参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句, - 返回语句 - 分号 (;)结束语句 - 声明 - 以分号结束。 - - - - - - - 定义了square之后, - 我们现在可以使用它: - - - 声明了square之后, - 我们现在可以在 - 函数应用表达式中使用它,并通过使用分号将其转化为一个语句: - - - - square_definition - -(square 21) - - -441 - - -square(21); - - -441 - - - - - - - - - 由于运算符组合在句法上与函数应用不同,JavaScript版本需要在这里明确说明函数应用的评估规则。这为下一小节的替换模型奠定了基础。 - - 函数应用是继运算符 - 组合之后的我们遇到的第二种将 - 表达式组合成更大表达式的方法。 - 函数应用的一般形式是 - - -函数表达式(参数表达式) - - - 其中应用的 - 函数表达式 - 函数表达式 - 指定了要应用于以逗号分隔的 - 参数 - 参数表达式的函数。 - 为了评估函数应用,解释器遵循 - 评估函数应用 - 函数应用的评估 - 一个过程,与 - 在节中描述的运算符组合过程非常相似。 -
    -
  • 评估函数应用时,进行以下操作: -
      -
    1. - 评估应用的子表达式,即 - 函数表达式和参数表达式。 -
    2. -
    3. - 将函数表达式的值所代表的函数 - 应用到参数表达式的值上。 -
    4. -
    -
  • -
-
-
- - square_definition - -(square (+ 2 5)) - - -49 - - -square(2 + 5); - - -49 - - - - - 这里,参数表达式本身是一个复合表达式, - 运算符组合 2 + 5。 - - - - square_square - 81 - square_definition - -(square (square 3)) - - -81 - - -square(square(3)); - - -81 - - - - - 当然,函数应用表达式也可以作为参数表达式。 - - -
- - Compound 过程 - 函数 - - - - We have identified in - - Lisp - JavaScript - - 一些必须出现在任何强大编程语言中的元素: -
    -
  • - 数字和算术运算是原始数据和 - - 过程。 - 函数。 - -
  • -
  • - 组合的嵌套提供了组合运算的方法。 -
  • -
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 -
  • -
- 现在我们将学习 - - - 过程定义 - - - 声明 - - - - - 过程定义, - - - 函数声明, - - - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
- - We begin by examining how to express the idea of - 平方。 - 我们可能会说, - - - 要平方某个东西,将其乘以自身。 - - - 要平方某个东西,将其乘以自身。 - - - - 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 - - 这在我们的语言中表示为 - - square - function (keyword) - keywordsfunctionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - - We can understand this in the following way: - - - -(define (square x) (* x x)) -;; ^ ^ ^ ^ ^ ^ -;; 要 平方 某个东西, 将其 乘以 自身。 - - -function square( x ) { return x * x; } -// ^ ^ ^ ^ ^ ^ ^ -// 要 平方 某个东西, 将其 乘以 自身。 - - - - - - - \begin{flushleft}\normalcodesize - \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} - \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ - $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] - \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ - \end{tabular} - \end{flushleft} - - - 我们这里有一个 - - - 复合过程 - 过程复合 - 复合过程, - - - 复合函数 - 复合 - 复合函数, - - - 它被赋予了一个名称 square. The - - 过程 - 函数 - - 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, - which plays the same role that a pronoun plays in natural language. - - - 过程命名的命名 - 过程使用define命名 - 过程使用define创建 - - - 函数命名的命名 - 使用函数声明命名 - 使用函数声明创建 - - - 评估 - - - 定义 - - - 声明 - - - 创建这个复合 - - - 过程 - - - 函数 - - - 并将其与名称关联 - 语法形式函数声明 - - 函数声明 - 声明函数函数的 (function) - square. 注意,这里结合了两种不同的操作:我们正在创建 - - - 过程, - - - 函数, - - - 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 - - - 过程 - - - 函数 - - - 而不命名它们,以及给已创建的 - - - 过程 - - - 函数 - - - 命名。在节中我们将看到如何做到这一点。 - - - - - 过程定义的一般形式 - - - 函数声明的最简单形式 - - - 是 - - -(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) - - -function name(parameters) { return expression; } - - - The - - - 过程名称过程名称 - 过程的名称 - $\langle \textit{name}\rangle$ - - - 函数名称函数名称 - 的名称 - 名称 - - - 是要与环境中的 - - - 过程 - - - 函数 - - - 定义相关联的符号。在整个书中,我们将 - 本书中的符号表达式语法中的斜体符号 - 语法表达式描述表达式的语法 - 通过使用斜体符号来描述表达式的一般语法 以尖括号 - 分隔例如, - - - $\langle \textit{name}\rangle$表示 - - - 名称表示 - - - 实际使用表达式时需要填入的 - - - 过程形式参数 - 形式参数 - - - 参数 - 参数 - - - - - $\langle \textit{formal parameters}\rangle$ - - - 参数 - - - 是在 - - - 过程 - - - 函数 - - - 的主体中用于引用 - - - 过程 - - - 函数 - - - 对应参数的名称。 - - - 过程。 - - - 函数。 - - - - - The - 过程的主体 - 过程的主体 - $\langle \textit{body} \rangle$ - 是一个表达式 - 当形式参数被 - 实际参数替换,过程 - 被应用时,将产生过程应用的值。更 - 表达式序列过程在过程主体中 - 通常,过程的主体可以是一系列表达式。 - 在这种情况下,解释器依次评估序列中的每个表达式,并将最终表达式的值作为过程应用的值返回。 - $\langle \textit{name} \rangle$ - 和 - $\langle \textit{formal parameters} \rangle$ - 在 - 括号过程在过程定义中 - 过程的定义 - 括号内分组,就像在实际调用要定义的过程时一样。 - - - 参数 - 在 - 括号函数在函数声明中 - 括号函数在函数声明中 - 括号内分组并用逗号分隔,就像在函数声明的应用中一样。 - 返回语句 - 返回值 - return (关键字) - 语法形式返回语句 - 关键字returnreturn - 在最简单的形式中, - 主体的 - 函数的主体 - 主体 是一个单一的函数声明 - 返回语句更 - 语句序列函数在函数主体中 - 一般来说,函数的主体可以是一系列语句。 - 在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 - 它由关键字 - return - 后跟返回表达式组成 - 当 - - 形式 - - 参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句, - 返回语句 - 分号 (;)结束语句 - 声明 - 以分号结束。 - - - - - - - 定义了square之后, - 我们现在可以使用它: - - - 声明了square之后, - 我们现在可以在 - 函数应用表达式中使用它,并通过使用分号将其转化为一个语句: - - - - square_definition - -(square 21) - - -441 - - -square(21); - - -441 - - - - - - - - - 由于运算符组合在句法上与函数应用不同,JavaScript版本需要在这里明确说明函数应用的评估规则。这为下一小节的替换模型奠定了基础。 - - 函数应用是继运算符组合之后的我们遇到的第二种将表达式组合成更大表达式的方法。 - 函数应用的一般形式是 - - -函数表达式(参数表达式) - - - 其中应用的 - 函数表达式 - 函数表达式 - 指定应用于以逗号分隔的 - 参数 - 参数表达式的函数。 - 为了评估函数应用,解释器遵循 - 评估函数应用的 - 函数应用的评估 - 一个过程, - 与节中描述的运算符组合的过程非常相似。 -
    -
  • 评估函数应用时,执行以下操作: -
      -
    1. - 评估应用的子表达式,即函数表达式和参数表达式。 -
    2. -
    3. - 将函数表达式之值代表的函数应用于参数表达式之值。 -
    4. -
    -
  • -
-
-
- - square_definition - -(square (+ 2 5)) - - -49 - - -square(2 + 5); - - -49 - - - - - 这里,参数表达式本身是一个复合表达式, - 运算符组合 2 + 5。 - - - - square_square - 81 - square_definition - -(square (square 3)) - - -81 - - -square(square(3)); - - -81 - - - - - 当然,函数应用表达式也可以作为参数表达式。 - - -
- - - We can also use square - as a building block in defining other - - - 过程。 - - - 函数。 - - - 例如, $x^2 +y^2$ can be expressed as - - -(+ (square x) (square y)) - - -square(x) + square(y) - - - We can easily - - 定义 - 声明 - - 一个 - - - 过程 - sum-of-squares - - - 函数 - sum_of_squares多部分名称的书写方式,例如 - sum_of_squares,影响程序的可读性,不同的编程社区对此有不同的看法。 - 骆驼拼写 - 根据常见的JavaScript惯例,称为骆驼拼写, - 名称将是 - sumOfSquares。本书中使用的惯例称为蛇形拼写, - 命名惯例蛇形拼写 - 蛇形拼写 - 在本书的Scheme版本中使用的惯例更为相似,其中连字符承担了我们下划线的角色。 - - - 给定任意两个数字作为参数,产生它们平方和: - - sum_of_squares - sum_of_squares - 25 - sum_of_squares_example - square_definition - -(define (sum-of-squares x y) - (+ (square x) (square y))) - -(sum-of-squares 3 4) - - -function sum_of_squares(x, y) { - return square(x) + square(y); -} - - - - sum_of_squares_example - 25 - sum_of_squares - -(sum-of-squares 3 4) - - -25 - - -sum_of_squares(3, 4); - - -25 - - - Now we can use - - - sum-of-squares - - - sum_of_squares - - - 作为构建进一步 - - - 过程的构件: - - - 函数的构件: - - - - f - f_example - 136 - sum_of_squares - -(define (f a) - (sum-of-squares (+ a 1) (* a 2))) - - -function f(a) { - return sum_of_squares(a + 1, a * 2); -} - - - - f_example - f - -(f 5) - - -136 - - -f(5); - - -136 - - - - - Compound 过程 - 函数 - + Compound Procedures + Functions + We have identified in - - Lisp + + Lisp JavaScript - 一些必须出现在任何强大编程语言中的元素: + some of the elements that must appear in any powerful programming language:
  • - 数字和算术运算是原始数据和 + Numbers and arithmetic operations are primitive data and - 过程。 - 函数。 + procedures. + functions.
  • - 组合的嵌套提供了组合运算的方法。 + Nesting of combinations provides a means of combining operations.
  • - 将名称与值关联的常量声明提供了一种有限的抽象方式。 + Constant declarations that associate names with values provide a + limited means of abstraction.
- 现在我们将学习 + Now we will learn about - - 过程定义 - + + proceduredefinition of + - 声明 + declaration of - - 过程定义, - + + procedure definitions, + - 函数声明, + function declarations, - 一种更强大的抽象技术,通过这种技术,一个复合操作可以被赋予一个名称,然后作为一个单元来引用。 -
+ a much more powerful abstraction technique by which a compound + operation can be given a name and then referred to as a unit. + We begin by examining how to express the idea of - 平方。 - 我们可能会说, + squaring. + We might say, - - 要平方某个东西,将其乘以自身。 - + + To square something, multiply it by itself. + - 要平方某个东西,将其乘以自身。 + To square something, take it times itself. - 在这里,Scheme和JavaScript的表达略有不同,以更好地匹配JavaScript中的中缀表示法。 + The Scheme and JavaScript phrases differ a bit here, in order to better + match infix notation in JavaScript. - 这在我们的语言中表示为 - + This is expressed in our language as + square function (keyword) keywordsfunctionfunction square_definition square_example - + (define (square x) (* x x)) - + function square(x) { return x * x; @@ -1893,28 +82,28 @@ function square(x) { square_example - + (square 14) - + square(14); - + We can understand this in the following way: - + - + (define (square x) (* x x)) ;; ^ ^ ^ ^ ^ ^ -;; 要 平方 某个东西, 将其 乘以 自身。 - +;; To square something, multiply it by itself. + function square( x ) { return x * x; } // ^ ^ ^ ^ ^ ^ ^ -// 要 平方 某个东西, 将其 乘以 自身。 +// To square something, take it times itself. @@ -1925,237 +114,277 @@ function square( x ) { return x * x; } \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] - \normalsize 要 & \normalsize 平方 & \normalsize 某个东西, & & \normalsize 将其 &\normalsize 乘以 & \normalsize 自身. \\ + \normalsize To & \normalsize square & \normalsize something, & & \normalsize take &\normalsize it & \normalsize times & \normalsize itself. \\ \end{tabular} \end{flushleft} - 我们这里有一个 + We have here a - - 复合过程 - 过程复合 - 复合过程, - + + compound procedure + procedurecompound + compound procedure, + - 复合函数 - 复合 - 复合函数, + compound function + compound + compound function, - 它被赋予了一个名称 square. The - - 过程 - 函数 + which has been given the name square. The + + procedure + function - 代表了将某个东西乘以自身的运算。要被乘的东西被赋予一个本地名称, x, + represents the operation of multiplying something by itself. The thing to + be multiplied is given a local name, x, which plays the same role that a pronoun plays in natural language. - - - 过程命名的命名 - 过程使用define命名 - 过程使用define创建 - + + + namingof procedures + procedurenaming (with define) + procedurecreating with define + - 函数命名的命名 - 使用函数声明命名 - 使用函数声明创建 + namingof functions + naming (with function declaration) + creating with function declaration - 评估 + Evaluating the - - 定义 - + + definition + - 声明 + declaration - 创建这个复合 + creates this compound - - 过程 - + + procedure + - 函数 + function - 并将其与名称关联 - 语法形式函数声明 + and associates it with the name + syntactic formsfunction declaration - 函数声明 - 声明函数的函数 (function) - square. 注意,这里结合了两种不同的操作:我们正在创建 + function declaration + declarationfunctionof function (function) + square.Observe that there are two + different operations being combined here: we are creating the - - 过程, - + + procedure, + - 函数, + function, - 并且我们正在给它命名为square。能够分离这两种概念是可能的,确实是重要的创建 + and we are giving + it the name square. It is possible, indeed + important, to be able to separate these two notionsto create - - 过程 - + + procedures + - 函数 + functions - 而不命名它们,以及给已创建的 + without naming them, and to give names to - - 过程 - + + procedures + - 函数 + functions - 命名。在节中我们将看到如何做到这一点。 - - + that have already been created. We will see how to do this in + section. + + - - 过程定义的一般形式 - + + The general form of a procedure definition + - 函数声明的最简单形式 + The simplest form of a function declaration - 是 - - + is + + (define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) - + function name(parameters) { return expression; } The - - - 过程名称过程名称 - 过程名称 + + + nameprocedureof a procedure + procedurename of $\langle \textit{name}\rangle$ - + - 函数名称函数名称 - 名称 - 名称 + namefunctionof a function + name of + name - 是一个与环境中的 + is a symbol to be associated with the - - 过程 - + + procedure + - 函数 + function - 定义关联的符号。在整本书中,我们将 - 本书中的符号表达式语法中的斜体符号 - 语法表达式表达式的描述 - 使用斜体符号来描述表达式的一般语法 用尖括号 - 分隔例如, + definition in the environment.Throughout this book, we will + notation in this bookitalic symbols in expression syntax + syntaxexpressionsof expressions, describing + describe the general syntax of expressions by using italic + symbols delimited by angle + bracketse.g., - - $\langle \textit{name}\rangle$表示 - + + $\langle \textit{name}\rangle$to + - 名称表示 + nameto - 表达式中需要填入的,实际使用表达式时。 + denote the slots in the expression to be filled in + when such an expression is actually used. + The - - 过程形式参数 - 形式参数 - + + procedureformal parameters of + formal parameters + - 参数 - 参数 + parameters of + parameters - + $\langle \textit{formal parameters}\rangle$ - + - 参数 + parameters - 是在过程的主体中用于引用 + are the names used within the body of the - - 过程 - + + procedure + - 函数 + function - 对应参数的名称。 + to refer to the + corresponding arguments of the - - 过程。 - + + procedure. + - 函数。 + function. - - - 参数 - 在 - 括号函数在函数声明中 - 括号函数在函数声明中 - 括号内分组并用逗号分隔,就像在函数声明的应用中一样。 - 返回语句 - 返回值 - return (关键字) - 语法结构返回语句 - 关键字returnreturn - 在最简单的形式中, - 主体的 - 函数主体 - 主体 是一个单一的函数声明 - 返回语句更 - 语句序列函数在函数主体中 - 通常来说,函数的主体可以是一系列语句。 - 在这种情况下,解释器依次评估序列中的每个语句,直到返回语句确定函数应用的值。 - 它由关键字 + + + The + procedurebody of + body of a procedure + $\langle \textit{body} \rangle$ + is an expression + that will yield the value of + the procedure + application when the formal parameters are replaced by + the actual arguments to which the + procedure + is applied.More + sequence of expressionsproceduresin procedure body + generally, the body of the procedure can be a sequence of expressions. + In this case, the interpreter evaluates each expression in the + sequence in turn and returns the value of the final expression as the + value of the procedure application. + The $\langle \textit{name} \rangle$ + and the + $\langle \textit{formal parameters} \rangle$ + are grouped within + parenthesesprocedurein procedure definition + proceduredefinition of + parentheses, just as they would be in an actual call to the procedure + being defined. + + + The parameters + are grouped within + parenthesesfunctionin function declaration + parenthesesfunctionin function declaration + parentheses and separated by commas, as they will be in an application + of the function being declared. + return statement + return value + return (keyword) + syntactic formsreturn statement + keywordsreturnreturn + In the simplest form, the + body of + body of a function + body of a function declaration is a single + return statement,More + sequence of statementsfunctionsin function body + generally, the body of the function can be a sequence of statements. + In this case, the interpreter evaluates each statement in the sequence + in turn until a return statement determines the value of the + function application. + which consists of the keyword return - 后跟返回表达式组成 - 当 + followed by the return expression + that will yield the value of the function application, when the - 形式 + formal - 参数被实际参数替换,即函数被应用到的实际参数时,该表达式将产生函数应用的值。就像常量声明和表达式语句, - 返回语句 - 分号 (;)结束语句 - 声明 - 以分号结束。 + parameters are replaced by the actual arguments to which the function + is applied. Like constant declarations and expression statements, + return statements + semicolon (;)ending statement + declaration of + end with a semicolon. - + - + - - 定义了square之后, - 我们现在可以使用它: - + + Having defined square, + we can now use it: + - 声明了square之后, - 我们现在可以在 - 函数应用表达式中使用它,并通过使用分号将其转化为一个语句: + Having declared square, + we can now use it in a + function application expression, which we turn into a statement + using a semicolon: - + square_definition - + (square 21) - + 441 @@ -2165,51 +394,61 @@ square(21); 441 - + - - + + - 由于运算符组合在句法上与函数应用不同,JavaScript版本需要在这里明确说明函数应用的评估规则。这为下一小节的替换模型奠定了基础。 + Since operator combinations are syntactically distinct from function + applications, the JavaScript version needs to explicitly spell out + the evaluation rules for function application here. This prepares + the ground for the substitution model in the next sub-section. - 函数应用是继运算符组合之后的我们遇到的第二种将表达式组合成更大表达式的方法。 - 函数应用的一般形式是 + Function applications areafter operator + combinationsthe second kind of combination of + expressions into larger expressions that we encounter. + The general form of a function application is -函数表达式(参数表达式) +function-expression(argument-expressions) - 其中应用的 - 函数表达式 - 函数表达式 - 指定应用于以逗号分隔的 - 参数 - 参数表达式的函数。 - 为了评估函数应用,解释器遵循 - 评估函数应用的 - 函数应用的评估 - 一个过程,与节中描述的运算符组合的过程非常相似。 + where the + function expression + function-expression + of the application specifies + the function to be applied to the comma-separated + argument(s) + argument-expressions. + To evaluate a function application, the interpreter follows + evaluationof function application + function applicationevaluation of + a procedure + quite similar to the procedure for operator combinations described in + section.
    -
  • 评估函数应用时,执行以下操作: +
  • To evaluate a function application, do the following:
    1. - 评估应用的子表达式,即函数表达式和参数表达式。 + Evaluate the subexpressions of the application, namely + the function expression and the argument expressions.
    2. - 将函数表达式之值代表的函数应用于参数表达式之值。 + Apply the function that is the value of the function expression + to the values of the argument expressions.
- + square_definition - + (square (+ 2 5)) - + 49 @@ -2219,20 +458,20 @@ square(2 + 5); 49 - + - 这里,参数表达式本身是一个复合表达式, - 运算符组合 2 + 5。 + Here, the argument expression is itself a compound expression, + the operator combination 2 + 5. - + square_square 81 square_definition - + (square (square 3)) - + 81 @@ -2242,71 +481,78 @@ square(square(3)); 81 - + - 当然,函数应用表达式也可以作为参数表达式。 + Of course function application expressions can also serve as argument expressions. -
+
- We can also use square + We can also use square as a building block in defining other - - - 过程。 - + + + procedures. + - 函数。 + functions. - 例如, $x^2 +y^2$ can be expressed as + For example, $x^2 +y^2$ can be expressed as - + (+ (square x) (square y)) - + square(x) + square(y) We can easily - - 定义 - 声明 - - 一个 - - 过程 - sum-of-squares - - - 函数 - sum_of_squares多部分名称的书写方式,例如 - sum_of_squares,影响程序的可读性,不同的编程社区对此有不同的看法。 - 驼峰式拼写 - 根据常见的JavaScript惯例,称为驼峰式拼写, - 名称将是 - sumOfSquares。本书中使用的惯例称为蛇形拼写, - 命名惯例蛇形拼写 - 蛇形拼写 - 在本书的Scheme版本中使用的惯例更为相似,其中连字符承担了我们下划线的角色。 - - - 给定任意两个数字作为参数,产生它们平方和: - + define + declare + + a + + + procedure + sum-of-squares + + + function + sum_of_squaresThe + way multi-part names such as + sum_of_squares are written affects + the readability of programs, and programming communities differ + on this. + camel case + According to the common JavaScript convention, called camel case, + the name would be + sumOfSquares. The convention + naming conventionssnake case + snake case + used in this book is called snake case, and was chosen + for its closer resemblance + to the convention used in the Scheme version of this book, where + hyphens play the role of our underscores. + + + that, given any two numbers as arguments, produces the + sum of their squares: + sum_of_squares sum_of_squares 25 sum_of_squares_example square_definition - + (define (sum-of-squares x y) (+ (square x) (square y))) (sum-of-squares 3 4) - + function sum_of_squares(x, y) { return square(x) + square(y); @@ -2317,9 +563,9 @@ function sum_of_squares(x, y) { sum_of_squares_example 25 sum_of_squares - + (sum-of-squares 3 4) - + 25 @@ -2331,32 +577,32 @@ sum_of_squares(3, 4); Now we can use - - - sum-of-squares - + + + sum-of-squares + sum_of_squares - 作为构建进一步 + as a building block in constructing further - - 过程的构件: - + + procedures: + - 函数的构件: + functions: - + f f_example 136 sum_of_squares - + (define (f a) (sum-of-squares (+ a 1) (* a 2))) - + function f(a) { return sum_of_squares(a + 1, a * 2); @@ -2366,9 +612,9 @@ function f(a) { f_example f - + (f 5) - + 136 @@ -2380,44 +626,57 @@ f(5); - + - - 复合 - 复合过程像原始过程一样使用 - 过程的使用方式与原始过程完全相同。事实上,仅通过查看上面给出的sum-of-squares定义,我们无法判断square是内置于解释器中的,像+*一样,还是定义为复合过程。 - - - 除了复合函数之外,任何JavaScript环境还提供 - 原始 - 原始函数 - 内置在解释器中或从库中加载的原始函数。 - 本书中使用的JavaScript环境 - 除了运算符提供的原始函数外, - 本书使用的JavaScript环境包括额外的原始函数,例如函数 - math_log (原始函数) + + Compound + compound procedureused like primitive procedure + procedures are used in exactly the same way as primitive + procedures. Indeed, one could not tell by looking at the definition + of sum-of-squares given above whether + square was built into + the interpreter, like + and + *, + or defined as a compound procedure. + + + In addition to compound functions, any JavaScript environment provides + primitive + primitive function + primitive functions that are built into the interpreter or loaded + from libraries. + JavaScript environment used in this book + Besides the primitive functions provided by the operators, + the JavaScript environment used in this book includes + additional primitive functions + such as the function + math_log (primitive function) math_logMath.log - math_log, - 它计算参数的自然对数。我们的 - JavaScript环境包括ECMAScript的所有函数和常量 - Math对象, - 以math_$\ldots$命名。 - ECMAScriptMathMath对象 - 例如,ECMAScript的Math.log - 可以作为math_log使用。 - MIT出版社 - 为本书提供的网页包括JavaScript包 - sicp JavaScript包 - JavaScript包 sicp - sicp,提供这些及书中认为是原始的所有其他JavaScript函数。 - - 这些附加的原始函数的使用方式与 - 复合函数像原始函数一样使用 - 复合函数完全相同;评估应用 - math_log(1) 的结果为数字0。 - 事实上,仅通过查看上面给出的 - sum_of_squares定义,我们无法判断 - square 是内置于解释器中的,还是从库中加载,或定义为复合函数。 + math_log, + which computes the natural logarithm of its argument.Our + JavaScript environment includes all functions and constants of + ECMAScript's + Math object, + under the names math_$\ldots$. + ECMAScriptMathMath object + For example, ECMAScript's Math.log + is available as math_log. + The MIT Press + web page for this book includes the JavaScript package + sicp JavaScript package + JavaScript package sicp + sicp that provides these and all other + JavaScript functions that are considered primitive in the book. + + These additional primitive functions are used in exactly the same way as + compound functionused like primitive function + compound functions; evaluating the application + math_log(1) results in the number 0. + Indeed, 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. -
\ No newline at end of file + + diff --git a/xml/cn/chapter1/section1/subsection5.xml b/xml/cn/chapter1/section1/subsection5.xml index 70e4745b0..ec4062442 100644 --- a/xml/cn/chapter1/section1/subsection5.xml +++ b/xml/cn/chapter1/section1/subsection5.xml @@ -1,2863 +1,132 @@ - - - - 过程应用的替代模型 - + + + + 程序应用的代换模型 + - 函数应用的替代模型 + 函数应用的代换模型 - - - - 过程应用的替代模型 - - - 函数应用的替代模型 - - - - 过程及 - - - 函数 - - - 函数 - - - 应用的替代模型 - - - - 过程应用的替代模型 - - - 函数应用的替代模型 - - - - 过程及 - - - 函数 - - - 函数 - - - 应用的替代模型 - - - - - - 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程与其操作符命名原始过程的组合大致相同,我们在 - 中描述了该过程。 - - - 为了评估一个函数应用,解释器遵循在中描述的过程。 - - - 也就是说,解释器评估 - - - 组合 - - - 应用 - - - 的元素,并应用 - - - 过程 - - - 函数 - - - (它是 - - - 组合的操作员 - - - 应用的函数表达式 - - - 的值)到参数(它们是 - - - 组合的操作数 - - - 应用的参数表达式 - - - 的值)。 - - - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - - - Function - - - Application - - - - - - 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 - - - 为了评估一个函数应用,解释器遵循在中描述的过程。 - - - 也就是说,解释器评估 - - - 组合的元素 - - - 应用的元素 - - - 并应用 - - - 过程 - - - 函数 - - - (它是 - - - 组合的操作员的值) - - - 应用的函数表达式的值) - - - 到参数(它们是 - - - 组合的操作数的值)。 - - - 应用的参数表达式的值)。 - - - - - - - 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 - - - 我们可以假设基本函数的应用由解释器或库处理。 - - - 对于复合 - - - 过程, - - - 函数, - - - 应用过程如下: -
    -
  • - 要应用一个复合 - - - 过程 - - - 函数 - - - 于参数, - - - 评估过程的主体 - - - 评估函数的返回表达式 - - - 将每个 - - 形式 - - 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 -
  • -
- 为了说明这个过程,让我们评估 - - - 组合 - - - 应用 - - - - f_of_five - f - 136 - -(f 5) - - -f(5) - - -f(5); - - - where f is the - - - 定义的过程 - - - 声明的函数 - - - 在章节中。 - 我们首先检索 - - - 主体 - - - 返回表达式 - - - 的 -f: - - f - -(sum-of-squares (+ a 1) (* a 2)) - - -sum_of_squares(a + 1, a * 2) - - - Then we replace the parameter a - by the argument 5: - - -(sum-of-squares (+ 5 1) (* 5 2)) - - -sum_of_squares(5 + 1, 5 * 2) - - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 - - - 操作数 - - - 参数 - - - 和 - - - 一个运算符 sum-of-squares。 - - - 一个函数表达式 sum_of_squares。 - - - 评估这个 - - - 组合 - - - 应用 - - - 涉及三个子问题。我们必须评估 - - 运算符 - 函数表达式 - - 以获得要应用的 - - - 过程 - - - 函数 - - - ,并且我们必须评估 - - - 操作数 - - - 参数表达式 - - - 以获得参数。现在 - - - (+ 5 1) - - - 5 + 1 - - - 产生 6 而 - - - (* 5 2) - - - 5 * 2 - - - 产生 10,因此我们必须将 - - - sum-of-squares 过程 - - - sum_of_squares 函数 - - - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - - - sum_of_squares, - - - reducing the expression to - - -(+ (square 6) (square 10)) - - -square(6) + square(10) - - - If we use the - - - 定义 - - - 声明 - - - 的 -square, this reduces to - - -(+ (* 6 6) (* 10 10)) - - -(6 * 6) + (10 * 10) - - - which reduces by multiplication to - - -(+ 36 100) - - -36 + 100 - - - and finally to - - -136 - - -136 - - -
- - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - - - Function - - - Application - - - - - - 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 - - - 为了评估一个函数应用,解释器遵循在中描述的过程。 - - - 也就是说,解释器评估 - - - 组合的元素 - - - 应用的元素 - - - 并应用 - - - 过程 - - - 函数 - - - (它是 - - - 组合的操作员的值) - - - 应用的函数表达式的值) - - - 到参数(它们是 - - - 组合的操作数的值)。 - - - 应用的参数表达式的值)。 - - - - - - - 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 - - - 我们可以假设基本函数的应用由解释器或库处理。 - - - 对于复合 - - - 过程, - - - 函数, - - - 应用过程如下: -
    -
  • - 要应用一个复合 - - - 过程 - - - 函数 - - - 于参数, - - - 评估过程的主体 - - - 评估函数的返回表达式 - - - 将每个 - - 形式 - - 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 -
  • -
- 为了说明这个过程,让我们评估 - - - 组合 - - - 应用 - - - - f_of_five - f - 136 - -(f 5) - - -f(5) - - -f(5); - - - where f is the - - - 定义的过程 - - - 声明的函数 - - - 在章节中。 - 我们首先检索 - - - 主体 - - - 返回表达式 - - - 的 -f: - - f - -(sum-of-squares (+ a 1) (* a 2)) - - -sum_of_squares(a + 1, a * 2) - - - Then we replace the parameter a - by the argument 5: - - -(sum-of-squares (+ 5 1) (* 5 2)) - - -sum_of_squares(5 + 1, 5 * 2) - - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 - - - 操作数 - - - 参数 - - - 和 - - - 一个运算符 sum-of-squares。 - - - 一个函数表达式 sum_of_squares。 - - - 评估这个 - - - 组合 - - - 应用 - - - 涉及三个子问题。我们必须评估 - - 运算符 - 函数表达式 - - 以获得要应用的 - - - 过程 - - - 函数 - - - ,并且我们必须评估 - - - 操作数 - - - 参数表达式 - - - 以获得参数。现在 - - - (+ 5 1) - - - 5 + 1 - - - 产生 6 而 - - - (* 5 2) - - - 5 * 2 - - - 产生 10,因此我们必须将 - - - sum-of-squares 过程 - - - sum_of_squares 函数 - - - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - - - sum_of_squares, - - - reducing the expression to - - -(+ (square 6) (square 10)) - - -square(6) + square(10) - - - If we use the - - - definition - - - declaration - - - 的 -square, this reduces to - - -(+ (* 6 6) (* 10 10)) - - -(6 * 6) + (10 * 10) - - - which reduces by multiplication to - - -(+ 36 100) - - -36 + 100 - - - and finally to - - -136 - - -136 - - -
- - - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - - - Function - - - Application - - - - - - 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 - - - 为了评估一个函数应用,解释器遵循在中描述的过程。 - - - 也就是说,解释器评估 - - - 组合的元素 - - - 应用的元素 - - - 并应用 - - - 过程 - - - 函数 - - - (它是 - - - 组合的操作员的值) - - - 应用的函数表达式的值) - - - 到参数(它们是 - - - 组合的操作数的值)。 - - - 应用的参数表达式的值)。 - - - - - - - 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 - - - 我们可以假设基本函数的应用由解释器或库处理。 - - - 对于复合 - - - 过程, - - - 函数, - - - 应用过程如下: -
    -
  • - 要应用一个复合 - - - 过程 - - - 函数 - - - 于参数, - - - 评估过程的主体 - - - 评估函数的返回表达式 - - - 将每个 - - 形式 - - 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 -
  • -
- 为了说明这个过程,让我们评估 - - - 组合 - - - 应用 - - - - f_of_five - f - 136 - -(f 5) - - -f(5) - - -f(5); - - - where f is the - - - 定义的过程 - - - 声明的函数 - - - 在章节中。 - 我们首先检索 - - - 主体 - - - 返回表达式 - - - 的 -f: - - f - -(sum-of-squares (+ a 1) (* a 2)) - - -sum_of_squares(a + 1, a * 2) - - - Then we replace the parameter a - by the argument 5: - - -(sum-of-squares (+ 5 1) (* 5 2)) - - -sum_of_squares(5 + 1, 5 * 2) - - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 - - - 操作数 - - - 参数 - - - 和 - - - 一个运算符 sum-of-squares。 - - - 一个函数表达式 sum_of_squares。 - - - 评估这个 - - - 组合 - - - 应用 - - - 涉及三个子问题。我们必须评估 - - 运算符 - 函数表达式 - - 以获得要应用的 - - - 过程 - - - 函数 - - - ,并且我们必须评估 - - - 操作数 - - - 参数表达式 - - - 以获得参数。现在 - - - (+ 5 1) - - - 5 + 1 - - - 产生 6 而 - - - (* 5 2) - - - 5 * 2 - - - 产生 10,因此我们必须将 - - - sum-of-squares 过程 - - - sum_of_squares 函数 - - - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - - - sum_of_squares, - - - reducing the expression to - - -(+ (square 6) (square 10)) - - -square(6) + square(10) - - - If we use the - - - definition - - - declaration - - - 的 -square, this reduces to - - -(+ (* 6 6) (* 10 10)) - - -(6 * 6) + (10 * 10) - - - which reduces by multiplication to - - -(+ 36 100) - - -36 + 100 - - - and finally to - - -136 - - -136 - - -
- - - The process we have just described is called the substitution - model for - - procedure - function - - application. It can be taken as a model that - determines the meaning of - - procedure - function - - application, insofar as the - - procedures - functions - - in this chapter are concerned. However, there are two - points that should be stressed: -
    -
  • - 替换的目的是帮助我们思考 - - 过程 - 函数 - - 应用,而不是提供解释器实际工作方式的描述。典型的解释器不会通过操作 - - 过程 - 函数 - - 的文本来替换形式参数的值来评估应用程序。实际上,"替换"是通过为 - - 形式 - - - - - 参数使用局部环境来完成的。我们将在第3章和第4章中更加充分地讨论这个问题,当我们详细检查解释器的实现时。 -
  • -
  • - 在这本书中,我们将展示一系列不断复杂的解释器工作模型,最终在第章获得一个完整的解释器和编译器实现。替换模型只是这些模型中的第一个——通过正式考虑评估过程来入门的一种方式。一般来说,当 - 建模在科学和工程中 - 科学和工程中建模现象时,我们从简化的、不完整的模型开始。当我们更详细地研究事物时,这些简单模型变得不够,需要用更精细的模型来代替。替换模型也不例外。特别是,当我们在第章讨论使用 - - 过程 - 函数 - - 与“可变数据”时,我们将看到替换模型会崩溃,必须用更复杂的 - - 过程 - 函数 - - 应用模型来替代。尽管替换思想很简单,但给出替换过程的严格数学定义却出奇地复杂。问题源于可能存在对 - - 过程的形式参数 - 函数的参数 - - 的名称和可能用于应用的表达式中(可能相同)名称之间的混淆。实际上,在逻辑和编程语义的文献中,存在着对替换的错误定义的悠久历史。 - Stoy, Joseph E. - 详细讨论了替换,请参见Stoy 1977 -
  • -
-
- - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - - - Function - - - Application - - - - - - 为了评估一个组合,其操作符命名一个复合过程,解释器遵循的过程大致类似于操作符命名基本过程的组合,我们在中描述。 - - - 为了评估一个函数应用,解释器遵循在中描述的过程。 - - - 也就是说,解释器评估 - - - 组合的元素 - - - 应用的元素 - - - 并应用 - - - 过程 - - - 函数 - - - (它是 - - - 组合的操作员的值) - - - 应用的函数表达式的值) - - - 到参数(它们是 - - - 组合的操作数的值)。 - - - 应用的参数表达式的值)。 - - - - - - - 我们可以假设将基本过程应用于参数的机制已内置于解释器中。 - - - 我们可以假设基本函数的应用由解释器或库处理。 - - - 对于复合 - - - 过程, - - - 函数, - - - 应用过程如下: -
    -
  • - 要应用一个复合 - - - 过程 - - - 函数 - - - 于参数, - - - 评估过程的主体 - - - 评估函数的返回表达式 - - - 将每个 - - 形式 - - 参数替换为相应的参数。如果函数体是语句序列,则用参数替换这些参数并对其进行评估,应用的值是遇到的第一个返回语句的返回表达式的值。 -
  • -
- 为了说明这个过程,让我们评估 - - - 组合 - - - 应用 - - - - f_of_five - f - 136 - -(f 5) - - -f(5) - - -f(5); - - - where f is the - - - 定义的过程 - - - 声明的函数 - - - 在章节中。 - 我们首先检索 - - - 主体 - - - 返回表达式 - - - 的 -f: - - f - -(sum-of-squares (+ a 1) (* a 2)) - - -sum_of_squares(a + 1, a * 2) - - - Then we replace the parameter a - by the argument 5: - - -(sum-of-squares (+ 5 1) (* 5 2)) - - -sum_of_squares(5 + 1, 5 * 2) - - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 - - - 操作数 - - - 参数 - - - 和 - - - 一个运算符 sum-of-squares。 - - - 一个函数表达式 sum_of_squares。 - - - 评估这个 - - - 组合 - - - 应用 - - - 涉及三个子问题。我们必须评估 - - 运算符 - 函数表达式 - - 以获得要应用的 - - - 过程 - - - 函数 - - - ,并且我们必须评估 - - - 操作数 - - - 参数表达式 - - - 以获得参数。现在 - - - (+ 5 1) - - - 5 + 1 - - - 产生 6 而 - - - (* 5 2) - - - 5 * 2 - - - 产生 10,因此我们必须将 - - - sum-of-squares 过程 - - - sum_of_squares 函数 - - - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - - - sum_of_squares, - - - reducing the expression to - - -(+ (square 6) (square 10)) - - -square(6) + square(10) - - - If we use the - - - definition - - - declaration - - - 的 -square, this reduces to - - -(+ (* 6 6) (* 10 10)) - - -(6 * 6) + (10 * 10) - - - which reduces by multiplication to - - -(+ 36 100) - - -36 + 100 - - - and finally to - - -136 - - -136 - - -
- - - The process we have just described is called the substitution - model for - - procedure - function - - application. It can be taken as a model that - determines the meaning of - - procedure - function - - application, insofar as the - - procedures - functions - - in this chapter are concerned. However, there are two - points that should be stressed: -
    -
  • - 替换的目的是帮助我们思考 - - 过程 - 函数 - - 应用,而不是提供解释器实际工作方式的描述。典型的解释器不会通过操作 - - 过程 - 函数 - - 的文本来替换形式参数的值来评估应用程序。实际上,"替换"是通过为 - - 形式 - - - - - 参数使用局部环境来完成的。我们将在第3章和第4章中更加充分地讨论这个问题,当我们详细检查解释器的实现时。 -
  • -
  • - 在这本书中,我们将展示一系列不断复杂的解释器工作模型,最终在第章获得一个完整的解释器和编译器实现。替换模型只是这些模型中的第一个——通过正式考虑评估过程来入门的一种方式。一般来说,当 - 建模在科学和工程中 - 科学和工程中建模现象时,我们从简化的、不完整的模型开始。当我们更详细地研究事物时,这些简单模型变得不够,需要用更精细的模型来代替。替换模型也不例外。特别是,当我们在第章讨论使用 - - 过程 - 函数 - - 与“可变数据”时,我们将看到替换模型会崩溃,必须用更复杂的 - - 过程 - 函数 - - 应用模型来替代。尽管替换思想很简单,但给出替换过程的严格数学定义却出奇地复杂。问题源于可能存在对 - - 过程的形式参数 - 函数的参数 - - 的名称和可能用于应用的表达式中(可能相同)名称之间的混淆。实际上,在逻辑和编程语义的文献中,存在着对替换的错误定义的悠久历史。 - Stoy, Joseph E. - 详细讨论了替换,请参见Stoy 1977 -
  • -
-
- - - Applicative order versus normal order - - - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - - - Function - - - Application - - - - - - To evaluate a combination whose operator names a compound procedure, the - interpreter follows much the same process as for combinations whose - operators name primitive procedures, which we described in - section. - - - To evaluate a function application, the interpreter follows the process - described in section. - - - That is, the interpreter evaluates the elements of the - - - combination - - - application - - - and applies the - - - procedure - - - function - - - (which is the value of the - - - operator of the combination) - - - function expression of the application) - - - to the arguments (which are the values of the - - - operands of the combination). - - - argument expressions of the application). - - - - - - - We can assume that the mechanism for applying primitive - procedures - to arguments is built into the interpreter. - - - We can assume that the application of primitive - functions is handled by the interpreter or libraries. - - - For compound - - - procedures, - - - functions, - - - the application process is as follows: -
    -
  • - To apply a compound - - - procedure - - - function - - - to arguments, - - - evaluate the body of the procedure - - - evaluate the return expression of the function - - - with each - - formal - - parameter replaced by the corresponding argument.If the - body of the function is a sequence of statements, the - body is evaluated with the parameters replaced, and the value of the - application is the value of the return expression of the first - return statement encountered. -
  • -
- To illustrate this process, lets evaluate the - - - combination - - - application - - - - f_of_five - f - 136 - -(f 5) - - -f(5) - - -f(5); - - - where f is the - - - procedure defined - - - function declared - - - in section. - We begin by retrieving the - - - body - - - return expression - - - of -f: - - f - -(sum-of-squares (+ a 1) (* a 2)) - - -sum_of_squares(a + 1, a * 2) - - - Then we replace the parameter a - by the argument 5: - - -(sum-of-squares (+ 5 1) (* 5 2)) - - -sum_of_squares(5 + 1, 5 * 2) - - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 - - - 操作数 - - - 参数 - - - 和 - - - 一个运算符 sum-of-squares。 - - - 一个函数表达式 sum_of_squares。 - - - 评估这个 - - - 组合 - - - 应用 - - - 涉及三个子问题。我们必须评估 - - 运算符 - 函数表达式 - - 以获得要应用的 - - - 过程 - - - 函数 - - - ,并且我们必须评估 - - - 操作数 - - - 参数表达式 - - - 以获得参数。现在 - - - (+ 5 1) - - - 5 + 1 - - - 产生 6 而 - - - (* 5 2) - - - 5 * 2 - - - 产生 10,因此我们必须将 - - - sum-of-squares 过程 - - - sum_of_squares 函数 - - - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - - - sum_of_squares, - - - reducing the expression to - - -(+ (square 6) (square 10)) - - -square(6) + square(10) - - - If we use the - - - definition - - - declaration - - - of -square, this reduces to - - -(+ (* 6 6) (* 10 10)) - - -(6 * 6) + (10 * 10) - - - which reduces by multiplication to - - -(+ 36 100) - - -36 + 100 - - - and finally to - - -136 - - -136 - - -
- - - The process we have just described is called the substitution - model for - - procedure - function - - application. It can be taken as a model that - determines the meaning of - - procedure - function - - application, insofar as the - - procedures - functions - - in this chapter are concerned. However, there are two - points that should be stressed: -
    -
  • - The purpose of the substitution is to help us think about - - procedure - function - - application, not to provide a description of how the interpreter - really works. Typical interpreters do not evaluate - - procedure - function - - applications by manipulating the text of a - - procedure to substitute values for the formal - function to substitute values for the - - parameters. In practice, the substitution is - accomplished by using a local environment for the - - formal - - - - - parameters. We will discuss this more fully in chapters 3 and - 4 when we examine the implementation of an interpreter in detail. -
  • -
  • - Over the course of this book, we will present a sequence of - increasingly elaborate models of how interpreters work, culminating - with a complete implementation of an interpreter and compiler in - chapter. The substitution model is only the first of - these modelsa way to get started thinking formally - about the evaluation process. In general, when - modelingin science and engineering - modeling phenomena in science and engineering, we begin with - simplified, incomplete models. As we examine things in greater detail, - these simple models become inadequate and must be replaced by more - refined models. The substitution model is no exception. In particular, - when we address in chapter the use of - - procedures - functions - - with mutable data, we will see that the substitution - model breaks down and must be replaced by a more complicated model of - - procedure - function - - application.Despite the - simplicity of the substitution idea, it turns out to be - surprisingly complicated to give a rigorous mathematical - definition of the substitution process. The problem arises - from the possibility of confusion between the names used for the - - formal parameters of a procedure - parameters of a function - - and the (possibly identical) names used in the expressions - to which the - - procedure - function - - may be applied. Indeed, there is a long - history of erroneous definitions of substitution in the - literature of logic and programming semantics. - Stoy, Joseph E. - See Stoy 1977 for a - careful discussion of substitution. -
  • -
-
- - - Applicative order versus normal order - - - - According to the description of evaluation given in - - - section, - - - section, - - - the interpreter first evaluates the - - - operator - - - function - - - and - - - operands - - - argument expressions - - - and then applies the resulting - - - procedure - - - function - - - to the resulting arguments. This is not the only way to perform evaluation. - An alternative evaluation model would not evaluate the - - - operands - - - arguments - - - until their values were needed. Instead it would first substitute - - - operand - - - argument - - - expressions for parameters until it obtained an expression involving - only - - primitive operators, - operators and primitive functions, - - and would then perform the evaluation. If we - used this method, the evaluation of - - -(f 5) - - -f(5) - - - would proceed according to the sequence of expansions - - -(sum-of-squares (+ 5 1) (* 5 2)) - -(+ (square (+ 5 1)) (square (* 5 2)) ) - -(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2))) - - -sum_of_squares(5 + 1, 5 * 2) - -square(5 + 1) + square(5 * 2) - -(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2) - - - followed by the reductions - - -(+ (* 6 6) (* 10 10)) - -(+ 36 100) - - 136 - - -6 * 6 + 10 * 10 - - 36 + 100 - - 136 - - - This gives the same answer as our previous evaluation model, but the - process is different. In particular, the evaluations of - - - (+ 5 1) - - - 5 + 1 - - - and - - - (* 5 2) - - - 5 * 2 - - - are each performed twice here, corresponding to the reduction of the - expression - - -(* x x) - - -x * x - - - with x replaced respectively by - - - (+ 5 1) - - - 5 + 1 - - - and - - - (* 5 2). - - - 5 * 2. - - - - - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - - - Function - - - Application - - - - - - To evaluate a combination whose operator names a compound procedure, the - interpreter follows much the same process as for combinations whose - operators name primitive procedures, which we described in - section. - - - To evaluate a function application, the interpreter follows the process - described in section. - - - That is, the interpreter evaluates the elements of the - - - combination - - - application - - - and applies the - - - procedure - - - function - - - (which is the value of the - - - operator of the combination) - - - function expression of the application) - - - to the arguments (which are the values of the - - - operands of the combination). - - - argument expressions of the application). - - - - - - - We can assume that the mechanism for applying primitive - procedures - to arguments is built into the interpreter. - - - We can assume that the application of primitive - functions is handled by the interpreter or libraries. - - - For compound - - - procedures, - - - functions, - - - the application process is as follows: -
    -
  • - To apply a compound - - - procedure - - - function - - - to arguments, - - - evaluate the body of the procedure - - - evaluate the return expression of the function - - - with each - - formal - - parameter replaced by the corresponding argument.If the - body of the function is a sequence of statements, the - body is evaluated with the parameters replaced, and the value of the - application is the value of the return expression of the first - return statement encountered. -
  • -
- To illustrate this process, lets evaluate the - - - combination - - - application - - - - f_of_five - f - 136 - -(f 5) - - -f(5) - - -f(5); - - - where f is the - - - procedure defined - - - function declared - - - in section. - We begin by retrieving the - - - body - - - return expression - - - of -f: - - f - -(sum-of-squares (+ a 1) (* a 2)) - - -sum_of_squares(a + 1, a * 2) - - - Then we replace the parameter a - by the argument 5: - - -(sum-of-squares (+ 5 1) (* 5 2)) - - -sum_of_squares(5 + 1, 5 * 2) - - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 - - - 操作数 - - - 参数 - - - 和 - - - 一个运算符 sum-of-squares。 - - - 一个函数表达式 sum_of_squares。 - - - 评估这个 - - - 组合 - - - 应用 - - - 涉及三个子问题。我们必须评估 - - 运算符 - 函数表达式 - - 以获得要应用的 - - - 过程 - - - 函数 - - - ,并且我们必须评估 - - - 操作数 - - - 参数表达式 - - - 以获得参数。现在 - - - (+ 5 1) - - - 5 + 1 - - - 产生 6 而 - - - (* 5 2) - - - 5 * 2 - - - 产生 10,因此我们必须将 - - - sum-of-squares 过程 - - - sum_of_squares 函数 - - - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - - - sum_of_squares, - - - reducing the expression to - - -(+ (square 6) (square 10)) - - -square(6) + square(10) - - - If we use the - - - definition - - - declaration - - - of -square, this reduces to - - -(+ (* 6 6) (* 10 10)) - - -(6 * 6) + (10 * 10) - - - which reduces by multiplication to - - -(+ 36 100) - - -36 + 100 - - - and finally to - - -136 - - -136 - - -
- - - The process we have just described is called the substitution - model for - - procedure - function - - application. It can be taken as a model that - determines the meaning of - - procedure - function - - application, insofar as the - - procedures - functions - - in this chapter are concerned. However, there are two - points that should be stressed: -
    -
  • - The purpose of the substitution is to help us think about - - procedure - function - - application, not to provide a description of how the interpreter - really works. Typical interpreters do not evaluate - - procedure - function - - applications by manipulating the text of a - - procedure to substitute values for the formal - function to substitute values for the - - parameters. In practice, the substitution is - accomplished by using a local environment for the - - formal - - - - - parameters. We will discuss this more fully in chapters 3 and - 4 when we examine the implementation of an interpreter in detail. -
  • -
  • - Over the course of this book, we will present a sequence of - increasingly elaborate models of how interpreters work, culminating - with a complete implementation of an interpreter and compiler in - chapter. The substitution model is only the first of - these modelsa way to get started thinking formally - about the evaluation process. In general, when - modelingin science and engineering - modeling phenomena in science and engineering, we begin with - simplified, incomplete models. As we examine things in greater detail, - these simple models become inadequate and must be replaced by more - refined models. The substitution model is no exception. In particular, - when we address in chapter the use of - - procedures - functions - - with mutable data, we will see that the substitution - model breaks down and must be replaced by a more complicated model of - - procedure - function - - application.Despite the - simplicity of the substitution idea, it turns out to be - surprisingly complicated to give a rigorous mathematical - definition of the substitution process. The problem arises - from the possibility of confusion between the names used for the - - formal parameters of a procedure - parameters of a function - - and the (possibly identical) names used in the expressions - to which the - - procedure - function - - may be applied. Indeed, there is a long - history of erroneous definitions of substitution in the - literature of logic and programming semantics. - Stoy, Joseph E. - See Stoy 1977 for a - careful discussion of substitution. -
  • -
-
- - - Applicative order versus normal order - - - - According to the description of evaluation given in - - - section, - - - section, - - - the interpreter first evaluates the - - - operator - - - function - - - and - - - operands - - - argument expressions - - - and then applies the resulting - - - procedure - - - function - - - to the resulting arguments. This is not the only way to perform evaluation. - An alternative evaluation model would not evaluate the - - - operands - - - arguments - - - until their values were needed. Instead it would first substitute - - - operand - - - argument - - - expressions for parameters until it obtained an expression involving - only - - primitive operators, - operators and primitive functions, - - and would then perform the evaluation. If we - used this method, the evaluation of - - -(f 5) - - -f(5) - - - would proceed according to the sequence of expansions - - -(sum-of-squares (+ 5 1) (* 5 2)) - -(+ (square (+ 5 1)) (square (* 5 2)) ) - -(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2))) - - -sum_of_squares(5 + 1, 5 * 2) - -square(5 + 1) + square(5 * 2) - -(5 + 1) * (5 + 1) + (5 * 2) * (5 * 2) - - - followed by the reductions - - -(+ (* 6 6) (* 10 10)) - -(+ 36 100) - - 136 - - -6 * 6 + 10 * 10 - - 36 + 100 - - 136 - - - This gives the same answer as our previous evaluation model, but the - process is different. In particular, the evaluations of - - - (+ 5 1) - - - 5 + 1 - - - and - - - (* 5 2) - - - 5 * 2 - - - are each performed twice here, corresponding to the reduction of the - expression - - -(* x x) - - -x * x - - - with x replaced respectively by - - - (+ 5 1) - - - 5 + 1 - - - and - - - (* 5 2). - - - 5 * 2. - - - - - This alternative fully expand and then reduce - evaluation method is known as - normal-order evaluation - normal-order evaluation, in contrast to the evaluate - the arguments and then apply method that the interpreter actually - uses, which is called - applicative-order evaluation - applicative-order evaluation. It can be shown that, for - - procedure - function - - applications that can be modeled using substitution (including all the +
+ + 代换模型 - procedures - functions - - in the first two chapters of this book) and that yield legitimate values, - normal-order and applicative-order evaluation produce the same value. - (See exercise - for an instance of an illegitimate value where normal-order - and applicative-order evaluation do not give the same result.) - - - - substitution model of procedure application - - - substitution model of function application - - - - The Substitution Model for - - - Procedure - + + 程序 + - Function + 函数 - Application - + 应用 + - + - - To evaluate a combination whose operator names a compound procedure, the - interpreter follows much the same process as for combinations whose - operators name primitive procedures, which we described in - section. - + + 为了评估一个运算符名称为复合程序的组合,解释器遵循与我们在 + + 小节中描述的运算符名称为原语程序的组合几乎相同的过程。 + - To evaluate a function application, the interpreter follows the process - described in section. + 为了评估一个函数应用,解释器遵循 + + 小节中描述的过程。 - That is, the interpreter evaluates the elements of the + 也就是说,解释器评估 - - combination - + + 组合 + - application + 应用 - and applies the + 的元素,并将 - - procedure - + + 程序 + - function + 函数 - (which is the value of the + (这是 - - operator of the combination) - + + 组合的运算符 + - function expression of the application) + 应用的函数表达式 - to the arguments (which are the values of the + 的值)应用于参数(这些参数的值是 - - operands of the combination). - + + 组合的操作数)。 + - argument expressions of the application). + 应用的参数表达式)。 - - - - - We can assume that the mechanism for applying primitive - procedures - to arguments is built into the interpreter. - + + + + + 我们可以假设将原语程序应用于参数的机制内置于解释器中。 + - We can assume that the application of primitive - functions is handled by the interpreter or libraries. + 我们可以假设原语函数的应用由解释器或库处理。 - For compound + 对于复合 - - procedures, - + + 程序, + - functions, + 函数, - the application process is as follows: + 应用过程如下:
  • - To apply a compound + 要将一个复合 - - procedure - + + 程序 + - function + 函数 - to arguments, + 应用于参数, - - evaluate the body of the procedure - + + 评估程序的主体 + - evaluate the return expression of the function + 评估函数的返回表达式 - with each + 并用相应的参数替换每个 - formal + 形式 - parameter replaced by the corresponding argument.If the - body of the function is a sequence of statements, the - body is evaluated with the parameters replaced, and the value of the - application is the value of the return expression of the first - return statement encountered. + 参数。如果函数的主体是语句序列,则主体将在替换参数后求值,应用的值是遇到的第一个返回语句的返回表达式的值。
- To illustrate this process, lets evaluate the + 为了说明这个过程,让我们评估 - - combination - + + 组合 + - application + 应用 - + f_of_five f 136 @@ -2870,29 +139,31 @@ f(5) f(5); - - where f is the - - - procedure defined - + + 在哪里 +f 是 + + + 程序定义 + - function declared + 函数声明 - in section. - We begin by retrieving the + 在 小节中。 + 我们首先检索 - - body - + + 主体 + - return expression + 返回表达式 - of -f: - + 的内容 +f + : + f (sum-of-squares (+ a 1) (* a 2)) @@ -2900,117 +171,116 @@ f(5); sum_of_squares(a + 1, a * 2) - - Then we replace the parameter a - by the argument 5: - + + 然后我们替换参数 +a + 由参数 5 替换: + (sum-of-squares (+ 5 1) (* 5 2)) sum_of_squares(5 + 1, 5 * 2) - - Thus the problem reduces to the evaluation of - - - 一个组合 - - - 一个应用 - - - 具有两个 + + 通过两个 - + 操作数 - + 参数 - 和 + 和 - - 一个运算符 sum-of-squares。 - + + 一个运算符 sum-of-squares。 + - 一个函数表达式 sum_of_squares。 + 一个函数表达式 + sum_of_squares 评估这个 - + 组合 - + 应用 涉及三个子问题。我们必须评估 - 运算符 - 函数表达式 + + 运算符 + + + 函数表达式 + 以获得要应用的 - - 过程 - + + 程序 + 函数 - ,并且我们必须评估 + ,同时我们必须评估 - - 操作数 - + + 操作数 + 参数表达式 以获得参数。现在 - - (+ 5 1) - + + (+ 5 1) + 5 + 1 - 产生 6 而 + 产生 6,而 - - (* 5 2) - + + (* 5 2) + 5 * 2 产生 10,因此我们必须将 - - sum-of-squares 过程 - + + sum-of-squares 程序 + sum_of_squares 函数 - 应用于 6 和 10。这些值被替代为 - 形式 - 参数 -x and - y in the body of - - - sum-of-squares, - + 应用于 6 和 10。这些值被替换用于 + + 形式参数 +x + 和 +y + 在 + + + sum-of-squares + - sum_of_squares, + sum_of_squares - reducing the expression to + 的主体中,将表达式简化为 (+ (square 6) (square 10)) @@ -3018,37 +288,38 @@ sum_of_squares(5 + 1, 5 * 2) square(6) + square(10) - - If we use the - - - definition - + + 如果我们使用 + + + 定义 + - declaration + 声明 - of -square, this reduces to - + 的 +square + , 这简化为 + (+ (* 6 6) (* 10 10)) (6 * 6) + (10 * 10) - - which reduces by multiplication to - + + 通过乘法简化为 + (+ 36 100) 36 + 100 - - and finally to - + + 并最终简化为 + 136 @@ -3057,172 +328,111 @@ square(6) + square(10)
- - - The process we have just described is called the substitution - model for + + + 我们刚刚描述的过程称为 代换模型 + 用于 - procedure - function + 程序 + 函数 - application. It can be taken as a model that - determines the meaning of + 应用。它可以作为一个模型,用于确定 + 意义 + 的 - procedure - function + 程序 + 函数 - application, insofar as the + 应用,因为本章中涉及的 - procedures - functions + 程序 + 函数 - in this chapter are concerned. However, there are two - points that should be stressed: -
    -
  • - The purpose of the substitution is to help us think about + 。然而,有两个要点需要强调: +
      +
    • + 代换的目的是帮助我们思考 - procedure - function + 程序 + 函数 - application, not to provide a description of how the interpreter - really works. Typical interpreters do not evaluate + 应用,而不是提供解释器实际工作的描述。典型的解释器并不是通过操作 - procedure - function + 程序 + 函数 - applications by manipulating the text of a - - procedure to substitute values for the formal - function to substitute values for the - - parameters. In practice, the substitution is - accomplished by using a local environment for the - - formal - - - - - parameters. We will discuss this more fully in chapters 3 and - 4 when we examine the implementation of an interpreter in detail. -
    • -
    • - Over the course of this book, we will present a sequence of - increasingly elaborate models of how interpreters work, culminating - with a complete implementation of an interpreter and compiler in - chapter. The substitution model is only the first of - these modelsa way to get started thinking formally - about the evaluation process. In general, when - modelingin science and engineering - modeling phenomena in science and engineering, we begin with - simplified, incomplete models. As we examine things in greater detail, - these simple models become inadequate and must be replaced by more - refined models. The substitution model is no exception. In particular, - when we address in chapter the use of - - procedures - functions - - with mutable data, we will see that the substitution - model breaks down and must be replaced by a more complicated model of + 的文本来评估它们的应用,而是通过使用局部环境来实现 代换,用于 - procedure + 形式 function - - application.Despite the - simplicity of the substitution idea, it turns out to be - surprisingly complicated to give a rigorous mathematical - definition of the substitution process. The problem arises - from the possibility of confusion between the names used for the + + 参数。我们将在第 3 章和第 4 章中详细讨论这一点,当时我们将仔细检查解释器的实现。 +
    • +
    • + 在本书中,我们将呈现一系列越来越复杂的模型,以解释解释器是如何工作的,最终将以第章中完整的解释器和编译器实现作为结尾。代换模型仅是这些模型的第一个一种开始正式思考求值过程的方式。一般来说,当我们在 + 建模在科学和工程中 + 时,我们从简化和不完整的模型开始。随着我们对事物进行更详细的检查,这些简单模型变得不足,并必须被更精细的模型所替代。代换模型也不例外。特别是当我们在第章中讨论使用 - formal parameters of a procedure - parameters of a function + 程序 + 函数 - and the (possibly identical) names used in the expressions - to which the + 与可变数据时,我们将看到代换模型失效,必须被更复杂的模型所替代,用于 - procedure - function + 程序 + 函数 - may be applied. Indeed, there is a long - history of erroneous definitions of substitution in the - literature of logic and programming semantics. - Stoy, Joseph E. - See Stoy 1977 for a - careful discussion of substitution. -
    • -
    + 应用。 + +尽管代换思想很简单,但给出代换过程的严格数学定义却出乎意料地复杂。问题出在于用于 + 程序的形式参数 + 函数的参数 + </SPLITINLINE> + 的名称与在表达式中使用的(可能是相同的)名称之间可能产生的混淆。实际上,逻辑和程序语义文献中对代换的错误定义历史悠久。Stoy, Joseph E.请参见Stoy 1977,以了解有关代换的详细讨论。 +
  • +
- - Applicative order versus normal order - + + 应用序与正则序 + - - According to the description of evaluation given in - - - section, - - - section, - + + 根据在 + + 第节 + - the interpreter first evaluates the + 中给出的求值描述,解释器首先评估 - - operator - - - function - + 运算符 + 函数 - and + 和 - - operands - - - argument expressions - + 操作数 + 参数表达式 - and then applies the resulting + ,然后将结果 - - procedure - - - function - + 程序 + 函数 - to the resulting arguments. This is not the only way to perform evaluation. - An alternative evaluation model would not evaluate the + 应用到结果参数上。这并不是执行求值的唯一方法。替代的求值模型不会在它们的值被需要之前评估 - - operands - - - arguments - + 操作数 + 参数 - until their values were needed. Instead it would first substitute + 。而是,它将首先将 - - operand - - - argument - + 操作数 + 参数 - expressions for parameters until it obtained an expression involving - only + 表达式替换为参数,直到获得一个只涉及 - primitive operators, - operators and primitive functions, + 原语运算符, + 运算符和原语函数, - and would then perform the evaluation. If we - used this method, the evaluation of + 的表达式,然后执行求值。如果我们使用这种方法,求值将... (f 5) @@ -3230,9 +440,9 @@ square(6) + square(10) f(5) - - would proceed according to the sequence of expansions - + + 将按照扩展的顺序进行 + (sum-of-squares (+ 5 1) (* 5 2)) @@ -3247,9 +457,9 @@ square(5 + 1) + square(5 * 2) (5 + 1) * (5 + 1) + (5 * 2) * (5 * 2) - - followed by the reductions - + + 随后是化简 + (+ (* 6 6) (* 10 10)) @@ -3264,27 +474,26 @@ square(5 + 1) + square(5 * 2) 136 - - This gives the same answer as our previous evaluation model, but the - process is different. In particular, the evaluations of - - - (+ 5 1) - + + 这给出了与我们之前的求值模型相同的答案,但过程是不同的。特别是, + + + (+ 5 1) + 5 + 1 - - (* 5 2) - + + (* 5 2) + 5 * 2 - 在此处执行两次,对应于表达式的简化 + 的求值在这里各执行两次,分别对应于表达式的化简。 (* x x) @@ -3292,103 +501,92 @@ square(5 + 1) + square(5 * 2) x * x - - with x replaced respectively by - - - (+ 5 1) - + + 与 +x + 分别被替换为 + + + (+ 5 1) + 5 + 1 - 和 + 和 - - (* 5 2). - + + (* 5 2). + 5 * 2. - - This alternative fully expand and then reduce - evaluation method is known as - normal-order evaluation - normal-order evaluation, in contrast to the evaluate - the arguments and then apply method that the interpreter actually - uses, which is called - applicative-order evaluation - applicative-order evaluation. It can be shown that, for + + 这种替代的完全展开然后化简求值方法称为 + 正则序求值 + 正则序求值,与解释器实际使用的评估参数然后应用方法形成对比,后者称为 + 应用序求值 + 应用序求值。可以证明,对于可以通过代换建模的 - procedure - function + 程序 + 函数 - applications that can be modeled using substitution (including all the + 应用(包括本书前两章中所有的 - procedures - functions + 程序 + 函数 - in the first two chapters of this book) and that yield legitimate values, - normal-order and applicative-order evaluation produce the same value. - (See exercise - for an instance of an illegitimate value where normal-order - and applicative-order evaluation do not give the same result.) - - + )并且能够产生合法值的情况,正则序和应用序求值会产生相同的值。(请参见练习中的一个例子,其中正则序和应用序求值未能产生相同的结果,此时的值被称为非法。) + + + - + Lisp - applicative-order evaluationLispin Lisp - Lispapplicative-order evaluation in - + 应用序求值Lisp在 Lisp 中 + Lisp在应用序求值中的 + JavaScript - applicative-order evaluationJavaScriptin JavaScript - JavaScriptapplicative-order evaluation in + 应用序求值JavaScript在 JavaScript 中 + JavaScript在应用序求值中的 - uses applicative-order evaluation, partly because of the - additional efficiency obtained from avoiding multiple evaluations of - expressions such as those illustrated with + 使用应用序求值,部分原因是通过避免对表达式的多次求值而获得的额外效率,例如以上用 - - (+ 5 1) - and (* 5 2) - + + (+ 5 1) + 和 (* 5 2) + 5 + 1 - and 5 * 2 + 和 5 * 2 - above and, more significantly, because normal-order evaluation - becomes much more complicated to deal with when we leave the realm of + 所示的表达式,更重要的是,当我们离开可以通过代换建模的 - procedures - functions + 程序 + 函数 - that can be modeled by substitution. On the other hand, - normal-order evaluation can be an extremely valuable tool, and we will - investigate some of its implications in chapters 3 and 4.In - chapter 3 we will introduce stream processing, which is a - way of handling apparently infinite data structures - by incorporating a limited form of normal-order evaluation. In - section we will modify the - Scheme - JavaScript + 领域时,正则序求值变得更加复杂。另一方面,正则序求值可以成为一个极其有价值的工具,我们将在第3章和第4章中探讨它的一些含义。在第3章中,我们将介绍 流处理,这是一种通过结合有限形式的正则序求值来处理显然无穷的数据结构的方法。在节中,我们将修改 + + Scheme + JavaScript - interpreter to produce a normal-order variant of + 解释器,以生成正则序的变体 - Scheme. - JavaScript. + Scheme。 + JavaScript。 - - - substitution model of procedure application - + + + 程序应用的代换模型 + - substitution model of function application + 函数应用的代换模型 -
\ No newline at end of file + + diff --git a/xml/cn/chapter1/section1/subsection6.xml b/xml/cn/chapter1/section1/subsection6.xml index be6de1d3d..461640af5 100644 --- a/xml/cn/chapter1/section1/subsection6.xml +++ b/xml/cn/chapter1/section1/subsection6.xml @@ -1,22 +1,29 @@ - + 条件表达式和谓词 - + - - The expressive power of the class of + + 目前我们可以定义的 - - 过程 - + + procedure + - 函数 + function - + + 类别的表达能力非常有限,因为我们无法进行测试,并依据测试结果执行不同的操作。 + For instance, we cannot define a procedure that computes the - 绝对值 + absolute value + absolute + value of a number by testing whether the number is positive, negative, + or zero and taking different actions in the different cases according + to the rule + \[\begin{array}{lll} |x| & = & \left\{ \begin{array}{rl} x & \mbox{if $x>0$} \\ @@ -27,8 +34,16 @@ \end{array}\] This construct is called a - 案例分析通用案例分析条件特殊形式(那些标注为 ns 的不在 IEEE Scheme 标准之内)条件条件条件表达式条件条件cond (which stands for - 条件 + case analysisgeneral + case analysis, and + there is a special form in Lisp for notating such a case + analysis. It is called + cond + special forms (those marked ns are not in the IEEE Scheme standard)condcond + conditional expressioncondcond + cond (which stands for + conditional), and it is used as follows: + abs abs_definition abs_example @@ -55,27 +70,45 @@ consisting of the symbol cond followed by - 括号限定条件限定条件子句($\langle p\ e \rangle$) + parenthesesdelimiting conddelimiting cond clauses + parenthesized pairs of expressions + ($\langle p\ e \rangle$) called - 子句,条件条件子句子句谓词条件条件的子句条件表达式谓词,结果,备选谓词 - 解释为 - - + clause, of a cond + condclause + clauses. The first expression in each pair is a + predicatecondof cond clause + conditional expressionpredicate, consequent, alternative of + predicatethat is, an expression whose value is + interpreted as either true or false. + Interpreted as either + true + false \#t - + true \#f - - 真或假 - 意思是这样的:在 Scheme 中,有两个特殊的值,由常量 #t#f 表示。 - 当解释器检查一个谓词的值时,它将 #f 解释为假。任何其他值 - 被视为真。(因此,提供 #t - 在逻辑上是不必要的,但很方便。)在本书中,我们将使用名称 true 和 - false, - 分别与值 #t 和 - #f 关联。 - - 我们从条件表达式开始,因为它们更适合替代模型。注意,第 1.1.1 至 1.1.7 节中的所有函数都以单一的返回语句为主体,这强调了条件表达式的作用。条件语句将在 1.3.2 节介绍。 - 绝对值 + false + true or false + means this: In Scheme, there are two distinguished values that are + denoted by the constants + #t and #f. + When the interpreter checks a predicates value, it interprets + #f as false. Any other value + is treated as true. (Thus, providing #t + is logically unnecessary, but it is convenient.) In this book we will + use names true and + false, + which are associated with the values #t and + #f respectively. + + + + 我们在这里从条件表达式开始,因为它们更适合于代换模型。请注意,第 1.1.1 节到第 1.1.7 节中的所有函数的主体有一个单一的返回语句,这强调了条件表达式的重要性。条件语句将在 1.3.2 中介绍。 + + 例如,我们无法声明一个计算 + 绝对值 + 的函数,通过测试该数字是否为非负数,并根据规则在每种情况下采取不同的行动。 + \[\begin{array}{lll} |x| & = & \left\{ \begin{array}{rl} x & \mbox{if $x \geq 0$} \\ @@ -83,9 +116,15 @@ \end{array} \right. \end{array}\] - - 我们简化了示例,以便可以只使用一个条件表达式。 - 案例分析案例分析条件表达式 + + + 我们简化例子,以便可以用一个条件表达式解决问题。 + + 这种结构是一种 + 情况分析 + 情况分析,可以使用 + 条件表达式在 JavaScript 中编写。 + abs abs_definition abs_example @@ -102,135 +141,171 @@ function abs(x) { abs(-5); - - which could be expressed in English as 如果$x$大于或等于零,则返回$x$;否则返回$- x$ + + 可以用英语表达为 如果 $x$ 大于或等于零,则返回 $x$; 否则 返回 $- x$。 + 条件表达式的一般形式是 + predicate ? consequent-expression : alternative-expression - - Conditional - 条件表达式语法形式条件表达式?:;3谓词条件条件表达式的truetrue(关键字)falsefalse(关键字)关键字truetrue关键字falsefalse表达式原始布尔型布尔值(true, false)谓词truefalse布尔truefalse谓词结果表达式备选表达式 + + 条件 + 条件表达式 + 语法形式条件表达式 + ?:;3 + 谓词条件的条件表达式 + truetrue (关键字) + falsefalse (关键字) + 关键字truetrue + 关键字falsefalse + 表达式原语布尔 + 布尔值 (true, false) + 表达式以一个 + 谓词开始, + 也就是一个值为 + truefalse 的表达式,这在 JavaScript 中是两个特定的 + 布尔 值。 + 原语布尔表达式 + true 和 + false 显然评估为布尔值 true 和 false,分别。 + 谓词 + 后面跟着一个问号, + 后续表达式, + 一个冒号,最后是 + 替代表达式。 + - 条件表达式是 - 条件的计算 - 计算条件条件 - 按如下方式计算。谓词 - $\langle p_1 \rangle$ 首先被计算。 - 如果其值为假,则 - $\langle p_2 \rangle$ 被计算。 - 如果 $\langle p_2 \rangle$ 的 - 值也为假,则 - $\langle p_3 \rangle$ 被计算。 - 这个过程继续,直到找到一个值为真的谓词,在这种情况下,解释器会返回 - 对应的 - 结果条件条件子句 - 结果表达式 - $\langle e \rangle$ 作为该子句的值作为条件表达式的值。如果所有的 - $\langle p \rangle$ 都不为真,则 条件 的值是未定义的。 + Conditional expressions are + condevaluation of + evaluationof condof cond + evaluated as follows. The predicate + $\langle p_1 \rangle$ is evaluated first. + If its value is false, then + $\langle p_2 \rangle$ is evaluated. + If $\langle p_2 \rangle$s + value is also false, then + $\langle p_3 \rangle$ is evaluated. + This process continues until a predicate is + found whose value is true, in which case the interpreter returns the + value of the corresponding + consequentcondof cond clause + consequent expression + $\langle e \rangle$ of the + clause as the value of the conditional expression. If none of the + $\langle p \rangle$s + is found to be true, the value of the cond + is undefined. - + - 要 - 计算条件表达式的 - 条件表达式的计算 - 计算一个条件表达式, - 解释器首先计算该表达式的 + 为了 + 求值条件表达式的 + 条件表达式的求值 + 评估一个条件表达式, + 解释器首先评估该表达式的 谓词。 - 如果该谓词的值为真,解释器会计算 - 结果条件表达式的条件表达式 - 结果表达式并以其值作为条件的值返回。 - 如果谓词 - 的值为假,则会计算 - 备选条件表达式的条件表达式 - 备选表达式并以其值作为 - 条件的值返回。 + 如果 + 谓词 + 评估为 true,解释器将评估 + 后续条件表达式的条件表达式 + 后续表达式 并将其值作为条件的值返回。 + 如果 谓词 + 评估为 false,它将评估 + 替代条件表达式的条件表达式 + 替代表达式 并将其值作为条件的值返回。 条件表达式非布尔值作为谓词 条件语句 - - 在完整的 JavaScript 中接受任意值,不仅是布尔型,作为 - 谓词表达式的计算结果(详见脚注 - 在第节)。本书中的程序 - 只使用布尔值作为条件语句的谓词。 + + 在完整的 JavaScript 中接受任何值,而不仅仅是布尔值,作为评估 + 谓词 表达式的结果(有关详细信息,请参见脚注 + 在节中)。本书中的程序 + 仅使用布尔值作为条件的谓词。 - - + + + - 术语 - 谓词 - 谓词用于返回真或假的过程,以及评估为真或假的表达式。 - 绝对值过程 abs 使用 - >(原始数值比较谓词) - <(原始数值比较谓词) - =(原始数值相等谓词) - 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)>> - 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)<< - 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)== - 数值比较 - 数值相等 - 相等数值数值 - 原始谓词 >、 - < 和 - = - Abs 还使用 - -(原始减法过程)作为取负 - 原始过程(那些标注为 ns 的不在 IEEE Scheme 标准之内)-- - 操作符减号-, - 当与单一操作数一起使用时,如(- x), - 表示取反。 这些以两个数作为参数, - 分别测试第一个数是否大于、小于或等于第二个数,并返回相应的真假值。 + The word + predicate + predicate is used for procedures that return true + or false, as well as for expressions that evaluate to true or false. + The absolute-value procedure abs makes use of the + > (primitive numeric comparison predicate) + < (primitive numeric comparison predicate) + = (primitive numeric equality predicate) + primitive procedures (those marked ns are not in the IEEE Scheme standard)>> + primitive procedures (those marked ns are not in the IEEE Scheme standard)<< + primitive procedures (those marked ns are not in the IEEE Scheme standard)== + number(s)comparison of + number(s)equality of + equalitynumbersof numbers + primitive predicates >, + <, and + =. + Abs also uses + - (primitive subtraction procedure)as negation + primitive procedures (those marked ns are not in the IEEE Scheme standard)-- + the minus operator -, + which, when used with a single + operand, as in (- x), + indicates negation. These take two numbers as arguments and + test whether the first number is, respectively, greater than, less than, + or equal to the second number, returning true or false accordingly. - + + - 术语 + 词 谓词 - 谓词用于返回真或假的运算符和函数,以及评估为真或假的表达式。绝对值函数 - abs 使用 - >=(数值比较运算符) - >=(数值比较) - 数值比较 - 数值相等 - 原始谓词 >=, - 一个比较运算符,需要两个数作为参数并测试第一个数是否大于或等于第二个数,并返回相应的真假值。 + 谓词 用于返回 true 或 false 的运算符和函数,以及用于评估为 true 或 false 的表达式。绝对值函数 + abs 利用 + >= (数值比较运算符) + >= (数值比较) + 数字比较 + 数字相等性 + 原语谓词 >=, + 一个接受两个数字作为参数并测试第一个数字是否大于或等于第二个数字的运算符,返回 true 或 false。 - - - + + + + - 另一种编写绝对值过程的方法是 - + 另一种写绝对值过程的方法是 + abs abs_example - + (define (abs x) (cond ((< x 0) (- x)) (else x))) - - - 英文表述为 - 如果 $x$ 小于零 - 返回 $- x$;否则返回 - $x$ - else条件中的特殊符号) - Else 是一个特殊符号,可以用在 - $\langle p \rangle$ 在 - 条件的最终子句中。 - 这会使 条件 返回其值 - 当所有前面的子句都被跳过时的对应 - $\langle e \rangle$ 的值。 - 事实上,任何总是评估为真值的表达式都可以用作这里的 - $\langle p \rangle$。 + + + 可以用英语表达为 + 如果 $x$ 小于零 + 则返回 $- x$; 否则返回 + $x$. + else (在 cond 中的特殊符号) + Else 是一个特殊符号,可以在 + cond 的最终子句中替代 $\langle p \rangle$。 + 这使得 cond 返回其值 + 对应的 + $\langle e \rangle$ + 每当所有先前的条款被跳过时。事实上,任何 + 始终评估为真值的表达式都可以在这里用作 + $\langle p \rangle$。 - + - + @@ -244,9 +319,16 @@ abs(-5); (- x) x)) - 条件表达式ififif特殊形式(那些标注为ns的不在 IEEE Scheme 标准之内)ififif, a restricted + + conditional expressionifif + This uses the + if + special forms (those marked ns are not in the IEEE Scheme standard)ifif + special form if, a restricted type of conditional that can be used when there are precisely - 案例分析两种两种情况(if)if + case analysistwowith two cases (if) + two cases in the case + analysis. The general form of an if expression is @@ -254,38 +336,49 @@ abs(-5); To - if的计算计算ififif谓词,结果和备选if expression, + ifevaluation of + evaluationof ifof if + ifpredicate, consequent, and alternative of + evaluate an if expression, the interpreter starts by evaluating the - 谓词ifif$\langle \textit{predicate}\rangle$ + predicateifof if + $\langle \textit{predicate}\rangle$ part of the expression. If the $\langle \textit{predicate}\rangle$ evaluates to a true value, the interpreter then evaluates the - 结果ifif$\langle \textit{consequent}\rangle$ + consequentifof if + $\langle \textit{consequent}\rangle$ and returns its value. Otherwise it evaluates the - 备选if$\langle \textit{alternative}\rangle$ - and returns its value. 一个微小的 - 区别 - ifcondcond - condifif - 表达式序列cond的结果中 - 在 if 和 - cond 之间是 - $\langle e \rangle$ - 每个cond - 子句的部分可以是表达式的序列。 - 如果相应的 $\langle p \rangle$ - 被发现为真, - 这些表达式 $\langle e \rangle$ - 按顺序计算,序列中最后一个表达式的值作为 - cond 的值返回。 - 然而,在一个 if 表达式中, - $\langle \textit{consequent}\rangle$ 和 - $\langle \textit{alternative}\rangle$ 必须是单一的表达式。 + alternative of if + $\langle \textit{alternative}\rangle$ + and returns its value.A minor + difference + ifcondcond vs. + condifif vs. + sequence of expressionsin consequent of cond + between if and + cond is that the + $\langle e \rangle$ + part of each cond + clause may be a sequence of expressions. + If the corresponding $\langle p \rangle$ + is found to be true, + the expressions + $\langle e \rangle$ + are evaluated in sequence and the value of the final + expression in the sequence is returned as the value of the + cond. + In an if expression, however, + the $\langle \textit{consequent}\rangle$ and + $\langle \textit{alternative}\rangle$ must + be single expressions. + - - If 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} |x| &=& \left\{ \begin{array}{rl} x & \mbox{if $x > 0$} \\ @@ -294,10 +387,9 @@ abs(-5); \end{array} \right. \end{array}\] - - In JavaScript, we express a case analysis with multiple cases by nesting - conditional expressions as alternative expressions inside other conditional expressions: - + + 在 JavaScript 中,我们通过在其他条件表达式内部将条件表达式嵌套作为替代表达式来表达具有多个条件的情况分析: + abs abs_example @@ -309,9 +401,22 @@ function abs(x) { : - x; } - - Parentheses are not needed around the alternative expression - x === 0 ? 0 : - x条件表达式备选条件表达式的备选结合性条件表达式条件表达式的条件表达式右结合性右结合?: + + 替代表达式 + x === 0 ? 0 : - x 周围不需要括号,因为条件表达式的语法形式 + 条件表达式替代作为条件表达式的替代 + 结合性条件表达式的条件表达式 + 条件表达式的右结合性 + 右结合的 + 是右结合的。 + 解释器忽略空格和换行符,这里插入是为了可读性,以对齐 + ?: + 在情况分析的第一个谓词下。 + 一般的情况分析 + 情况分析一般 + 的形式是 + + p$_1$ ? e$_1$ @@ -322,19 +427,38 @@ $\vdots$ ? e$_n$ : final-alternative-expression - - We call a predicate $p_i$ - together with its consequent expression - $e_i$ - a - 案例分析的子句谓词子句子句的子句案例分析序列子句的序列p$_1$. - If its value is false, then p$_2$ - is evaluated. - If p$_2$p$_3$ - is evaluated. This process continues until a predicate is - found whose value is true, in which case the interpreter returns the - value of the corresponding - 结果子句子句的ep + + 我们称谓词 +$p_i$ + 连同其后续表达式 +$e_i$ + 一个 + 情况分析的子句 + 谓词子句的子句 + 子句。情况分析 + 可以看作是一系列子句,后面跟着一个最终的 + 替代表达式。 + 情况分析子句序列的序列 + 根据条件表达式的评估, + 情况分析是通过首先评估 + 谓词 p 来进行评估的。 +$_1$ + 如果它的值为假,那么 p +$_2$ + 被评估。 + 如果 p +$_2$ + 的值也是假,那么 p +$_3$ + 被评估。此过程继续,直到找到一个谓词,其值为 true,此时,解释器返回对应的 + 后续子句的子句 + 后续表达式 + e + 作为情况分析的值。 + 如果没有任何 + p 被发现为 true,情况分析的值 + 就是最终替代表达式的值。 + @@ -345,236 +469,296 @@ $\vdots$ =, and >, there are logical composition operations, which enable us to construct compound predicates. The three most frequently used are these: -
  • +
      +
    • and - 特殊形式(那些标注为ns的不在 IEEE Scheme 标准之内)andand - and的计算 - 计算andand + special forms (those marked ns are not in the IEEE Scheme standard)andand + andevaluation of + evaluationof andof and (and $\langle e_1\rangle \ldots \langle e_n \rangle$ )

      - 解释器按从左到右的顺序逐个计算 - $\langle e \rangle$ 表达式。 - 如果任何 - $\langle e \rangle$ 的计算结果为假, - 那么 and 表达式的值为假, - 其余的 + The interpreter evaluates the expressions + $\langle e \rangle$ one at a time, + in left-to-right order. If any + $\langle e \rangle$ evaluates to false, + the value of the and expression is + false, and the rest of the $\langle e \rangle$s - 不再计算。如果所有的 + are not evaluated. If all $\langle e \rangle$s - 计算结果都是真值, 那么 + evaluate to true values, the value of the and - 表达式的值是最后一个表达式的值。 -
    • + expression is the value of the last one. +
    • +
    • or - 特殊形式(那些标注为ns的不在 IEEE Scheme 标准之内)oror - or的计算 - 计算oror + special forms (those marked ns are not in the IEEE Scheme standard)oror + orevaluation of + evaluationof orof or (or $\langle e_1 \rangle\ldots \langle e_n \rangle$)

      - 解释器按从左到右的顺序逐个计算表达式 - $\langle e \rangle$。 - 如果任何 + The interpreter evaluates the expressions + $\langle e \rangle$ one at a time, in + left-to-right order. If any $\langle e \rangle$ - 的计算结果为真值,这个值将作为 - or 表达式的值返回,其余的 - $\langle e \rangle$s 不再计算。 - 如果所有的 + evaluates to a true value, that value is returned as the value of the + or expression, and the rest of the + $\langle e \rangle$s are not + evaluated. If all $\langle e \rangle$s - 的计算结果为假, 那么 - or 表达式的值为假。 -
    • + evaluate to false, the value of the + or expression is false. +
    • +
    • not - 原始过程(那些标注为ns的不在 IEEE Scheme 标准之内)notnot + primitive procedures (those marked ns are not in the IEEE Scheme standard)notnot (not $\langle e \rangle$)

      - not 表达式的值为真 - 当表达式 $\langle e \rangle$ - 的计算结果为假,否则为假。 -
    - and为什么是特殊形式or为什么是特殊形式and and + The value of a not expression is true + when the expression $\langle e \rangle$ + evaluates to false, and false otherwise. +
  • +
+ andwhy a special form + orwhy a special form + Notice that and and or are special forms, not procedures, because the subexpressions are not necessarily all evaluated. Not is an ordinary procedure. - - In addition to primitive predicates such as - >(数值比较运算符)>=0<(数值比较运算符)>=1<=(数值比较运算符)===数值作为数值相等运算符"!==;4作为数值比较运算符数值>(数值比较)===1<(数值比较)===2<=(数值比较)===(用于数值比较)"!==(用于数值比较);4相等数值数值的相等>=><<====!==目前,我们将这些运算符限制在数值参数内。在第节中,我们将泛化等价和不等谓词 ===!==
  • - expression$_1$ && - expression$_2$

    - 此操作表示 + + + + 除了原始谓词,例如 + > (数值比较运算符) + >=0< (数值比较运算符) + >=1<= (数值比较运算符) + ===数值作为数值相等性运算符 + "!==;4作为数值比较运算符数值 + > (数值比较) + ===1< (数值比较) + ===2<= (数值比较) + === (用于数值比较) + "!== (用于数值比较);4 + 相等性数字的数字 + >=, + >, + <, + <=, + === 和 + !== 这些都应用于 + 数字, + + 目前,我们将这些运算符限制为数字参数。在节中,我们将对相等性和不等性谓词 + === 和 + !== 进行概括。</FOOTNOTE> + 有逻辑组合操作,这使我们能够构建复合谓词。三种最常用的操作是: +
      +
    • + + 表达式 +$_1$ + && + 表达式 +$_2$

      + 该操作表示 语法糖;1&& 和 {\tt "|"|} 作为 - &&(逻辑合取);1 - &&(逻辑合取)的计算;1 - 语法形式逻辑合取(&&) - 逻辑合取 + && (逻辑与);1 + && (逻辑与)的求值;1 + 语法形式逻辑与 (&&) + 逻辑与 合取 - 计算&&的 ;1 - 逻辑合取,大致等同于英文单词和。 - 我们假设这个假设由脚注中提到的限制证明是合理的。完整的 JavaScript - 需要考虑 expression$_1$ 计算结果既不是真也不是假的情况。 这个语法形式是语法糖简单为方便替代表面结构而设计的语法形式有时被称为语法糖,使用这个术语是彼得·兰丁 + 求值&&的 ;1 + 逻辑与,大致意思与英语单词 and. + 我们假设这一假设是由脚注中提到的限制所证明的 + 。完整的 JavaScript 需要考虑评估 表达式$_1$ 的结果既不是 true 也不是 false 的情况。 这个语法形式被认为是语法糖语法形式只是那些可以用更统一的方式编写的方便替代表面结构,有时称为 语法糖,这个短语源自 Landin, Peter - 语法糖彼得·兰丁创造的。 - 对于

      - expression$_1$ ? - expression$_2$ : - false。 -
    • - expression$_1$ + 语法糖 + Peter Landin。 + 用于

      + 表达式 $_1$ + ? + 表达式 +$_2$ + : + false。 +
    • + +
    • + 表达式$_1$ || - expression$_2$

      - 此操作表示 - {\tt "|"|}(逻辑析取);2 - {\tt "|"|}(逻辑析取);2的计算 - 语法形式逻辑析取 ({\tt "|"|}) - 逻辑析取 + 表达式$_2$

      + 该操作表示 + {\tt "|"|} (逻辑或);2 + {\tt "|"|} (逻辑或);2的求值 + 语法形式逻辑或 ({\tt "|"|}) + 逻辑或 析取 - 计算的{\tt "|"|}的 ;2 - 逻辑析取,大致等同于英文单词或。 - 我们假设这个语法形式是语法糖对

      - expression$_1$ ? + 求值的 {\tt "|"|}的 ;2 + 逻辑或,大致意思与英语单词 or. + 我们假设这个语法形式是

      + 表达式$_1$ ? true : - expression$_2$。 -
    • + 表达式$_2$。 + +
    • +
    • ! - expression

      - 此操作表示 - "!(逻辑否定运算符);399 - ;399"!(逻辑否定) + 表达式

      + 该操作表示 + "! (逻辑非运算符);399 + ;399"! (逻辑非) 否定逻辑 ("!) - 逻辑否定,大致等同于英文单词不是。 - 表达式的值为真,当 - expression - 的计算结果为假,而当 - expression - 的计算结果为真时,值为假。 -
    - Notice that &&||&&(逻辑合取);1为什么是语法形式{\tt "|"|}(逻辑析取);2为什么是语法形式!一元二元!-作为数值否定运算符-(数值否定运算符)否定数值 (-)二元运算符一元运算符前缀运算符前缀运算符abs- x
    + 逻辑非,大致意思与英语单词 not. + 当 表达式 的求值为假时,该表达式的值为真,当 表达式 的求值为真时,该表达式的值为假。 +
  • +
+ 注意 + + && 和 + || 是语法形式, + 而不是运算符; + && (逻辑与);1为何是语法形式 + {\tt "|"|} (逻辑或);2为何是语法形式 + 它们的右侧表达式并不总是被求值。另一方面,运算符 + ! + 遵循节 + 中的求值规则。 + 它是一个 一元 运算符,这意味着它只接受一个参数,而到目前为止讨论的算术运算符和原始谓词都是 二元,接受两个参数。运算符 + ! 位于其参数之前; + 我们称它为 + -作为数值取反运算符 + - (数值取反运算符) + 否定数值 (-) + 二元运算符 + 一元运算符 + 前缀运算符 + 前缀运算符。另一个前缀运算符是 + 数值取反运算符,例如在上述 abs 函数中表达式 - x 的例子。 +
- - + + - 作为如何使用这些谓词的一个例子,条件一个数 $x$ 在范围 - $5 < x < 10$ 可能表示为 - - + 作为这些用法的一个例子,数字 $x$ 在范围 + $5 < x < 10$ 的条件可以表示为 + + (and (> x 5) (< x 10)) - - + + - + - 作为如何使用这些谓词的一个例子,条件一个数 $x$ 在范围 - $5 < x < 10$ 可能表示为 - + 作为这些谓词用法的一个例子,数字 $x$ 在范围 + $5 < x < 10$ 的条件可以表示为 + x > 5 && x < 10 - + 语法形式 && - 比较运算符的优先级低于 + 的优先级低于比较运算符 > - 和<,以及 - 条件表达式语法形式 - $\cdots$?$\cdots$:$\cdots$ - 优先级低于我们目前遇到的任何其他运算符, - 这是我们在上述 abs 函数中使用的一个特性。 + 和<,并且 + 条件表达式的语法形式 + $\cdots$?$\cdots$:$\cdots$ + 的优先级低于我们目前遇到的任何其他运算符,这是我们在 + 上述 abs 函数中使用的属性。 - - - 作为另一个例子,我们可以 + + + 作为另一个例子,我们可以 - + 定义 - + 声明 - 一个谓词来测试一个数字是否 - - - >= - - - 大于或等于另一个数字,如 - + 一个谓词来测试一个数字是否大于或等于另一个数字,表示为 + geq_example - + (define (>= x y) (or (> x y) (= x y))) - + function greater_or_equal(x, y) { return x > y || x === y; } - - 或者作为 + + 或者也可以表示为 - + >= - + - + geq_example - + (define (>= x y) (not (< x y))) - + function greater_or_equal(x, y) { return ! (x < y); } - - + + geq_example - + (>= 7 4) - + greater_or_equal(7, 4); - + - - + + - 函数 greater_or_equal, - 应用于两个数字时,表现与运算符 - >= 相同。一元运算符具有 - 优先级一元一元运算符的优先级 - 比二元运算符的优先级高,使得该示例中的括号是必要的。 + 函数 greater_or_equal 在应用于两个数字时,行为与运算符 + >= 相同。一元运算符的优先级 + 优先级一元运算符的优先级 + 高于二元运算符,这使得该例中的括号是必要的。 - - - - Below is a sequence of + + + + 以下是一系列 - + 表达式。 - + 语句。 - - - 表达式时打印的结果是什么? - + + 解释器对每个 + + + 表达式的结果是什么? + - 语句时打印的结果是什么? + 语句的结果是什么? - + + 假设按照展现的顺序来评估这一序列。 + ten 10 @@ -727,42 +911,45 @@ a === 4 * (a + 1); - - - 最后两个语句中条件表达式周围的括号是必要的,因为 - 条件表达式操作数运算符组合的操作数 - 条件表达式语法形式的 - 优先级条件表达式条件表达式的 + + + 括号围绕条件表达式在最后两个语句中是必要的,因为 + 条件表达式操作数作为运算符组合的操作数 + 条件表达式的语法形式优先级低于 + 优先级条件表达式的条件表达式 条件表达式的优先级 - 优先级低于算术运算符 + 算术运算符 +*。 + + + 以下是 + + + 表达式。 + + + 语句。 + + + 解释器对每个 + + + 表达式的结果是什么? + + + 语句的结果是什么? - - 解答由 GitHub 用户 Emekk 提供 -
    -
  1. 10
  2. -
  3. 12
  4. -
  5. 8
  6. -
  7. 3
  8. -
  9. 6
  10. -
  11. 3
  12. -
  13. 4
  14. -
  15. 19
  16. -
  17. false
  18. -
  19. 4
  20. -
  21. 16
  22. -
  23. 6
  24. -
  25. 16
  26. -
-
+ + 假设按照展现的顺序评估这一序列。 +
- - 将以下表达式翻译成 + + 将以下表达式翻译成 - + 前缀形式 - + JavaScript @@ -770,77 +957,90 @@ a === 4 \par\medskip - + $\begin{array}{l} \quad~~\dfrac{5+4+\left(2-\left(3-(6+\frac{4}{5})\right)\right)}{3 (6-2) (2-7)} \end{array}$ - + - + (5 + 4 + (2 - (3 - (6 + 4 / 5)))) / (3 * (6 - 2) * (2 - 7)); - + - - - + + + + - + 定义一个过程 - + 声明一个函数 - 接受三个数字作为参数,并返回两个较大数字的平方和。 + 接收三个数字作为参数并返回两个较大数字的平方和。 - + larger_two_square_example square_definition function f(x, y, z) { return square(x) + square(y) + square(z) - - // 减去最小的平方 + // subtract the square of the smallest square(x > y ? (y > z ? z : y) : (x > z ? z : x)); } - + (define (f x y z) (let ((smallest (if (> x y) (if (> y z) z y) (if (> x z) z x)))) (- (+ (square x) (square y) (square z)) (square smallest)))) - - + + - + larger_two_square_example - + (f 4 7 2) - + f(4, 7, 2); - - - + + + - - 注意我们的评估模型允许 - 组合复合表达式作为运算符的 - 复合表达式运算符作为组合的运算符 - 组合的运算符复合表达式作为 - 运算符是复合表达式的组合。利用这一观察来描述以下过程的行为: - + + + + + Observe that our model of evaluation allows for + combinationcompound expression as operator of + compound expressionoperatoras operator of combination + operator of a combinationcompound expression as + combinations whose + operators are compound expressions. Use this observation to describe + the behavior of the following procedure: + + (define (a-plus-abs-b a b) ((if (> b 0) + -) a b)) - + - - Observe that our model of evaluation allows for - 函数应用复合表达式作为函数表达式的复合表达式函数表达式作为应用的函数表达式函数表达式复合表达式作为a_plus_abs_b + + + 注意我们的评估模型允许 + 函数应用复合表达式作为函数表达式的 + 复合表达式函数表达式作为函数应用的表达式 + 函数表达式复合表达式作为 + 应用程序,这些应用程序的函数表达式是复合表达式。利用这一观察描述 + a_plus_abs_b 的行为: + plusminusexample function plus(a, b) { return a + b; } @@ -857,30 +1057,47 @@ function a_plus_abs_b(a, b) { a_plus_abs_b(5, -4); - - 注意,在 JavaScript 的条件表达式中,我们不能像在 Scheme 中那样直接使用运算符 +-,而是必须使用名称 plusminus,因为在中缀表示法中,中间只允许运算符符号,而不是复合表达式。 - - 根据第节,应用的计算过程如下: -
    -
  1. 计算应用的子表达式。 -
  2. -
  3. 计算函数表达式结果的返回表达式,其中每个参数被相应的参数表达式的结果替换。 -
  4. -
- 因此,应用 a_plus_abs_b(5, -4) 的计算 (1) 计算 a_plus_abs_b,结果是上面给出的函数,并且参数已经是值。所以我们需要计算 (2) 函数的返回表达式,其中参数被参数替换,因此:(-4 >= 0 ? plus : minus)(5, -4)。 - 使用相同的规则,我们需要 (1) 计算函数表达式,在这种情况下是条件表达式 -4 >= 0 ? plus : minus。由于谓词的计算结果为假,函数表达式计算结果为 minus。参数 (1) 再次已经是值。因此,我们最终计算 (2) minus 的主体,参数 ab 分别被 5 和 -4 替换,结果是 5 - (-4),最终将计算为 9。 -
+
+ + 请注意,在 JavaScript 中的条件表达式中,我们不能像在 Scheme 中那样直接使用运算符 + 和 + -,反而必须使用名称 plus 和 + minus,因为在中缀表示法中, + 只允许运算符符号出现在中间,而不允许复合表达式。 + + 根据节,应用程序的求值过程如下: +
    +
  1. 评估应用程序的子表达式。
  2. +
  3. 评估函数表达式的返回表达式,每个参数都替换为其各自参数表达式的结果。
  4. +
+ 因此,应用程序 + a_plus_abs_b(5, -4) 的求值 + (1) 评估 a_plus_abs_b,得到上面的函数,且参数已是值。所以我们需要评估 + (2) 函数的返回表达式,用参数替换为参数,从而得到: + (-4 >= 0 ? plus : minus)(5, -4)。根据相同规则,我们需要 (1) 评估函数表达式,在本例中是条件表达式 + -4 >= 0 ? plus : minus。由于谓词评估为假,函数表达式 + 评估为 minus。参数再次 (1) 已是值。因此,我们最终评估 (2) minus 的主体,参数 + a 和 + b 分别替换为 5 和 -4,结果为 + 5 - (-4),最终结果为 9。 +
- 正常顺序计算应用顺序与应用顺序计算正常顺序与 - + + + Ben Bitdiddle 发明了一项测试,用于判断他面对的解释器是使用 + 正则序求值应用序 vs. + 应用序求值正则序 vs. + 或正则序求值。他 + + 定义了以下两个过程: - + 声明了以下两个函数: - + + ptest (define (p) (p)) @@ -897,10 +1114,14 @@ function test(x, y) { return x === 0 ? 0 : y; } - - 表达式 + + + 然后他评估 + + 表达式 语句 - + + ptest (test 0 (p)) @@ -908,45 +1129,46 @@ function test(x, y) { test(0, p()); - 正常顺序计算条件条件表达式的条件表达式正常顺序计算的 - - 特殊形式if - - - 条件表达式 - - + + + 贝恩·比特迪尔发明了一项测试,用于确定他所面对的解释器是使用 + 应用序求值正则序 vs. + 正则序求值应用序 vs. + 应用序求值还是正则序求值。他 + + + 定义了以下两个过程: + + + 声明了以下两个函数: + + + + 解决方案: - 在应用顺序计算中, - test(0, p()), - 我们需要在计算函数 - test - 的返回表达式之前计算参数表达式。 + 在 + test(0, p()) 的应用序求值中, + 我们需要在评估函数 + test 的返回表达式之前,先评估参数表达式。 然而,参数表达式 - p() - 的计算将不会终止:它将不断计算形式为 - p()的应用表达式,因此 - test(0, p()) 的计算将不会生成有效的值。在正常顺序计算中,另一方面, + p() 的评估不会终止:它将不断评估形如 + p() 的应用表达式,因此 + test(0, p()) 的评估不会产生合法值。另一方面,在正则序求值中, 函数应用 - test(0, p()) - 将立即计算函数 - test - 的返回表达式, - x === 0 ? 0 : y - 在将参数 - x替换为 - 0和 - y替换为 - p()之后。 + test(0, p()) 将立即评估函数 + test 的返回表达式 + x === 0 ? 0 : y, + 替换参数 + x 为 + 0 和 + y 为 + p()。 替换的结果将是 0 === 0 ? 0 : p()。 - 谓词 - 0 === 0 - 的计算结果为 true,因此条件 - 表达式的计算结果为 0,无需 - 计算 p()。 + 谓词 0 === 0 的评估结果为真,因此条件表达式评估为 0,而不需要评估 + p() - +
diff --git a/xml/cn/chapter1/section1/subsection7.xml b/xml/cn/chapter1/section1/subsection7.xml index 88ef8a63d..7d65189cc 100644 --- a/xml/cn/chapter1/section1/subsection7.xml +++ b/xml/cn/chapter1/section1/subsection7.xml @@ -1,146 +1,159 @@ - 示例: 牛顿方法求平方根 + 示例:牛顿法的平方根方法 - + - - 过程数学函数 vs. - 函数(数学)过程 vs. - + + 过程数学函数与 + 函数(数学)过程与 + - 数学函数 vs. - 函数(数学)JavaScript 函数 vs. + 数学函数与 + 函数(数学)JavaScript函数与 - 过程, + 过程, 函数, - 上面介绍的,与普通数学函数非常相似。它们指定一个由一个或多个参数确定的值。但是数学函数和计算机 + 如上所述,过程与普通数学函数非常相似。它们 + 指定由一个或多个参数决定的值。但数学函数与计算机 - 过程 - 函数 - - 之间有一个重要的区别。 + 过程之间有一个重要区别。 + 函数之间存在一个重要区别。 + - + 过程 - + 计算机函数 必须是有效的。 - - - 举个例子,考虑计算平方根的问题。我们可以将平方根函数定义为 - + + + 作为一个例子,考虑计算平方根的问题。我们可以将平方根函数定义为 + \[ - \sqrt{x}\ =\text{ the }y\text{ such that }y \geq 0\text{ and } + \sqrt{x}\ =\text{ 使 }y\text{ 满足 }y \geq 0\text{ 且 } y^2\ =\ x \] - - 这描述了一个完全合法的数学函数。我们可以用它来识别一个数字是否是另一个数字的平方根,或者推导有关平方根的一般事实。另一方面,这个定义并没有描述 + + 这描述了一个完全合法的数学函数。我们可以用它来识别一个数字是否是另一个数字的平方根,或者推导出有关平方根的一般事实。另一方面,这个定义并没有描述一个 - 过程。 + 过程。 计算机函数。 - 实际上,它几乎没有告诉我们如何实际找到一个给定数的平方根。用 + 实际上,它几乎没有告诉我们如何实际找到给定数字的平方根。仅仅用 - 伪Lisp: + 伪Lisp: 伪JavaScript: - 来重新表述这个定义也无济于事。 - - + + (define (sqrt x) (the y (and (>= y 0) (= (square y) x)))) - + function sqrt(x) { return the y $\texttt{with}$ y >= 0 && square(y) === x; } - - 这只不过是回到了原点。 - - - - The contrast between + + 这只会引发问题。 + + + + function 和过程 - - 函数和过程 - + + 数学函数和计算机函数 + 数学函数和计算机函数 - 声明性与命令性知识命令性与声明性知识数学计算机科学 vs.计算机科学数学 vs.声明性和命令性描述密切相关,正如数学和计算机科学密切相关一样。例如,说程序产生的答案是 - 程序的正确性 - 正确的是对程序做出的声明性陈述。大量研究旨在建立 - 证明程序正确性 - 证明程序正确性的技术,而该学科的许多技术困难与在命令性陈述(程序由此构建)和声明性陈述(可用于推断事物)之间的过渡有关。 + + 之间的对比反映了描述事物属性与描述如何做事之间的普遍区别,或者有时也称为 + 声明性知识与命令性知识 + 命令性知识与声明性知识 + 声明性知识和命令性知识之间的区别。在 + 数学计算机科学与 + 计算机科学数学与 + 数学中,我们通常关注声明性(是什么) + 描述,而在计算机科学中,我们通常关注 + 命令性(如何)描述。 + 声明性和命令性描述是密切相关的,正如数学和计算机科学一样。例如,说明一个程序产生的答案是 + 程序正确性 + 正确就是对程序做出声明性陈述。有大量的研究旨在建立 + 程序正确性证明 + 证明程序正确性的技术,而这个主题的许多技术难点与在命令性语句(程序构建的基础)和声明性语句(可以用于推导事物)之间的转换有关。 - - 与此相关,编程语言设计的一个重要当前领域是探索所谓的 - 编程语言非常高级 - 非常高级语言 - 非常高级语言,在这种语言中,人们实际上以声明性陈述进行编程。 - - - 与此相关,编程语言设计者已经探索了所谓的 - 编程语言非常高级 - 非常高级语言 - 非常高级语言,其中人们实际上以声明性陈述进行编程。 + + 在相关方面,编程语言设计中的一个重要领域是探索所谓的 + 编程语言超高级 + 超高级语言 + 超高级语言,其中实际以声明性语句进行编程。 + + + 在相关方面,编程语言设计者探索了所谓的 + 编程语言超高级 + 超高级语言 + 超高级语言,其中实际以声明性语句进行编程。 - 这个想法是让解释器足够聪明,以便在给定程序员指定的是什么的知识后,它们可以自动生成如何做的知识。一般来说,这是不可能的,但在一些重要领域已经取得了进展。我们将在中重新探讨这一想法。 - - 过程数学函数 vs. - 函数(数学)过程 vs. - + 这个想法是使解释器足够复杂,以便在程序员指定的是什么知识的基础上,自动生成如何做知识。这在一般情况下是无法实现的,但在某些重要领域已经取得了进展。我们将在第章中重新讨论这个想法。 + + + 过程数学函数与 + 函数(数学)过程与 + - 数学函数 vs. - 函数(数学)JavaScript 函数 vs. + 数学函数与 + 函数(数学)JavaScript函数与 - - + + + 如何计算 平方根 - 牛顿方法平方用于平方根 - 平方根?最常见的方法是使用牛顿的逐次近似方法,该方法指出,只要我们有一个猜测 $y$ 作为数字 $x$ 的平方根的值,我们就可以通过将 $y$$x/y$ 平均来进行简单操作以获得更好的猜测(更接近实际平方根)。此平方根算法实际上是牛顿法则的一个特例,牛顿法则是一种用于求解方程根的一般技术。平方根算法本身是由亚历山大的希伦在公元一世纪开发的。我们将在中看到如何将一般的牛顿法表示为 + 牛顿平方的平方根 + 平方根?最常见的方法是使用牛顿法的逐次逼近,该方法指出,每当我们有一个猜测时 +$y$ 一个数的平方根的值 $x$ ,我们可以进行简单的操作,以获得更好的猜测(更接近实际平方根),通过平均 $y$$x/y$. 这个平方根算法实际上是牛顿法的一种特例,牛顿法是一种求解方程根的一般技术。平方根算法本身是由公元一世纪的亚历山大里亚的希罗(Heron of Alexandria)发展而来的。我们将看到如何在第节中将一般的牛顿法表示为 - + Lisp 过程 - + JavaScript 函数 - - 例如,我们可以如下计算 2 的平方根。假设我们的初始猜测是1: - + + 例如,我们可以按如下方式计算 2 的平方根。假设我们的初始猜测是1: \[ \begin{array}{lll} - \textrm{猜测} & \textrm{商} & \textrm{平均}\\[1em] + \textrm{Guess} & \textrm{Quotient} & \textrm{Average}\\[1em] 1 & {\displaystyle \frac{2}{1} = 2} & {\displaystyle \frac{(2+1)}{2} = 1.5} \\[1em] 1.5 & {\displaystyle \frac{2}{1.5} = 1.3333} & {\displaystyle \frac{(1.3333+1.5)}{2} = 1.4167} \\[1em] 1.4167 & {\displaystyle \frac{2}{1.4167} = 1.4118} & {\displaystyle \frac{(1.4167+1.4118)}{2} = 1.4142} \\[1em] 1.4142 & \ldots & \ldots \end{array} \] - - 继续这个过程,我们将获得越来越接近平方根的近似值。 - - - Now let被开方数 - + + 继续这个过程,我们将获得越来越接近平方根的更好近似值。 + + + 现在让我们将这个过程形式化为函数。我们从一个被开方数radicand(我们试图计算平方根的数字)的值和一个猜测值开始。如果该猜测对我们的目的足够好,我们就完成了;如果不够好,我们必须用改进的猜测重复该过程。我们将这个基本策略写成一个 + + 过程: - + 函数: - + + sqrt_iter is_good_enough improve @@ -167,10 +180,9 @@ function sqrt_iter(guess, x) { sqrt_iter(3, 25); - - A guess is improved by averaging it with the quotient of the radicand and - the old guess: - + + 通过将猜测值与被开方数和旧猜测的商相平均,可以改善猜测: + improve average_definition improve_example @@ -192,9 +204,9 @@ function improve(guess, x) { improve(3, 25); - - where - + + 其中 + average average_definition average_example @@ -216,17 +228,18 @@ function average(x, y) { average(3, 6); - - We also have to say what we mean by 足够好。我们通常会给 + + 我们还需要说明“足够好的”含义。以下的描述可以作为说明,但实际上这并不是一个很好的测试。(见习题。)这个想法是改善答案,直到它足够接近,使得其平方与被开方数之间的差距小于预定的容差(这里是0.001):我们通常给出 谓词命名约定 - 命名约定用于谓词 - 问号,用于谓词名称 - ,用于谓词名称 - 谓词名称以问号结尾,帮助我们记住它们是谓词。这只是一个风格上的约定。对于解释器来说,问号只是一个普通字符。我们通常会给 + 命名约定??用于谓词 + 问号,作为谓词名称 + ?, 作为谓词名称 + 以帮助我们记住它们是谓词。这仅是一个风格约定。就解释器而言,问号只是一个普通字符。我们通常给出 谓词命名约定 命名约定is_用于谓词 - is_,在谓词名称中 - 谓词名称以is_开头,帮助我们记住它们是谓词。 + is_, 作为谓词名称的开始 + 以帮助我们记住它们是谓词。 + is_good_enough abs_definition square_definition @@ -249,23 +262,22 @@ function is_good_enough(guess, x) { is_good_enough(1.41, 2); - - Finally, we need a way to get started. For instance, we can always guess - that the square root of any number - is注意,我们将初始猜测表示为1.0而不是1。这在许多Lisp实现中没有区别。 + + 最后,我们需要一种方法来开始。例如,我们可以总是假设任何数字的平方根为1:请注意,我们将初始猜测表达为1.0而不是1。这在许多Lisp实现中不会有什么区别。 有理数MIT在MIT Scheme中 精确整数 整数精确 - 整数除法 + 整数的除法 整数除法 - 有理数 - 整数,精确 - 小数点 - 数中的小数点 - MIT Scheme - 实现依赖 - 实现依赖 - 然而,MIT Scheme区分精确整数和小数值,两个整数相除得到一个有理数而不是小数。例如,10除以6得到5/3,而10.0除以6.0得到1.6666666666666667。(我们将在中学习如何实现对有理数的算术运算。)如果我们在平方根程序中以1作为初始猜测,而$x$是一个精确整数,那么平方根计算中产生的所有后续值将是有理数而非小数。对有理数和小数进行混合运算始终会得到小数,因此将初始猜测设为1.0会使所有后续值都变为小数。脚注已移除:它是特定于Scheme的(甚至更具体来说:特定于MIT Scheme) + 数字有理数 + 数字整数,精确 + 数字小数点 + 数字中的小数点 + MIT Scheme数字 + 实现依赖数字 + 数字实现依赖 + 然而,MIT Scheme区分精确整数和小数值,两个整数的除法产生有理数而不是小数。例如,10除以6得到5/3,而10.0除以6.0得到1.6666666666666667。(我们将在第节中学习如何对有理数进行算术运算。)如果我们在平方根程序中从初始猜测1开始,而$x$是一个精确整数,则在平方根计算中产生的所有后续值将是有理数而不是小数。在有理数和小数的混合操作中,总是会产生小数,因此以1.0作为初始猜测会迫使所有后续值都是小数。 + sqrt sqrt sqrt_iter @@ -280,25 +292,28 @@ function sqrt(x) { return sqrt_iter(1, x); } - - If we type these + + 如果我们将这些 - - 定义 - + + 定义 + - 声明 + 声明 - sqrt - just as we can use any + + 输入到解释器中,我们就可以使用 +sqrt + 就像我们可以使用任何 - + 过程: - + 函数: - + + sqrt_example sqrt @@ -376,56 +391,31 @@ square(sqrt(1000)); Newtons methodsquarefor square roots - - sqrt 程序还说明了我们迄今为止介绍的简单 - - - 迭代过程通过过程调用实现 - 过程式 - - - 迭代过程通过函数调用实现 - 函数式 - - - 语言足以编写可以在 C 或 Pascal 中编写的任何纯数值程序。这似乎令人惊讶,因为我们在语言中没有包含任何指示计算机反复执行某些操作的迭代循环结构 - (循环)结构。 - - - Sqrt-iter, - - - 函数 sqrt_iter, - - - 另一方面,演示了如何使用普通的过程调用而无需任何特殊结构来完成迭代。 - 过程。函数。对使用 - - - 过程 - - - 函数 - - - 调用来实现迭代的效率问题感到担忧的读者请注意尾递归中的说明。 + + 如果我们输入这些 - - 迭代过程通过过程调用实现 - + + 定义 + - 迭代过程通过函数调用实现 + 声明 - + 到解释器中,我们可以使用 + - - Alyssa P. Hacker 不明白为什么if需要作为 - if为什么是特殊形式 - 特殊形式需要 - 特殊形式提供。为什么我不能只是用cond来定义它作为一个普通过程呢?她问。 - Alyssa的朋友Eva Lu Ator声称这确实可以做到,她定义了一个if的新版本: + + + Alyssa P. Hacker doesnt see why if + needs to be provided as a + ifwhy a special form + special formneed for + special form. Why cant I just + define it as an ordinary procedure in terms of + cond? she asks. + Alyssas friend Eva Lu Ator claims this can indeed be done, and + she defines a new version of if: new_if @@ -434,7 +424,7 @@ square(sqrt(1000)); (else else-clause))) - Eva 为 Alyssa 演示了程序: + Eva demonstrates the program for Alyssa: new_if @@ -453,7 +443,8 @@ square(sqrt(1000)); 0 - 兴奋的 Alyssa 使用 new-if 重写了平方根程序: + Delighted, Alyssa uses new-if to rewrite + the square-root program: new_if is_good_enough @@ -467,26 +458,32 @@ square(sqrt(1000)); x))) - 当 Alyssa 尝试使用此方法计算平方根时会发生什么?解释。 + What happens when Alyssa attempts to use this to compute square roots? + Explain. - - Alyssa P. Hacker 不喜欢语法形式需要 - 条件表达式为什么是语法形式 - 条件表达式的语法,包括字符?:。 - 为什么我不能简单地声明一个普通的条件函数,其应用方式与条件表达式相同呢?她问道。 - 作为最初的计算机程序的构造与解释中的Lisp黑客,Alyssa更喜欢一种更简单、更统一的语法。 - Alyssa 的朋友 Eva Lu Ator 声称这确实可以做到,并且她声明了一个conditional - 函数,如下所示: - + + + Alyssa P. Hacker不喜欢 + 语法形式所需的 + 条件表达式为何是语法形式 + 条件表达式的语法,涉及到的字符 +? +和 +:. + + “我为什么不能仅仅声明一个普通的条件函数,它的应用方式就像条件表达式一样?”她问道。 + 作为最初的《计算机程序的结构与解释》的Lisp黑客,Alyssa更喜欢一种更简单、更统一的语法。 + Alyssa的朋友Eva Lu Ator声称这确实可以做到,她声明了一个条件函数,内容如下: + conditional function conditional(predicate, then_clause, else_clause) { return predicate ? then_clause : else_clause; } - - Eva 为 Alyssa 演示了程序: - + + Eva为Alyssa演示了程序: + conditional conditional(2 === 3, 0, 5); @@ -503,9 +500,11 @@ conditional(1 === 1, 0, 5); 0 - - 高兴的Alyssa使用conditional重写了平方根程序: - + + 高兴的Alyssa使用 +conditional + 以重写平方根程序: + delighted conditional is_good_enough @@ -519,53 +518,58 @@ function sqrt_iter(guess, x) { x)); } - - 当 Alyssa 尝试使用此方法计算平方根时会发生什么?解释。 - - - 任何调用sqrt_iter都会立即导致一个无限循环。原因是我们的应用序求值。sqrt_iter的返回表达式的求值需要首先求值其参数,包括递归调用sqrt_iter,无论谓词是求值为true还是false。当然,递归调用也是如此,因此conditional函数实际上从未真正被应用。 - - + + 当Alyssa尝试使用这个计算平方根时会发生什么?解释一下。 + + + 调用sqrt_iter会立即导致无限循环。原因在于我们的应用序求值。sqrt_iter的返回表达式的求值需要先评估其参数,包括对sqrt_iter的递归调用,无论谓词的求值结果是真的还是假的。当然,递归调用也会发生同样的情况,因此conditional函数实际上并没有被应用。 + + + - + + 用于计算平方根的 - - good-enough? - + + good-enough? + is_good_enough - 测试用于计算平方根对于非常小的数字将不是很有效。此外,在实际计算机中,算术运算几乎总是在有限精度下进行。这使得我们的测试对非常大的数字不够充分。用例子解释这些语句,显示测试对于小数和大数如何失败的。实现 + 测试对于寻找非常小的数字的平方根并不十分有效。此外,在实际计算机中,算术运算几乎总是以有限的精度进行。这使得我们的测试对于非常大的数字不够充分。解释这些陈述,并用示例说明测试如何在小数字和大数字中失败。 + 实现 - - good-enough? - + + good-enough? + is_good_enough - 的另一种策略是观察guess如何从一次迭代变化到下一次,并在变化是猜测的一小部分时停止。设计一个使用这种终止测试的平方根 + 的另一种策略是观察如何 +guess + 从一个迭代到下一个迭代的变化,并在变化仅是猜测的一小部分时停止。设计一个平方根 - + 过程 - + 函数 - 这对于小数和大数效果更好吗? + ,使用这种结束测试。这对于小号码和大号码的效果如何? - 绝对容差0.001在计算一个小值的平方根时太大。例如, + 当计算小值的平方根时,绝对容差0.001太大。例如, sqrt(0.0001) - 的结果是0.03230844833048122,而不是预期的值0.01,误差超过200%。 + 结果为0.03230844833048122,而不是期望值0.01,误差超过200%。

- 另一方面,对于非常大的值,舍入错误可能导致算法无法接近平方根,在这种情况下它将不会终止。 + 另一方面,对于非常大的值,舍入误差可能导致算法无法接近平方根,在这种情况下它将不会终止。

- 以下程序通过使用相对容差替换绝对容差来缓解这个问题。 - + 以下程序通过用相对容差替代绝对容差来缓解这个问题。 + abs_definition average_definition sqrt @@ -579,9 +583,9 @@ function is_good_enough(guess, x) { return abs(square(guess) - x) < guess * relative_tolerance; } - +
- + example_1.8 display(sqrt(0.0001)); @@ -592,40 +596,42 @@ display(sqrt(4000000000000));
- - - 牛顿方法用于 - 立方根牛顿通过牛顿的方法 - 牛顿方法立方用于立方根 - 立方根基于这样一个事实:如果 $y$$x$ 的立方根的一个近似值,那么更好的近似值由以下公式给出: - + + + 牛顿法用于 + 立方根牛顿通过牛顿法 + 牛顿法立方用于立方根 + 立方根的计算基于以下事实:如果 + $y$是$x$的立方根的近似值,则更好的近似值由以下值给出: + \[ \begin{array}{lll} \dfrac{x/y^{2}+2y} {3} \end{array} \] - - 使用此公式实现一个立方根 + + 使用这个公式来实现一个立方根 - - 过程 - + + 过程 + - 函数 + 函数 类似于平方根 - 过程。 + 过程。 函数。 - (在部分,我们将看到如何实现牛顿方法作为这些平方根和立方根的 + (在第节中,我们将看到如何将牛顿法一般性地实现为这些 + 平方根和立方根的 - 过程的 - 函数的抽象。) + 过程的抽象。 + 函数的抽象。 - + example_1.9 abs_definition cube_definition @@ -645,15 +651,15 @@ function cube_root(guess, x) { : cube_root(improve(guess, x), x); } - - - + + + - + example_1.9 cube_root(3, 27); - - + +
diff --git a/xml/cn/chapter1/section1/subsection8.xml b/xml/cn/chapter1/section1/subsection8.xml index 104f85035..48e1cc45e 100644 --- a/xml/cn/chapter1/section1/subsection8.xml +++ b/xml/cn/chapter1/section1/subsection8.xml @@ -1,203 +1,256 @@ - + - - 程序【8:0†try.txt】 - + + Procedures + - 函数【8:0†try.txt】 + Functions - 作为黑匣子抽象【8:0†try.txt】 + as Black-Box Abstractions - + - 平方根 + Sqrt - 函数sqrt + The function sqrt - 是通过一组相互定义的 + is our first example of a process defined by a set of mutually - 程序 - 函数 + defined procedures. + defined functions. - 定义的过程的第一个例子。注意到 + Notice that the - sqrt-iter的定义 - - sqrt_iter的声明 + definition of sqrt-iter + declaration of + sqrt_iter - 是 + is - - 递归程序递归程序定义 - + + recursive procedurerecursive procedure definition + - 递归函数递归函数声明 + recursive functionrecursive function declaration - 递归的;也就是说, + recursive; that is, the - 程序 - 函数 + + procedure + + + function + - 是基于自身定义的。能够基于自身定义一个 + is defined in terms of itself. The idea of being able to define a - 程序 - 函数 + + procedure + + + function + - 的概念可能令人不安;可能显得不清楚这种 - 循环定义如何能够有意义,更不用说由计算机执行的过程进行良好定义。这将在中更为详细地讨论。但首先,让我们考虑 - sqrt例子所展示的一些其他重要点。 + in terms of itself may be disturbing; it may seem unclear how such a + circular definition could make sense at all, much less + specify a well-defined process to be carried out by a computer. This will + be addressed more carefully in + section. But first + lets consider some other important points illustrated by the + sqrt example. - 注意到计算平方根的问题自然地分解成多个子问题: - 程序的结构 - 如何判断一个猜测是否足够好,如何改进一个猜测,等等。每项任务由一个单独的 + Observe that the problem of computing square roots breaks up naturally + into a number of subproblems: + programstructure of + how to tell whether a guess is good + enough, how to improve a guess, and so on. Each of these tasks is + accomplished by a separate - 程序。 - 函数。 + procedure. + function. - 整个平方根程序可以看作是一组 + The entire sqrt program can be viewed as a + cluster of - - 程序 - (如图所示) - + + procedures + (shown in figure) + - 函数 - (如图所示) + functions + (shown in figure) - 这种程序集反映问题的分解为子问题。 + that mirrors the decomposition of the problem into subproblems. - -
+ +
- 程序的过程分解 - 平方根程序。 + Procedural decomposition of the + sqrt program.
- +
-
+
- 程序的函数分解 - sqrt程序。 + Functional decomposition of the + sqrt program.
The importance of this - 程序分解成部分 - - 程序 - + decomposition of program into parts + decomposition strategy is not simply that one + is dividing the program into parts. After all, we could take any + large program and divide it into partsthe first ten lines, the next + ten lines, the next ten lines, and so on. Rather, it is crucial that + each + + + procedure + - 函数 + function - - 程序。 - 函数。 - - - 程序作为黑箱 - + + accomplishes an identifiable task that can be used as a module in defining + other + + procedures. + functions. + + + + procedureblackas black box + - 作为黑箱 + blackas black box - - - 够好吗?程序 - + + For example, when we define the + + + good-enough? procedure + - is_good_enough函数 + is_good_enough function - square, we are able to - regard the square - - 程序 - + + in terms of square, we are able to + regard the square + + + procedure + - 函数 + function - 黑箱黑箱。如何 - - 程序 - + + as a + black box + black box. We are not at that moment concerned with + how the + + + procedure + - 函数 + function - 确实 - - 够好吗?程序 - + + computes its result, only with the fact that it computes the + square. The details of how the square is computed can be suppressed, + to be considered at a later time. Indeed, as far as the + + + good-enough? procedure + - is_good_enough函数 + is_good_enough function - square is not quite a + + is concerned, square is not quite a - - 程序 - + + procedure + - 函数 + function - - 程序, - 函数, - - - 程序抽象 - 抽象过程 - 过程抽象. - + + but rather an abstraction of a + + procedure, + function, + + a so-called + + + procedural abstraction + abstractionprocedural + procedural abstraction. + - 功能抽象 - 抽象功能 - 功能抽象. + functional abstraction + abstractionfunctional + functional abstraction. - - - 程序 - + + At this level of abstraction, any + + + procedure + - 函数 + function - + + that computes the square is equally good. + - 因此,仅考虑它们返回的值,以下两个 + Thus, considering only the values they return, the following two - - 程序 - + + procedures + - 函数 + functions - 对一个数进行平方计算应该是无法区分的。每个都接受一个数值参数并产生该数的平方作为值。甚至不清楚这些 + squaring a number should be indistinguishable. Each takes a numerical + argument and produces the square of that number as the value.It + is not even clear which of these - - 程序 - + + procedures + - 函数 + functions - 中哪个是更高效的实现。这取决于可用的硬件。有些机器对于明显的 - 实现反而效率较低。考虑一台机器,存储了大量以非常高效方式存储的对数和反对数表。 + is a more efficient implementation. This depends upon the hardware + available. There are machines for which the obvious + implementation is the less efficient one. Consider a machine that has + extensive tables of logarithms and antilogarithms stored in a very + efficient manner. square_example - + (define (square x) (* x x)) - + function square(x) { return x * x; @@ -206,12 +259,12 @@ function square(x) { square_example - + (define (square x) (exp (double (log x)))) (define (double x) (+ x x)) - + function square(x) { return math_exp(double(math_log(x))); @@ -223,78 +276,95 @@ function double(x) { - 因此,一个 + So a - - 程序 - + + procedure + - 函数 + function - 应能够隐藏细节。 + should be able to suppress detail. The users of the - - 程序 - + + procedure + - 函数 + function - 的用户可能并未编写此 + may not have written the - - 程序 - + + procedure + - 函数 + function - ,而是从另一位程序员那里获得的一个黑箱。用户不需要知道该 + themselves, but may have obtained it from another programmer as a + black box. A user should not need to know how the - - 程序 - + + procedure + - 函数 + function - 是如何实现的,才能使用它。 + is implemented in order to use it. - - 程序作为黑箱 - + + procedureblackas black box + - 作为黑箱 + blackas black box - 局部名称 + Local names - 局部名称 + local name One detail of a - 程序s - 函数s - - 程序 - 函数 - - 程序s形式参数的名称。 - 函数s参数的名称。 - - 程序 - 函数 - + procedures + functions + + implementation that should not matter to the user of the + + + procedure + + + function + + + is the implementers choice of names for the + + procedures formal parameters. + functions parameters. + + Thus, the following + + + procedures + + + functions + + + should not be distinguishable: + square_example - + (define (square x) (* x x)) - + function square(x) { return x * x; @@ -303,48 +373,73 @@ function square(x) { square_example - + (define (square y) (* y y)) - + function square(y) { return y * y; } - This principle - 程序 - 函数 - - 程序 - 函数 - - 程序 - 函数 - square - - 在定义 - 够好吗? - - - 在声明 + This principlethat the meaning of a + + + procedure + + + function + + + should be independent of the parameter names used by its + authorseems on the surface to be self-evident, but its + consequences are profound. The simplest consequence is that the + parameter names of a + + + procedure + + + function + + + must be local to the body of the + + + procedure. + + + function. + + + For example, we used square + + + in the definition of + good-enough? + + + in the declaration of is_good_enough - - - 程序: - + + in our square-root + + + procedure: + - 函数: + function: - + + is_good_enough_example abs_definition square_definition - + (define (good-enough? guess x) (< (abs (- (square guess) x)) 0.001)) - + function is_good_enough(guess, x) { return abs(square(guess) - x) < 0.001; @@ -353,345 +448,421 @@ function is_good_enough(guess, x) { The intention of the author of - - 够好吗? - + + good-enough? + is_good_enough - - - 够好吗? - + + is to determine if the square of the first argument is within a given + tolerance of the second argument. We see that the author of + + + good-enough? + is_good_enough - guess to refer to the - first argument and x to refer to the - second argument. The argument of square - is guess. If the author of - square used x + + used the name guess to refer to the + first argument and x to refer to the + second argument. The argument of square + is guess. If the author of + square used x (as above) to refer to that argument, we see that the - x in + x in - - 够好吗? - + + good-enough? + is_@good_@enough - x than the one - in square. Running the + + must be a different x than the one + in square. Running the - - 程序 - + + procedure + - 函数 + function - square must not affect the value - of x that is used by + + square must not affect the value + of x that is used by - - 够好吗?, - + + good-enough?, + is_good_enough, - x may be needed by + + because that value of x may be needed by - - 够好吗? - + + good-enough? + is_good_enough - square is done computing. + + after square is done computing. - 如果参数不是各自 + If the parameters were not local to the bodies of their respective - 程序 - 函数 + procedures, + functions, - 主体的局部变量,那么在平方中的参数x可能会与在 + then the parameter x in + square could be confused with the parameter + x in - - 够好吗? - + + good-enough?, + - is_@good_@enough + is_@good_@enough, - 中的参数x混淆,并且 + and the behavior of - - 够好吗? - + + good-enough? + is_good_enough - 的行为将取决于我们使用的是哪个版本的平方。因此,平方就不会是我们所期望的黑箱。 + would depend upon which version of square + we used. Thus, square would not be the + black box we desired. A - 参数名称名称参数参数的名称 - - 程序的形式参数 - - - 函数的参数 - - - - 程序定义中具有很特殊的角色, - - - 函数声明中具有很特殊的角色, - - - - 形式 - - - - - - 绑定变量 - 变量绑定 - 一个绑定变量,我们称这个程序定义为 - - - 绑定名称 - 名称绑定 - 绑定,我们称这个函数声明为 - - 绑定绑定 - - 形式参数。 - - - 参数。 - - - - 程序定义的意义不会改变,如果一个绑定变量 - - - 函数声明的意义不会改变,如果一个绑定名称 - - 定义声明一致重新命名的概念实际上是微妙且难以形式化定义的。有名的逻辑学家在这里犯过尴尬的错误。 - 变量 - 名称 - - - 自由变量 - 变量自由 - - - 自由名称 - 名称自由 - - 自由的 - 定义 - 声明 - - - 变量的作用域 - 变量作用域 - - - 名称的作用域 - 名称作用域 - - 作用域 - - 程序定义中,绑定变量 - - - 函数声明中,绑定名称 - - - - 形式参数的作用域 - 程序形式参数的作用域 - 变量的作用域程序的形式参数 - - - 参数的作用域 - 参数的作用域 - 名称的作用域函数的参数 - - - - 程序的形式参数 - - - 函数的参数 - - - - 程序 - - - 函数 - - + parametersnames of + nameparameterof a parameter + + + formal parameter of a procedure + + + parameter of a function + + + has a very special role in the + + + procedure definition, + + + function declaration, + + + in that it doesnt matter what name the + + + formal + + + + + parameter has. Such a name is called + + + bound variable + variablebound + a bound variable, and we say that the procedure definition + + + bound name + namebound + bound, and we say that the function declaration + + + bind + binds its + + + formal parameters. + + + parameters. + + + The meaning of a + + + procedure definition is unchanged if a bound variable + + + function declaration is unchanged if a bound name + + + is consistently renamed throughout the + definitiondeclaration.The + concept of consistent renaming is actually subtle and difficult to + define formally. Famous logicians have made embarrassing errors + here. + If a + + variable + name + + is not bound, we say that it is + + + free variable + variablefree + + + free name + namefree + + + free. The set of + + expressions + statements + + for which a binding + + defines + declares + + a name is called the + + + scope of a variable + variablescope of + + + scope of a name + namescope of + + + scope of that name. In a + + + procedure definition, the bound variables + + + function declaration, the bound names + + + declared as the + + + formal parametersscope of + procedurescope of formal parameters + scope of a variableprocedures formal parameters + + + parametersscope of + scope of parameters + scope of a namefunctions parameters + + + + + formal parameters of the procedure + + + parameters of the function + + + have the body of the + + + procedure + + + function + + + as their scope. + In the - - 够好吗?的定义 - + + definition of good-enough? + - is_good_enough的声明 + declaration of is_good_enough - guess and - x are + + above, + guess and + x are bound - - 变量 - + + variables + - 名称 + names - - - <, - -, - - abs - and square are free. + + but + + + <, + -, + + + abs + and square are free. The meaning of - - 够好吗? - + + good-enough? + is_good_enough - guess and - x so long as they are distinct and + + should be independent of the names we choose for + guess and + x so long as they are distinct and different from - - <, - -, - - abs - and square. (If we renamed - guess to - abs we would have introduced a bug by - - - 捕获一个自由变量 - 错误捕获一个自由变量 - 自由变量捕获 - - - 捕获一个自由名称 - 错误捕获一个自由名称 - 自由名称捕获 - - 捕获 - - 变量 - - - 名称 - - abs. + + <, + -, + + + abs + and square. (If we renamed + guess to + abs we would have introduced a bug by + + + capturing a free variable + bugcapturing a free variable + free variablecapturing + + + capturing a free name + bugcapturing a free name + free namecapturing + + + capturing the + + + variable + + + name + + + abs. It would have changed from free to bound.) The meaning of - - 够好吗? - + + good-enough? + is_good_enough - - - 其自由变量的名称无关, - + + is not independent of the + + + names of its free variables, + - 其自由名称的选择无关, + choice of its free names, - - - (定义外部) - + + however. It surely depends upon the fact + + + (external to this definition) + - (声明外部) + (external to this declaration) - - - 符号abs命名一个计算绝对值的程序 - + + + + that the symbol abs names a procedure + - 名称abs指的是一个用于计算绝对值的函数 + that the name abs refers to a function - - - 够好吗? - + + for computing the absolute value of a number. + + + Good-enough? + - 函数is_good_enough + The function is_good_enough - - - cos - + + will compute a different function if we substitute + + + cos + math_cos - (原始的余弦函数) + (the primitive cosine function) - abs in its + + for abs in its - - 定义。 - + + definition. + - 声明。 + declaration. - 局部名称 + + local name + - 内部 + Internal - 定义 - 声明 + definitions + declarations - 和块结构 + and block structure - + - + - 到目前为止,我们有一种名称隔离的方法: + We have one kind of name isolation available to us so far: - - 程序的形式参数 - + + The formal parameters of a procedure + - 函数的参数 + The parameters of a function - 是 + are local to the body of the - 程序 - 函数 + procedure. + function. - 主体的局部变量。平方根程序展示了我们希望控制名称使用的另一种方式。 - 程序结构 - 现有的程序由几个独立的 + The square-root program illustrates another way in which we would like to + control the use of names. + programstructure of + The existing program consists of separate - - 程序: - + + procedures: + - 函数: + functions: @@ -700,7 +871,7 @@ function is_good_enough(guess, x) { abs_definition square_definition average_definition - + (define (sqrt x) (sqrt-iter 1.0 x)) @@ -714,7 +885,7 @@ function is_good_enough(guess, x) { (define (improve guess x) (average guess (/ x guess))) - + function sqrt(x) { return sqrt_iter(1, x); @@ -736,106 +907,131 @@ function improve(guess, x) { The problem with this program is that the only - - 程序 - + + procedure + - 函数 + function - sqrt is - sqrt. The other + + that is important to users of sqrt is + sqrt. The other - - 程序 - + + procedures + - 函数 + functions - - - (sqrt-iter, - 够好吗?, - + + + + (sqrt-iter, + good-enough?, + (sqrt_iter, is_good_enough, - improve) only clutter up their minds. + + and improve) only clutter up their minds. They may not - - 定义任何其他程序 - + + define any other procedure + - 声明任何其他函数 + declare any other function - - - 够好吗? - + + called + + + good-enough? + is_good_enough - sqrt + + as part of another program to work together + with the square-root program, because sqrt needs it. The problem is especially severe in the construction of large systems by many separate programmers. For example, in the construction of a large library of numerical - 程序, - 函数, - - - 程序 - + procedures, + functions, + + many numerical functions are computed as successive approximations and + thus might have + + + procedures + - 函数 + functions - - - 够好吗? - + + named + + + good-enough? + is_good_enough - improve as auxiliary + + and improve as auxiliary + + procedures. + functions. + + We would like to localize the - 程序。 - 函数。 - - 子程序, - 子函数, - sqrt so that - sqrt could coexist with other + subprocedures, + subfunctions, + + hiding them inside sqrt so that + sqrt could coexist with other successive approximations, each having its own private - 够好吗?程序。 - - is_good_enough函数。 + good-enough? procedure. + + is_good_enough function. - - - 程序 - + + To make this possible, we allow a + + + procedure + - 函数 + function - 块结构 - - 内部定义 - + + to have + block structure + + + internal definition + - 内部声明 + internal declaration - - 程序 - 函数 - + + internal declarations that are local to that + + procedure. + function. + + For example, in the square-root problem we can write + sqrt_example_2 2.2360688956433634 abs_definition square_definition average_definition - + (define (sqrt x) (define (good-enough? guess x) (< (abs (- (square guess) x)) 0.001)) @@ -846,7 +1042,7 @@ function improve(guess, x) { guess (sqrt-iter (improve guess x) x))) (sqrt-iter 1.0 x)) - + function sqrt(x) { function is_good_enough(guess, x) { @@ -865,105 +1061,130 @@ function sqrt(x) { - - + + + - 任何匹配的一对花括号表示一个,并且块内的声明是块的局部变量。 - - 句法形式 + Any matching pair of braces designates a block, and + declarations inside the block are local to the block. + block + syntactic formsblock - 块结构 - 辅助程序的定义内化 - 辅助函数的声明内化 - x is bound in the + + Such nesting of + + definitions, + declarations, + + called block structure, is basically the right solution to the + simplest name-packaging problem. But there is a better idea lurking here. + In addition to internalizing the + + definitions of the auxiliary procedures, + declarations of the auxiliary functions, + + we can simplify them. Since x is bound in the - 定义 - 声明 - sqrt, the + definition + declaration + + of sqrt, the - - 程序 - + + procedures + - 函数 + functions - - 够好吗?, + + + good-enough?, is_good_enough, - improve, and + + improve, and - sqrt-iter, - 定义在内部的 + sqrt-iter, + which are defined internally to sqrt_iter, - 声明在内部的 - sqrt, are in the scope of - x. Thus, it is not necessary to pass - x explicitly to each of these - - 程序。 - 函数。 - x to be a free - - - 内部定义的自由变量 - 自由变量内部在内部定义中 - - - 内部声明的自由名称 - 自由名称内部在内部声明中 - - - 变量 - 名称 - - 定义中 - 声明中 - x gets its value from + which are declared internally to + + sqrt, are in the scope of + x. Thus, it is not necessary to pass + x explicitly to each of these + + procedures. + functions. + + Instead, we allow x to be a free + + + internal definitionfree variable in + free variableinternalin internal definition + + + internal declarationfree name in + free nameinternalin internal declaration + + + + variable + name + + in the internal + + definitions, + declarations, + + as shown below. Then x gets its value from the argument with which the enclosing - - 程序 - + + procedure + - 函数 + function - sqrt is called. This discipline is called - 词法作用域词法作用域词法作用域决定了一个 + + sqrt is called. This discipline is called + lexical scoping + lexical scoping.Lexical scoping dictates that free - - 程序中的自由变量 - + + variables in a procedure + - 函数中的自由名称 + names in a function - 被认为是指封闭的 + are taken to refer to bindings made by enclosing - - 程序定义所做的绑定; - + + procedure definitions; + - 函数声明所做的绑定; + function declarations; - 即,它们在环境 - 环境词法作用域与 - 中被查找,在 + that is, they are looked up in + environmentlexical scoping and + the environment in which the - - 程序被定义的环境中。 - + + procedure was defined. + - 函数被声明的环境中。 + function was declared. - 当我们研究环境和解释器的详细行为时,我们将在中详细了解这种机制如何工作。 + We will see how this works in detail in chapter when we + study environments and the detailed behavior of the interpreter. + sqrtblock structured sqrt_example_2 abs_definition square_definition average_definition - + (define (sqrt x) (define (good-enough? guess) (< (abs (- (square guess) x)) 0.001)) @@ -974,7 +1195,7 @@ function sqrt(x) { guess (sqrt-iter (improve guess)))) (sqrt-iter 1.0)) - + function sqrt(x) { function is_good_enough(guess) { @@ -994,44 +1215,48 @@ function sqrt(x) { - 我们将广泛使用块结构来帮助我们将大型程序分解成可处理的部分。嵌入的 + We will use block structure extensively to help us break up large programs + into tractable pieces.Embedded - - 定义必须在程序的主体系中首先出现 - + + definitions must come first in a procedure + - 声明必须在函数的主体系中首先出现 + declarations must come first in a function - 。 + + body. - - 内部定义位置 - + + internal definitionposition of + - 内部声明位置 + internal declarationposition of - 管理层不对运行相互交织的 + The management is not responsible for the consequences of running programs + that intertwine - 定义 - 声明 + definition + declaration - 和使用的程序造成的后果负责;另请参见 - 注释 - 和 - 在一节中。 + and use; see also + footnotes + and + in section. - 块结构的概念起源于编程语言 - Algol块结构 - Algol60。它出现在大多数高级编程语言中,是帮助组织大型程序构建的重要工具。 - 程序结构 - 块结构 + The idea of block structure originated with the programming language + Algolblock structure + Algol60. It appears in most advanced programming languages and is an + important tool for helping to organize the construction of large programs. + programstructure of + block structure - - 内部定义 - + + internal definition + - 内部声明 + internal declaration diff --git a/xml/cn/chapter1/section2/section2.xml b/xml/cn/chapter1/section2/section2.xml new file mode 100644 index 000000000..2cf709b47 --- /dev/null +++ b/xml/cn/chapter1/section2/section2.xml @@ -0,0 +1,110 @@ +
+ + + 程序 + 函数 + + 及其生成的过程 + + + + + + + + 我们现在已经考虑了编程的元素:我们使用了 + 原始算术操作,组合了这些操作,并且 + 通过 + + 将其定义为复合程序。 + 将其声明为复合函数。 + + 但这还不足以让我们说我们知道如何编程。我们的情况类似于一个已经 + 学会了国际象棋棋子如何移动规则的人,但对典型开局、战术或策略一无所知。像初学者的棋手一样, + 我们尚不知道该领域的常见使用模式。 + 我们缺乏对哪些移动是值得做的知识 + + (哪些程序值得定义) + (哪些函数值得声明) + + 我们缺乏预测移动后果的经验 + + (执行一个程序)。 + (执行一个函数)。 + + + + + 视觉化所考虑的行动后果的能力对于成为一名专家程序员至关重要,正如在任何合成的创造性活动中一样。例如,成为一名专家摄影师,必须学会如何观察一个场景,并知道每个可能的曝光选择下,图像中每个区域在打印时将显得多么黑暗,以及 + + 显影。 + 处理选项。 + + + 术语显影可能对数字时代的主流受众并不熟悉。 + + 只有这样,人才能向后推理,规划构图、照明、曝光和 + + 显影 + 处理 + + 以获得所需的效果。因此编程也是如此,我们在规划一个过程的行动方案,并通过程序控制该过程。要成为专家,我们必须学会可视化各种类型生成的 + + 程序。 + 函数。 + + 只有在我们培养出这样的技能之后,才能可靠地构造出表现出所需行为的程序。 + + + + 一个 + + 程序 + 函数 + + 是一个 + 模式作为过程的局部演化模式 + 用于计算过程的局部演化的模式。它指定了过程的每个阶段如何建立在上一个阶段之上。我们希望能够对局部 + 过程的局部演化 + 过程局部演化 + 已由 + + 程序 + 函数 + + 指定的过程的整体或全局行为做出陈述。一般来说,这很难做到,但我们至少可以尝试描述一些典型的过程演化模式。 + + + + 在本节中,我们将检查一些由简单 + + 程序 + 函数 + + 生成的常见形状。我们还将研究这些过程消耗时间和空间这两种重要计算资源的速率。我们将考虑的 + + 程序 + 函数 + + 非常简单。它们的作用类似于摄影中的测试模式:作为过于简化的原型模式,而不是实际的例子。 + + + + &subsection1.2.1; + + + &subsection1.2.2; + + + &subsection1.2.3; + + + &subsection1.2.4; + + + &subsection1.2.5; + + + &subsection1.2.6; + +
diff --git a/xml/cn/chapter1/section2/subsection1.xml b/xml/cn/chapter1/section2/subsection1.xml new file mode 100644 index 000000000..34ad06554 --- /dev/null +++ b/xml/cn/chapter1/section2/subsection1.xml @@ -0,0 +1,820 @@ + + Linear Recursion and Iteration + + + iterative processrecursive process vs. + recursive processiterative process vs. + + + We begin by considering the + factorial + factorial function, defined by + + \[ + \begin{array}{lll} + n! &=& n\cdot(n-1)\cdot(n-2)\cdots3\cdot2\cdot1 + \end{array} + \] + + There are many ways to compute factorials. One way is to make use of + the observation that $n!$ is equal to + $n$ times $(n-1)!$ for + any positive integer$n$: + + \[ + \begin{array}{lll} + n! &=& n\cdot\left[(n-1)\cdot(n-2)\cdots3\cdot2\cdot1\right] \quad = \quad n \cdot(n-1)! + \end{array} + \] + + Thus, we can compute $n!$ by computing + $(n-1)!$ and multiplying the + result by $n$. If we add the stipulation that 1! + is equal to 1, + this observation translates directly into a + + procedure: + computer function: + + + factoriallinear recursive version + factorial_definition + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* n (factorial (- n 1))))) + + +function factorial(n) { + return n === 1 + ? 1 + : n * factorial(n - 1); +} + + + + + factorial_example + +(factorial 5) + + +factorial(5); + + + + + + We can use the substitution model of + section to watch this + substitution model of procedure applicationshape of process + procedure in action computing 6!, as shown in + figure. +
+ + A linear recursive process for computing 6!. + +
+ + + We can use the substitution model of + section to watch this + substitution model of function applicationshape of process + function in action computing 6!, as shown in + figure. + + + +
+
+ + A linear recursive process for computing 6!. + +
+
+
+
+
+ + + Now lets take a different perspective on computing factorials. We + could describe a rule for computing $n!$ by + specifying that we first multiply 1 by 2, then multiply the result by 3, + then by 4, and so on until we reach $n$. + More formally, we maintain a running product, together with a counter + that counts from 1 up to $n$. We can describe + the computation by saying that the counter and the product simultaneously + change from one step to the next according to the rule + + \[ + \begin{array}{lll} + \textrm{product} & \leftarrow & \textrm{counter} \cdot \textrm{product}\\ + \textrm{counter} & \leftarrow & \textrm{counter} + 1 + \end{array} + \] + +and stipulating that $n!$ is the value of the + product when the counter exceeds $n$. + + + + Once again, we can recast our description as a + + procedure + function + + for computing + factorials:In a real program we would probably use the + block structure introduced in the last section to hide the + + + definition of fact-iter: + + + declaration of fact_iter: + + + + + factorial_example + 120 + +(define (factorial n) + (define (iter product counter) + (if (> counter n) + product + (iter (* counter product) + (+ counter 1)))) + (iter 1 1)) + + +function factorial(n) { + function iter(product, counter) { + return counter > n + ? product + : iter(counter * product, + counter + 1); + } + return iter(1, 1); +} + + + We avoided doing this here so as to minimize the number of things to + think about at + once. + + factoriallinear iterative version + factorial_iterative_definition + factorial_example + +(define (factorial n) + (fact-iter 1 1 n)) + +(define (fact-iter product counter max-count) + (if (> counter max-count) + product + (fact-iter (* counter product) + (+ counter 1) + max-count))) + + +function factorial(n) { + return fact_iter(1, 1, n); +} +function fact_iter(product, counter, max_count) { + return counter > max_count + ? product + : fact_iter(counter * product, + counter + 1, + max_count); +} + + + As before, we can use the substitution model to visualize the process + + +
+ + A linear iterative process for computing + $6!$. + +
+ of computing $6!$, as shown in + figure. + + + + + +
+
+ + A linear recursive process for computing 6!. + +
+
+
+
+ + A linear iterative process for computing + $6!$. + +
+ of computing $6!$, as shown in + figure. +
+
+
+ + + Compare the two processes. From one point of view, they seem hardly + different at all. Both compute the same mathematical function on the + same domain, and each requires a number of steps proportional to + $n$ + to compute $n!$. Indeed, both processes even + carry out the same sequence of multiplications, obtaining the same sequence + of partial products. On the other hand, when we consider the + shape of a process + processshape of + shapes of the two processes, we find that they evolve quite + differently. + + + + Consider the first process. The substitution model reveals a shape of + expansion followed by contraction, indicated by the arrow in + figure. + The expansion occurs as the process builds up a chain of + deferred operations + deferred operations (in this case, a chain of multiplications). + The contraction occurs as the operations are actually performed. This + type of process, characterized by a chain of deferred operations, is called a + recursive process + processrecursive + recursive process. Carrying out this process requires that the + interpreter keep track of the operations to be performed later on. In the + computation of $n!$, the length of the chain of + deferred multiplications, and hence the amount of information needed to + keep track of it, + linear growth + grows linearly with $n$ (is proportional to + $n$), just like the number of steps. + Such a process is called a + recursive processlinear + linear recursive process + processlinear recursive + linear recursive process. + + + + By contrast, the second process does not grow and shrink. At each + step, all we need to keep track of, for any $n$, + are the current values of the + + variables + names + + product, counter, + and + + max-count. + max_count. + + We call this an + iterative process + processiterative + iterative process. In general, an iterative process is one whose + state can be summarized by a fixed number of + state variable + state variables, together with a fixed rule that describes how + the state variables should be updated as the process moves from state to + state and an (optional) end test that specifies conditions under which the + process should terminate. In computing $n!$, the + number of steps required grows linearly with $n$. + Such a process is called a + iterative processlinear + linear iterative process + processlinear iterative + linear iterative process. + + + + + The contrast between the two processes can be seen in another way. + In the iterative case, the state variables provide a complete description of + the state of the process at any point. If we stopped the computation between + steps, all we would need to do to resume the computation is to supply the + interpreter with the values of the three state variables. Not so with the + recursive process. In this case there is some additional + hidden information, maintained by the interpreter and not + contained in the state variables, which indicates where the process + is in negotiating the chain of deferred operations. The longer the + chain, the more information must be maintained.When we discuss the + implementation of + + procedures + functions + + on register machines in chapter, we will see that any iterative + process can be realized in hardware as a machine that has a + fixed set of registers and no auxiliary memory. In contrast, realizing a + recursive process requires a machine that uses an + auxiliary data structure known as a + stack + stack. + substitution model of procedurefunction applicationshape of process + + + + In contrasting iteration and recursion, we must be careful not to + confuse the notion of a + recursive procedurefunctionrecursive process vs. + recursive processrecursive procedurefunction vs. + recursive process with the notion of a recursive + + + procedure. + + + function. + + + When we describe a + + procedure + function + + as recursive, we are referring to the syntactic fact that the + + procedure definition + function declaration + + refers (either directly or indirectly) to the + + procedure + function + + itself. But when we describe a process as following a pattern that is, say, + linearly recursive, we are speaking about how the process evolves, not + about the syntax of how a + + procedure + function + + is written. It may seem disturbing that we refer to a recursive + + procedure + function + + such as + + fact-iter + fact_iter + + as generating an iterative process. However, the process really is + iterative: Its state is captured completely by its three state variables, + and an interpreter need keep track of only three + + variables + names + + in order to execute the process. + + + + One reason that the distinction between process and + + procedure + function + + may be confusing is that most implementations of common languages + + + (including + Adarecursive procedures + Pascalrecursive procedures + Crecursive procedures + Ada, Pascal, and C) + + + (including + Crecursive functions in + C, + Java, recursive functions in + Java, and + Python, recursive functions in + Python) + + + are designed in such a way that the interpretation of + any recursive + + procedure + function + + consumes an amount of memory that grows with the number of + + procedure + function + + calls, even when the process described is, in principle, iterative. + As a consequence, these languages can describe iterative processes only + by resorting to special-purpose + looping constructs + looping constructs such as + $\texttt{do}$, + $\texttt{repeat}$, + $\texttt{until}$, + $\texttt{for}$, and + $\texttt{while}$. + The implementation of + + Scheme + JavaScript + + we shall consider in chapter does not share this defect. It will + execute an iterative process in constant space, even if the iterative + process is described by a recursive + + procedure. + function. + + + + An implementation with this property is called + tail recursion + tail-recursive. With a tail-recursive implementation, + iteration can be expressed using the ordinary procedure + iterative processimplemented by procedure call + call mechanism, so that special iteration constructs are useful only as + syntactic sugarlooping constructs as + syntactic sugar.Tail recursion has long been + known as a compiler optimization trick. A coherent semantic basis for + tail recursion was provided by + Hewitt, Carl Eddie + Carl Hewitt (1977), who explained it in + message passingtail recursion and + terms of the message-passing model of computation that we + shall discuss in chapter. Inspired by this, Gerald Jay Sussman + and + Steele, Guy Lewis Jr. + Guy Lewis Steele Jr.(see Steele 1975) + constructed a tail-recursive interpreter for Scheme. Steele later showed + how tail recursion is a consequence of the natural way to compile + procedure + Sussman, Gerald Jay + calls (Steele 1977). + The IEEE standard for Scheme requires that Scheme implementations + tail recursionSchemein Scheme + be tail-recursive. + + + An implementation with this property is called + tail recursion + tail-recursive.Tail recursion has long been + known as a compiler optimization trick. A coherent semantic basis for + tail recursion was provided by + Hewitt, Carl Eddie + Carl Hewitt (1977), who explained it in + message passingtail recursion and + terms of the message-passing model of computation that we + shall discuss in chapter. Inspired by this, Gerald Jay Sussman + and + Steele, Guy Lewis Jr. + Guy Lewis Steele Jr.(see Steele 1975) + constructed a tail-recursive interpreter for Scheme. Steele later showed + how tail recursion is a consequence of the natural way to compile + function calls + Sussman, Gerald Jay + (Steele 1977). + The IEEE standard for Scheme requires that Scheme implementations + tail recursionSchemein Scheme + tail recursionJavaScriptin JavaScript + Schemetail recursion in + JavaScripttail recursion in + be tail-recursive. The ECMA standard for JavaScript eventually followed + suit with ECMAScript 2015 (ECMA 2015). Note, however, + that as of this writing (2021), most implementations of JavaScript do + not comply with this standard with respect to tail recursion. + With a tail-recursive implementation, + iterative processimplemented by function call + iteration can be expressed using the ordinary function + call mechanism, so that special iteration constructs are useful only as + syntactic sugarlooping constructs as + syntactic sugar.Exercise + explores JavaScript's while loops as syntactic + sugar for functions that give rise to iterative processes. + The full language JavaScript, like other conventional languages, + features a plethora of syntactic + forms, all of which can be expressed more uniformly in the + language Lisp. + This, together with the fact that these constructs typically involve + semicolons whose placement rules are sometimes not obvious, + led Alan Perlis to quip: Syntactic sugar causes + cancer of the semicolon. + syntactic sugar + Perlis, Alan J.quips by + semicolon (;)cancer of + + + + iterative processrecursive process vs. + recursive processiterative process vs. + + + + + Each of the following two + + procedures + functions + + defines a method for adding two positive integers in terms of the + + procedures + functions + + inc, which increments its argument by 1, + and dec, which decrements its argument by 1. + + + inc_dec_definition + +(define (inc x) + (- x -1)) +(define (dec x) + (- x 1)) + + +function inc(x) { + return x + 1; +} +function dec(x) { + return x - 1; +} + + + + + plus_example + +(+ 4 5) + + +plus(4, 5); + + + + + inc_dec_definition + 9 + plus_example + +(define (+ a b) + (if (= a 0) + b + (inc (+ (dec a) b)))) + + +function plus(a, b) { + return a === 0 ? b : inc(plus(dec(a), b)); +} + + + + + inc_dec_definition + 9 + plus_example + +(define (+ a b) + (if (= a 0) + b + (+ (dec a) (inc b)))) + + +function plus(a, b) { + return a === 0 ? b : plus(dec(a), inc(b)); +} + + + + Using the substitution model, illustrate the process generated by each + + procedure + function + + in evaluating + + (+ 4 5). + plus(4, 5);. + + Are these processes iterative or recursive? + + + + + The process generated by the first function is recursive. + + +plus(4, 5) +4 === 0 ? 5 : inc(plus(dec(4), 5)) +inc(plus(dec(4), 5)) +... +inc(plus(3, 5)) +... +inc(inc(plus(2, 5))) +... +inc(inc(inc(plus(1, 5)))) +... +inc(inc(inc(inc(plus(0, 5))))) +inc(inc(inc(inc( 0 === 0 ? 5 : inc(plus(dec(0), 5)))))) +inc(inc(inc(inc( 5 )))) +inc(inc(inc( 6 ))) +inc(inc( 7 )) +inc( 8 ) +9 + + + The process generated by the second function is iterative. + + +plus(4, 5) +4 === 0 ? 5 : plus(dec(4), inc(5)) +plus(dec(4), inc(5)) +... +plus(3, 6) +... +plus(2, 7) +... +plus(1, 8) +... +plus(0, 9) +0 === 0 ? 9 : plus(dec(0), inc(9)) +9 + + + + + + + + + The following + + procedure + function + + computes a mathematical function called + Ackermanns function + function (mathematical)Ackermanns + Ackermanns function. + + + ackermann_definition + ackermann_example + +(define (A x y) + (cond ((= y 0) 0) + ((= x 0) (* 2 y)) + ((= y 1) 2) + (else (A (- x 1) + (A x (- y 1)))))) + + +function A(x, y) { + return y === 0 + ? 0 + : x === 0 + ? 2 * y + : y === 1 + ? 2 + : A(x - 1, A(x, y - 1)); +} + + + + What are the values of the following + + + expressions? + + + statements? + + + + ackermann_example + ackermann_definition + 1024 + +(A 1 10) + + +A(1, 10); + + + + + ackermann_definition + 65536 + +(A 2 4) + + +A(2, 4); + + + + + ackermann_definition + 65536 + +(A 3 3) + + +A(3, 3); + + + + Consider the following + + procedures, + functions, + + where A is the + + procedure defined + function declared + + above: + + fghk_definition + fghk_example + ackermann_definition + +(define (f n) (A 0 n)) + +(define (g n) (A 1 n)) + +(define (h n) (A 2 n)) + +(define (k n) (* 5 n n)) + + +function f(n) { + return A(0, n); +} +function g(n) { + return A(1, n); +} +function h(n) { + return A(2, n); +} +function k(n) { + return 5 * n * n; +} + + + + + fghk_example + 80 + fghk_definition + +(k 4) + + +k(4); + + + Give concise mathematical definitions for the functions computed by + the + + procedures + functions + + f, g, and + h for positive integer values of + $n$. For example, + $k(n)$ computes + $5n^2$. + + + The function $f(n)$ computes + $2 n$, + the function $g(n)$ computes + $2^n$, and + the function $h(n)$ computes + $2^{2^{\cdot^{\cdot^{\cdot^2}}}}$ + where the number of 2s in the chain of exponentiation is + $n$. + + + + + +
diff --git a/xml/cn/chapter1/section2/subsection2.xml b/xml/cn/chapter1/section2/subsection2.xml new file mode 100644 index 000000000..65d191ac1 --- /dev/null +++ b/xml/cn/chapter1/section2/subsection2.xml @@ -0,0 +1,777 @@ + + Tree Recursion + + + tree-recursive process + processtree-recursive + recursive processtree + + + Another common pattern of computation is called tree recursion. + As an example, consider computing the sequence of + Fibonacci numbers + Fibonacci numbers, + in which each number is the sum of the preceding two: + + \[\begin{array}{l} + 0, 1, 1, 2, 3, 5, 8, 13, 21, \ldots + \end{array}\] + + In general, the Fibonacci numbers can be defined by the rule + + \[\begin{array}{lll} + \textrm{Fib}(n) & = & \left\{ \begin{array}{ll} + 0 & \mbox{if $n=0$}\\ + 1 & \mbox{if $n=1$}\\ + \textrm{Fib}(n-1)+\textrm{Fib}(n-2) & \mbox{otherwise} + \end{array} + \right. + \end{array}\] + + We can immediately translate this definition into a recursive + + procedure + function + + for computing Fibonacci numbers: + + fibtree-recursive version + fib_definition + fib_example + +(define (fib n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else (+ (fib (- n 1)) + (fib (- n 2)))))) + + +function fib(n) { + return n === 0 + ? 0 + : n === 1 + ? 1 + : fib(n - 1) + fib(n - 2); +} + + + + + fib_example + fib_definition + 8 + +(fib 6) + + +fib(6); + + + + + +
+
+ + The tree-recursive process generated in computing + (fib 5). + +
+ + +
+
+ + The tree-recursive process generated in computing + fib(5). + +
+
+
+
+ + + Consider the pattern of this computation. To compute + + (fib 5), + fib(5), + + we compute + + (fib 4) + fib(4) + + and + + (fib 3). + fib(3). + + To compute + + (fib 4), + fib(4), + + we compute + + (fib 3) + fib(3) + + and + + (fib 2). + fib(2). + + In general, the evolved process looks like a tree, as shown in + + + figure. + + + figure. + + + Notice that the branches split into + two at each level (except at the bottom); this reflects the fact that the + fib + + procedure + function + + calls itself twice each time it is invoked. + + + + This + + procedure + function + + is instructive as a prototypical tree recursion, but it is a terrible way to + compute Fibonacci numbers because it does so much redundant computation. + Notice in + + + figure + + + figure + + + that the entire + computation of + + + (fib 3)almost + half the workis + + + fib(3)almost + half the workis + + + duplicated. In fact, it is not hard to show that the number of times the + + procedure + function + + will compute + + (fib 1) + fib(1) + + or + + + (fib 0) + + + fib(0) + + + (the number of leaves in the above tree, in general) is precisely + $\textrm{Fib}(n+1)$. To get an idea of how + bad this is, one can show that the value of + $\textrm{Fib}(n)$ + exponential growthtree-recursiveof tree-recursive Fibonacci-number computation + grows exponentially with $n$. More precisely + (see exercise), + $\textrm{Fib}(n)$ is the closest integer to + $\phi^{n} /\sqrt{5}$, where + + \[\begin{array}{lllll} + \phi&=&(1+\sqrt{5})/2 & \approx & 1.6180 + \end{array}\] + + is the + golden ratio + golden ratio, which satisfies the equation + + \[\begin{array}{lll} + \phi^{2} &=&\phi + 1 + \end{array}\] + + Thus, the process uses a number of steps that grows exponentially with the + input. On the other hand, the space required grows only linearly with the + input, because we need keep track only of which nodes are above us in the + tree at any point in the computation. In general, the number of steps + required by a tree-recursive process will be proportional to the number of + nodes in the tree, while the space required will be proportional to the + maximum depth of the tree. + + + + We can also formulate an iterative process for computing the Fibonacci + numbers. The idea is to use a pair of integers $a$ + and $b$, initialized to + $\textrm{Fib}(1)=1$ and + $\textrm{Fib}(0)=0$, and to repeatedly apply the + simultaneous transformations + + \[\begin{array}{lll} + a & \leftarrow & a+b \\ + b & \leftarrow & a + \end{array}\] + + It is not hard to show that, after applying this transformation + $n$ times, $a$ and + $b$ will be equal, respectively, to + $\textrm{Fib}(n+1)$ and + $\textrm{Fib}(n)$. Thus, we can compute + Fibonacci numbers iteratively using the + + procedure + function + + + fiblinear iterative version + fib_example + 8 + +(define (fib n) + (fib-iter 1 0 n)) + +(define (fib-iter a b count) + (if (= count 0) + b + (fib-iter (+ a b) a (- count 1)))) + + +function fib(n) { + return fib_iter(1, 0, n); +} +function fib_iter(a, b, count) { + return count === 0 + ? b + : fib_iter(a + b, a, count - 1); +} + + + This second method for computing $\textrm{Fib}(n)$ + is a linear iteration. The difference in number of steps required by the two + methodsone linear in $n$, one growing as + fast as $\textrm{Fib}(n)$ itselfis + enormous, even for small inputs. + + + + One should not conclude from this that tree-recursive processes are useless. + When we consider processes that operate on hierarchically structured data + rather than numbers, we will find that tree recursion is a natural and + powerful tool.An example of this was hinted at in + section: The interpreter + itself evaluates expressions using a tree-recursive process. But + even in numerical operations, tree-recursive processes can be useful in + helping us to understand and design programs. For instance, although the + first + fib + + procedure + function + + is much less efficient than the second one, it is more straightforward, + being little more than a translation into + + + Lisp + + + JavaScript + + + of the definition of the Fibonacci sequence. To formulate the iterative + algorithm required noticing that the computation could be recast as an + iteration with three state variables. + + + + Example: Counting change + + counting change + + + It takes only a bit of cleverness to come up with the iterative Fibonacci + algorithm. In contrast, consider the following problem: + How many different ways can we make change of + + + \$1.00, + + + 1.00 (100 cents), + + + given half-dollars, quarters, dimes, nickels, and pennies + (50 cents, 25 cents, 10 cents, 5 cents, and 1 cent, respectively)? + More generally, can + we write a + + procedure + function + + to compute the number of ways to change any given amount of money? + + + + This problem has a simple solution as a recursive + + procedure. + function. + + Suppose we think of the types of coins available as arranged in some order. + Then the following relation holds: +
+ The number of ways to change amount $a$ using + $n$ kinds of coins equals +
    +
  • + the number of ways to change amount $a$ + using all but the first kind of coin, plus +
  • +
  • + the number of ways to change amount $a-d$ + using all $n$ kinds of coins, where + $d$ is the denomination of the first kind + of coin. +
  • +
+
+
+ + + To see why this is true, observe that the ways to make change can be divided + into two groups: those that do not use any of the first kind of coin, and + those that do. Therefore, the total number of ways to make change for some + amount is equal to the number of ways to make change for the amount without + using any of the first kind of coin, plus the number of ways to make change + assuming that we do use the first kind of coin. But the latter number is + equal to the number of ways to make change for the amount that remains after + using a coin of the first kind. + + + + Thus, we can recursively reduce the problem of changing a given amount to + problems of changing smaller amounts or using fewer kinds of coins. Consider + this reduction rule carefully, and convince yourself that we can use it to + describe an algorithm if we specify the following degenerate + cases:For example, work through in detail how the reduction rule + applies to the problem of making change for 10 cents using pennies and + nickels. + +
    +
  • + If $a$ is exactly 0, we should count that + as 1 way to make change. +
  • +
  • + If $a$ is less than 0, we should count + that as 0 ways to make change. +
  • +
  • If $n$ is 0, we should count that + as 0 ways to make change. +
  • +
+ We can easily translate this description into a recursive + + procedure: + function: + + + + count_change + count_change_definition + count_change_example + +(define (count-change amount) + (cc amount 5)) + +(define (cc amount kinds-of-coins) + (cond ((= amount 0) 1) + ((or (< amount 0) + (= kinds-of-coins 0)) 0) + (else (+ (cc amount + (- kinds-of-coins 1)) + (cc (- amount + (first-denomination + kinds-of-coins)) + kinds-of-coins))))) + +(define (first-denomination kinds-of-coins) + (cond ((= kinds-of-coins 1) 1) + ((= kinds-of-coins 2) 5) + ((= kinds-of-coins 3) 10) + ((= kinds-of-coins 4) 25) + ((= kinds-of-coins 5) 50))) + + +function count_change(amount) { + return cc(amount, 5); +} + +function cc(amount, kinds_of_coins) { + return amount === 0 + ? 1 + : amount < 0 || kinds_of_coins === 0 + ? 0 + : cc(amount, kinds_of_coins - 1) + + + cc(amount - first_denomination(kinds_of_coins), + kinds_of_coins); +} + +function first_denomination(kinds_of_coins) { + return kinds_of_coins === 1 ? 1 + : kinds_of_coins === 2 ? 5 + : kinds_of_coins === 3 ? 10 + : kinds_of_coins === 4 ? 25 + : kinds_of_coins === 5 ? 50 + : 0; +} + + + (The + + + first-denomination procedure + + + first_denomination function + + + takes as input the number of kinds of coins available and returns the + denomination of the first kind. Here we are thinking of the coins as + arranged in order from largest to smallest, but any order would do as well.) + We can now answer our original question about changing a dollar: + + + count_change_example + count_change_definition + 292 + +(count-change 100) + + +292 + + +count_change(100); + + +292 + + +
+ + + + Count-change + + The function count_change + + + generates a tree-recursive process with redundancies similar to those in + our first implementation of fib. + + + (It will take + quite a while for that + + 292 + 293 + + to be computed.) + + + On the other hand, it is not + obvious how to design a better algorithm for computing the result, and we + leave this problem as a challenge. The observation that a + efficiencytreeof tree-recursive process + tree-recursive process may be highly inefficient but often easy to specify + and understand has led people to propose that one could get the best of both + worlds by designing a smart compiler that could transform + tree-recursive + + procedures + functions + + into more efficient + + procedures + functions + + that compute the same result.One approach to coping with redundant + computations is to arrange matters so that we automatically construct a + table of values as they are computed. Each time we are asked to apply the + + procedure + function + + to some argument, we first look to see if the value is already stored in the + table, in which case we avoid performing the redundant computation. This + strategy, known as + tabulation + tabulation or + memoization + memoization, can be implemented in a + straightforward way. Tabulation can sometimes be used to transform processes + that require an exponential number of steps + + + (such as count-change) + + + (such as count_change) + + + into processes whose space and time requirements grow linearly with the + input. See exercise. + tree-recursive process + processtree-recursive + recursive processtree + counting change + + + + A function $f$ is defined by the + + + rule that + + + rules + + + $f(n)=n$ if $n < 3$ + and $f(n)={f(n-1)}+2f(n-2)+3f(n-3)$ if + $n\ge 3$. Write a + + procedure + JavaScript function + + that computes $f$ by means of a recursive process. + Write a + + procedure + function + + that computes $f$ by means of an iterative + process. + + + example_1.12_1 + 25 + +// iterative function +function f_iterative(n) { + return n < 3 + ? n + : f_iterative_impl(2, 1, 0, n - 2); +} +function f_iterative_impl(a, b, c, count) { + return count === 0 + ? a + : f_iterative_impl(a + 2 * b + 3 * c, a, b, count - 1); +} + + + + + example_1.12_2 + 25 + +//recursive function +function f_recursive(n) { + return n < 3 + ? n + : f_recursive(n - 1) + + 2 * f_recursive(n - 2) + + 3 * f_recursive(n - 3); +} + + + + + example_1.12_1 + +f_iterative(5); + + + + example_1.12_2 + +f_recursive(5); + + + + + + + The following pattern of numbers is called + Pascals triangle + Pascals triangle. + + \[ + { + \begin{array}{rrrrcrrrr} + & & & & 1 & & & & \\ + & & &1 & &1 & & & \\ + & &1 & & 2 & &1 & & \\ + &1 & &3 & &3 & &1 & \\ + 1 & & 4 & & 6 & & 4 & & 1 \\ + & & & & \ldots & & & & + \end{array}} + \] + + The numbers at the edge of the triangle are all 1, and each number inside + the triangle is the sum of the two numbers above it.The elements + of Pascals triangle are called the binomial coefficients, + because the $n$th row consists of + binomial coefficients + the coefficients of the terms in the expansion of + $(x+y)^n$. This pattern for computing the + coefficients + appeared in + PascalPascal, Blaise + Blaise Pascals 1653 seminal work on probability theory, + Trait du triangle arithmtique. + According to + Edwards, Anthony William Fairbank + Edwards (2019), the same pattern appears + in the works of + the eleventh-century Persian mathematician + Al-Karaji + Al-Karaji, + in the works of the twelfth-century Hindu mathematician + Bhaskara + Bhaskara, and + in the works of the + thirteenth-century Chinese mathematician + Yang Hui + Yang Hui. + + Write a + + procedure + function + + that computes elements of Pascals triangle by means of a recursive + process. + + + pascal_triangle + example_1.13 + +function pascal_triangle(row, index) { + return index > row + ? false + : index === 1 || index===row + ? 1 + : pascal_triangle(row - 1, index - 1) + + + pascal_triangle(row - 1, index); +} + + + + + + + example_1.13 + pascal_triangle + 4 + +pascal_triangle(5, 4); + + + + + + + + + Prove that $\textrm{Fib}(n)$ is the closest + integer to $\phi^n/\sqrt{5}$, where + $\phi= (1+\sqrt{5})/2$. + + + Hint: Let + $\psi= (1-\sqrt{5})/2$. Use induction and the + definition of the Fibonacci numbers (see + section) to prove that + $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$. + + + Hint: Use induction and the + definition of the Fibonacci numbers to prove that + $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$, + where + $\psi= (1-\sqrt{5})/2$. + + + + + First, we show that + $\textrm{Fib}(n) = + \dfrac{\phi^n-\psi^n}{\sqrt{5}}$, + where + $\psi = \dfrac{1-\sqrt{5}}{2}$ + using strong induction. +

+ $\textrm{Fib}(0) = 0$ + and + $\dfrac{\phi^0-\psi^0}{\sqrt{5}} = 0$ +

+ $\textrm{Fib}(1) = 1$ + and + $\dfrac{\phi^1-\psi^1}{\sqrt{5}} = + \dfrac{\dfrac{1}{2}\left(1+\sqrt{5} - 1 + \sqrt{5}\right)}{\sqrt{5}} = 1$ + +

+ So the statement is true for $n=0,1$. + Given $n \geq 1$, assume the proposition + to be true for $0, 1, \dots , n$. +

+ $\textrm{Fib}(n+1) = + \textrm{Fib}(n) + \textrm{Fib}(n-1) = + \dfrac{\phi^n-\psi^n + \phi^{n-1} - \psi^{n-1}}{\sqrt{5}}$ +

+ $= \dfrac{\phi^{n-1}(\phi + 1) - + \psi^{n-1}(\psi + 1)}{\sqrt{5}}$ + +

+ $=\dfrac{\phi^{n-1}(\phi^2) - \psi^{n-1}(\psi^2)}{\sqrt{5}} + = \dfrac{\phi^{n+1} - \psi^{n+1}}{\sqrt{5}}$, + so the statement is true. +

+ Notice that since $|\psi| < 1$ and + $\sqrt{5} > 2$, one has + $\left|\dfrac{\psi^n}{\sqrt{5}}\right| < + \dfrac{1}{2}$ +

+ Then the integer closest to + $\textrm{Fib}(n) + \dfrac{\psi^n}{\sqrt{5}} = + \dfrac{\phi^n}{\sqrt{5}}$ is + $\textrm{Fib}(n)$. +
+
+ +
diff --git a/xml/cn/chapter1/section2/subsection3.xml b/xml/cn/chapter1/section2/subsection3.xml new file mode 100644 index 000000000..72c92c0d0 --- /dev/null +++ b/xml/cn/chapter1/section2/subsection3.xml @@ -0,0 +1,402 @@ + + + Orders of Growth + + + order of growth + + + The previous examples illustrate that processes can differ + considerably in the rates at which they consume computational + resources. One convenient way to describe this difference is to use + the notion of + processorder of growth of + order of growth to obtain a gross measure of the + processresources required by + resources required by a process as the inputs become larger. + + + + Let $n$ be a parameter that measures the size of + the problem, and let $R(n)$ be the amount + of resources the process requires for a problem of size + $n$. In our previous examples we took + $n$ to be the number for which a given + function is to be computed, but there are other possibilities. + For instance, if our goal is to compute an approximation to the + square root of a number, we might take + $n$ to be the number of digits accuracy required. + For matrix multiplication we might take $n$ to + be the number of rows in the matrices. In general there are a number of + properties of the problem with respect to which it will be desirable to + analyze a given process. Similarly, $R(n)$ + might measure the number of internal storage registers used, the + number of elementary machine operations performed, and so on. In + computers that do only a fixed number of operations at a time, the + time required will be proportional to the number of elementary machine + operations performed. + + + + We say that $R(n)$ has order of growth + 0e$\theta(f(n))$ (theta of $f(n)$) + theta$\theta(f(n))$ (theta of $f(n)$) + order notation + $\Theta(f(n))$, written + $R(n)=\Theta(f(n))$ (pronounced + theta of $f(n)$), if there are + positive constants $k_1$ and + $k_2$ independent of + $n$ such that + + \[ + \begin{array}{lllll} + k_1\,f(n) & \leq & R(n) & \leq & k_2\,f(n) + \end{array} + \] + + for any sufficiently large value of $n$. + (In other words, for large $n$, + the value $R(n)$ is sandwiched between + $k_1f(n)$ and + $k_2f(n)$.) + + + + order of growthlinear recursive process + linear recursive processorder of growth + recursive processlinear + For instance, with the linear recursive process for computing factorial + described in section the + number of steps grows proportionally to the input + $n$. Thus, the steps required for this process + grows as $\Theta(n)$. We also saw that the space + required grows as $\Theta(n)$. For the + order of growthlinear iterative process + linear iterative processorder of growth + iterative processlinear + iterative factorial, the number of steps is still + $\Theta(n)$ but the space is + $\Theta(1)$that is, + constant.These statements mask a great deal of oversimplification. + For instance, if we count process steps as machine operations + we are making the assumption that the number of machine operations needed to + perform, say, a multiplication is independent of the size of the numbers to + be multiplied, which is false if the numbers are sufficiently large. + Similar remarks hold for the estimates of space. Like the design and + description of a process, the analysis of a process can be carried out at + various levels of abstraction. + The + order of growthtree-recursive process + tree-recursive processorder of growth + recursive processtree + tree-recursive Fibonacci computation requires + $\Theta(\phi^{n})$ steps and space + $\Theta(n)$, where + $\phi$ is the golden ratio described in + section. + + + + Orders of growth provide only a crude description of the behavior of a + process. For example, a process requiring $n^2$ + steps and a process requiring $1000n^2$ steps and + a process requiring $3n^2+10n+17$ steps all have + $\Theta(n^2)$ order of growth. On the other hand, + order of growth provides a useful indication of how we may expect the + behavior of the process to change as we change the size of the problem. + For a + linear growth + $\Theta(n)$ (linear) process, doubling the size + will roughly double the amount of resources used. For an + exponential growth + exponential process, each increment in problem size will multiply the + resource utilization by a constant factor. In the remainder of + section + we will examine two algorithms whose order of growth is + logarithmic growth + logarithmic, so that doubling the problem size increases the resource + requirement by a constant amount. + order of growth + + + + Draw the tree illustrating the process generated by the + + count-change procedure + + count_change function + + + of section in making change for + 11 cents. What are the orders of growth of the space and number of steps + used by this process as the amount to be changed increases? + + The tree-recursive process generated in computing + cc(11, 5) is illustrated by the + image below, due to Toby Thain, assuming that the coin values in + first_denomination are + $\mathbb{C}_{1} = 1$, + $\mathbb{C}_{2} = 5$, + $\mathbb{C}_{3} = 10$, + $\mathbb{C}_{4} = 25$ and + $\mathbb{C}_{5} = 50$. + +
+ +
+ + Let us consider the process for evaluating + cc(n, k), which means the amount to + be changed is n and the number of + kinds of coins is k. Let us assume + the coin values are constants, not dependent on + n or + k. +

+ The space required for a tree-recursive process isas discussed in + sectionproportional to the + maximum depth of the tree. At each step from a parent to a child in the + tree, either n strictly decreases + (by a constant coin value) or k + decreases (by 1), and leaf nodes have an amount of at most 0 or a number + of kinds of coins of 0. Thus, every path has a length of + $\Theta(n + k)$, which is also the order of + growth of the space required for + cc(n, k). +

+ + Let us derive a function $T(n, k)$ such that + the time required for calculating + cc(n, k) has an order of growth of + $\Theta(T(n, k))$. The following argument is + due to Yati Sagade, including the illustrations + (Sagade 2015). + Let us start with the call tree for changing some amount + $n$ with just 1 kind of coin, i.e., + the call tree for cc(n, 1). + +
+ +
+ + We are only allowed here to use one kind of coin, with value + $\mathbb{C}_{1} = 1$. The red nodes are + terminal nodes that yield 0, the green node is a terminal node that + yields 1 (corresponding to the first condition in the declaration of + cc). Each nonterminal node spawns + two calls to cc, one (on the left) + with the same amount, but fewer kinds of coins, and the other (on the + right) with the amount reduced by 1 and equal kinds of coins. +

+ Excluding the root, each level has exactly 2 nodes, and there are + $n$ such levels. This means, the number of + cc calls generated by a single + cc(n, 1) call (including the original + call) is: + + \[ + T(n,1) = 2n + 1 = \Theta(n) + \] + + Next, we will look at the call tree of + cc(n, 2) + to calculate $T(n,2)$: + +
+ +
+ + Here, we are allowed to use two denominations of coins: + $\mathbb{C}_{2} = 5$ + and $\mathbb{C}_{1} = 1$. +

+ Each black node spawns a cc(m, 1) + subtree (blue), which we’ve already analyzed, and a + cc(m - 5, 2) subtree. The node + colored in red and green is a terminal node, but yields 0 if the amount + is less than zero and 1 if the amount is exactly zero. Sagade denotes + this final amount as $\epsilon$, which can + be $\le0$. +

+ Excluding the root and and the last level in this tree which contains the + red-green terminal node, there will be exactly + $\big\lfloor {\frac {n} {5}} \big\rfloor$ + levels. Now each of these levels contains a call to + cc(m, 1) (the blue nodes), each of + which, in turn, is $\Theta(n)$ in time. So each + of these levels contains $T(n,1) + 1$ calls to + cc. Therefore, the total number of + nodes (including the terminal node and the root) in the call tree for + cc(n, 2) is: + + \[ + T(n,2) = \big\lfloor {\frac {n} {5} } \big\rfloor ( T(n,1) + 1) + 2 = \big\lfloor {\frac {n} {5} } \big\rfloor ( 2n + 2 ) + 2 = \Theta(n^2) + \] + + Moving ahead, let’s take a look at the call tree of + cc(n, 3), i.e., we are now allowed + to use three denominations of coins, the new addition being + $\mathbb{C}_{3} = 10$: + +
+ +
+ + Here also, we see, similar to the previous case, that the total number of + calls to cc will be + + \[ + T(n,3) = \big\lfloor {\frac {n} {10} } \big\rfloor ( T(n,2) + 1) + 2 = \big\lfloor {\frac {n} {10} } \big\rfloor \times \Theta(n^2) + 2 = \Theta(n^3) + \] + + We can see a pattern here. For some $k$, + $k \gt 1$, we have, + + \[ + T(n,k) = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor ( T(n, k-1) + 1) + 2 + \] + + Here, $\mathbb{C}_{k}$ is the + $k^{th}$ coin denomination. We can expand this + further: + + \[ + T(n,k) + = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor ( T(n, k-1) + 1 ) + 2 + = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor + ( \big\lfloor {\frac {n} { \mathbb{C}_{k-1} } } \big\rfloor + (... \big\lfloor \frac {n} { \mathbb{C}_{2} } \big\rfloor (2n+1) ...) + ) + 2 + = \Theta(n^k) + \] + + Note that the actual values of the coin denominations have no effect on + the order of growth of this process, if we assume they are constants that + do not depend on n and + k. +
+ +
+ + + sineapproximation for small angle + The sine of an angle (specified in radians) can be computed by making use + of the approximation $\sin x\approx x$ + if $x$ is sufficiently small, and the + trigonometric identity + + \[ + \begin{array}{lll} + \sin x &=& 3\sin {\dfrac{x}{3}}-4\sin^3{\dfrac{x}{3}} + \end{array} + \] + + to reduce the size of the argument of $\sin$. + (For purposes of this exercise an angle is considered sufficiently + small if its magnitude is not greater than 0.1 radians.) These + ideas are incorporated in the following + + procedures: + functions: + + + cube + sine_definition + sine_example + abs_definition + +(define (cube x) (* x x x)) + +(define (p x) + (- (* 3 x) (* 4 (cube x)))) + +(define (sine angle) + (if (not (> (abs angle) 0.1)) + angle + (p (sine (/ angle 3.0))))) + + +function cube(x) { + return x * x * x; +} +function p(x) { + return 3 * x - 4 * cube(x); +} +function sine(angle) { + return ! (abs(angle) > 0.1) + ? angle + : p(sine(angle / 3)); +} + + + + + 0.9999996062176211 + sine_example + sine_definition + +(define pi 3.14159) +(sine (/ pi 2)) + + +sine(math_PI / 2); + + + +
    +
  1. How many times is the + + procedure + function + + p + applied when + + (sine 12.15) + sine(12.15) + + is evaluated? +
  2. +
  3. + What is the order of growth in space and number of steps (as a function + of$a$) used by the process generated + by the sine + + procedure + function + + when + + (sine a) + sine(a) + + is evaluated? +
  4. +
+ + +
    +
  1. The function p + will call itself recursively as long as the angle value is greater + than 0.1. There will be altogether 5 calls of + p, with arguments 12.15, 4.05, + 1.35, 0.45, 0.15 and 0.05. +
  2. +
  3. + The function sine gives + rise to a recursive process. In each recursive call, the + angle is divided by 3 + until its absolute value is smaller than 0.1. + Thus the number of steps and the space required has an order + of growth of $O(\log a)$. Note that the base of the logarithm + is immaterial for the order of growth because the logarithms + of different bases differ only by a constant factor. +
  4. +
+
+
+ +
+ +
diff --git a/xml/cn/chapter1/section2/subsection4.xml b/xml/cn/chapter1/section2/subsection4.xml new file mode 100644 index 000000000..58b026303 --- /dev/null +++ b/xml/cn/chapter1/section2/subsection4.xml @@ -0,0 +1,662 @@ + + 幂运算 + + + 幂运算 + + + 考虑计算给定数字的指数的问题。 + 我们希望有一个 + + 过程 + 函数 + + ,作为参数接受一个基数 +$b$ 和一个正整数指数 $n$ 和 + 计算 $b^n$ . 实现这一点的一种方法是通过 + 递归定义 + + \[ + \begin{array}{lll} + b^{n} &=& b\cdot b^{n-1}\\ + b^{0} &=& 1 + \end{array} + \] + + 这很容易转化为 + + 过程 + 函数 + + + exptlinear recursive version + expt_definition + expt_example + +(define (expt b n) + (if (= n 0) + 1 + (* b (expt b (- n 1))))) + + +function expt(b, n) { + return n === 0 + ? 1 + : b * expt(b, n - 1); +} + + + + expt_example + expt_definition + 81 + +(expt 3 4) + + +expt(3, 4); + + + 这是一个线性递归过程,要求 +$\Theta(n)$ 步骤和$\Theta(n)$ 空间。正如在阶乘中一样,我们 + 可以很容易地制定一个等效的线性迭代: + + exptlinear iterative version + expt_linear_definition + expt_example2 + 81 + +(define (expt b n) + (expt-iter b n 1)) + +(define (expt-iter b counter product) + (if (= counter 0) + product + (expt-iter b + (- counter 1) + (* b product)))) + + +function expt(b, n) { + return expt_iter(b, n, 1); +} +function expt_iter(b, counter, product) { + return counter === 0 + ? product + : expt_iter(b, counter - 1, b * product); +} + + + + expt_example2 + +(expt 3 4) + + +expt(3, 4); + + + + 该版本要求 $\Theta(n)$ 步骤和$\Theta(1)$ 空间。 + + + 我们可以通过使用 + 逐次平方法 + 更少的步骤来计算指数。 + 例如,不是计算 $b^8$ 为 + + \[ + \begin{array}{l} + b\cdot(b\cdot(b\cdot(b\cdot(b\cdot(b\cdot(b\cdot b)))))) + \end{array} + \] + + 我们可以使用三次乘法来计算它: + + \[ + \begin{array}{lll} + b^{2} &= & b\cdot b\\ + b^{4} &= & b^{2}\cdot b^{2}\\ + b^{8} &= & b^{4}\cdot b^{4} + \end{array} + \] + + + + + 这种方法对于以 2 为底的指数效果很好。如果我们使用规则,也可以在一般情况下利用逐次平方法来计算指数 + + \[ + \begin{array}{llll} + b^{n} &=& (b^{n/2})^{2} &\qquad\,\mbox{if}\ n\ \mbox{is even}\\ + b^{n} &=& b\cdot b^{n-1} &\qquad\mbox{if}\ n\ \mbox{is odd} + \end{array} + \] + + 我们可以将这种方法表示为 + + 过程: + 函数: + + + fast_expt + expt_log_definition + square_definition + even_definition + fast_expt_example + +(define (fast-expt b n) + (cond ((= n 0) 1) + ((even? n) + (square (fast-expt b (/ n 2)))) + (else + (* b (fast-expt b (- n 1)))))) + + + +function fast_expt(b, n) { + return n === 0 + ? 1 + : is_even(n) + ? square(fast_expt(b, n / 2)) + : b * fast_expt(b, n - 1); +} + + + + + fast_expt_example + expt_log_definition + 81 + +(fast-expt 3 4) + + +fast_expt(3, 4); + + + 用于测试一个整数是否为偶数的谓词是通过 + + + 原始过程 + 余数整数整除后的余数 + {\tt "%} (余数运算符)/// + {\tt "%} (余数)"% + 余数, + + + 余数整数整除后的余数 + {\tt "%} (余数运算符)/// + {\tt "%} (余数)/// + 运算符%, + 它计算整除后的余数, + + + 来定义的 + + is_even + even_definition + even_example + +(define (even? n) + (= (remainder n 2) 0)) + + +function is_even(n) { + return n % 2 === 0; +} + + + + + even_example + even_definition + +(even 7) + + +is_even(7); + + + 该过程通过 + + 快速幂函数 + fast_expt + + 增长阶对数 + 对数增长 + 以对数方式增长 +$n$ 在空间和步骤数量上都得到优化。为了证明这一点,可以观察到计算$b^{2n}$ 使用 + + 快速幂函数 + fast_expt + + 只需比计算 +$b^n$ . 因此,我们可以计算的指数的大小在每次允许的乘法中大约翻倍。因此,计算需要的乘法次数对于一个指数 $n$ 以接近对数的速度增长 $n$ 以 2 为底。该过程已 $\Theta(\log n)$ 增长。更准确地说,所需的乘法次数等于 $n$ 的以 2 为底的对数减去 1,加上 $n$ 的二进制表示中 1 的数量。这个总数总是小于 $n$ 的对数的两倍。定义阶次符号的任意常数 $k_1$ 和 $k_2$ 意味着,对于对数过程,所取对数的底数并不重要,因此所有这样的过程都可描述为 $\Theta(\log n)$。 + + + $\Theta(\log n)$ 增长和 $\Theta(n)$ 增长之间的差异在 $n$ 变大时变得非常明显。例如, + + 快速幂函数 + fast_expt + + 当 $n=1000$ 时,仅需 14 次乘法.你可能会想,为什么有人会关心将数字提高到 1000 次方。请参见 + section. + 同样可以利用逐次平方法的思想来设计一种迭代算法,该算法可以在以对数数量的步骤中计算指数(见 练习),尽管,正如迭代算法经常出现的那样,这种算法并没有像递归算法那样简洁地写下来.这个迭代算法是古老的。它出现在 + Chandah-sutra + Chandah-sutra 由 + Pingala, chrya + chrya,写于公元前 200 年之前。见 + Knuth, Donald E. + Knuth 1997b,第 4.6.3 节,关于此和其他幂运算方法的全面讨论和分析。 + 幂运算 + + + + 设计一个 + + 过程 + 函数 + + ,该过程演变为一个迭代的幂运算过程,使用逐次平方法,并使用对数数量的步骤,正如 + + 快速幂函数. + fast_expt. + + (提示:使用观察到的结果 +$(b^{n/2})^2 =(b^2)^{n/2}$ + ,保持与 + 指数一起 +$n$ + 和基数 +$b$ + 和一个额外的状态变量 +$a$ + ,并以这样的方式定义状态转变,使得乘积 +$a b^n$ + 状态在每个状态之间保持不变。在过程开始时 +$a$ + 被视为 1,答案由 + $n$ 的值给出 +$a$ + 在过程结束时。一般而言,定义一个 + 迭代过程的不变量 + 不变量,使其在状态之间保持不变,是思考 + 迭代过程算法设计 + 迭代算法设计的一种有效方法。) + + + + + fast_expt_iter + example_1.17 + 8 + even_definition + +function fast_expt_iter(a, b, n){ + return n === 0 + ? a + : is_even(n) + ? fast_expt_iter(a, b * b, n / 2) + : fast_expt_iter(a * b, b, n - 1); +} +function fast_expt(b, n){ + return fast_expt_iter(1, b, n); +} + + + + + example_1.17 + fast_expt_iter + +fast_expt(2, 3); + + + + + + + 本节中的幂运算算法基于通过重复乘法进行幂运算。类似地,可以通过重复加法执行整数乘法。以下乘法 + + 过程 + 函数 + + (假设我们的语言只能加法,而不能进行乘法)类似于 +expt + + + 过程: + 函数: + + + times_definition + times_example + +(define (* a b) + (if (= b 0) + 0 + (+ a (* a (- b 1))))) + + +function times(a, b) { + return b === 0 + ? 0 + : a + times(a, b - 1); +} + + + + times_example + 12 + times_definition + +(* 3 4) + + +times(3, 4); + + + 该算法所需的步骤数量是线性的 +b + 现在假设我们包括,与 + 加法一起, + + 操作 + 函数 + +double + ,它将一个整数翻倍,和 +halve + ,它将一个(偶)整数除以 2。使用这些,设计一个乘法 + + 过程 + 函数 + + 类似于 + + + 快速幂函数 + fast_expt + + 使用对数数量的步骤。 + + + example_1.18_definition + example_1.18 + even_definition + +function double(x) { + return x + x; +} + +function halve(x) { + return x / 2; +} + +function fast_times(a, b) { + return b === 1 + ? a + : a === 0 || b === 0 + ? 0 + : is_even(b) + ? double(fast_times(a, halve(b))) + : a + fast_times(a, b - 1); +} + + + + + + + example_1.18 + example_1.18_definition + 12 + +fast_times(3, 4); + + + + + + + + 使用练习的结果 + 和,设计一个 + + 过程 + 函数 + + ,生成一个迭代过程,以加法、翻倍和除以一半为基础 + 用对数数量的步骤实现两个整数的乘法。这个 + 算法,有时被称为 + 俄罗斯农民乘法法 + 俄罗斯农民法乘法 + 俄罗斯农民法 的乘法,是古老的。它的使用示例在 + 莱茵德纸草书 + 莱茵德纸草书中可以找到,这是现存的两部最古老的数学文献之一, + 约公元前 1700 年由一位名叫 + AhmoseAh-mose + Ah-mose 的埃及抄写员撰写(并复制自更古老的文献)。 + + + + fast_times_iter + example_1.19 + even_definition + +function double(x) { + return x + x; +} + +function half(x) { + return x / 2; +} + +function fast_times_iter(total, a, b) { + return b === 1 + ? total + a + : a === 0 || b===0 + ? 0 + : is_even(b) + ? fast_times_iter(total, double(a), half(b)) + : fast_times_iter(total + a, a, b - 1); +} + +function times(a, b) { + return fast_times_iter(0, a, b); +} + + + + + + example_1.19 + fast_times_iter + 12 + +times(3, 4); + + + + + + + + + 有一个聪明的算法,用于在 + fib对数版本 + 对数步骤中计算斐波那契数。回想状态变量的转换 +$a$$b$ + 在 + + + fib-iter + fib_iter + + 过程中,节: +$a\leftarrow a+b$$b\leftarrow a$ + 使用练习的结果 + 和,设计一个 + + 过程 + 函数 + + ,生成一个迭代过程,以加法、翻倍和除以一半为基础 + 用对数数量的步骤实现两个整数的乘法。这个 + 算法,有时被称为 + 俄罗斯农民乘法法 + 俄罗斯农民法乘法 + 俄罗斯农民法 的乘法,是古老的。它的使用示例在 + 莱茵德纸草书 + 莱茵德纸草书中可以找到,这是现存的两部最古老的数学文献之一, + 约公元前 1700 年由一位名叫 + AhmoseAh-mose + Ah-mose 的埃及抄写员撰写(并复制自更古老的文献)。 +$T$ + ,并观察应用 +$T$ + 一遍又一遍地 +$n$ + 次数,从 1 和 0 开始, + 产生这一对 +$\textrm{Fib}(n+1)$$\textrm{Fib}(n)$ + . 换句话说,斐波那契数是通过应用 +$T^n$ + . 也就是说,斐波那契数是通过应用 +$n$ + 次方的转换 +$T$ + 次方的转换 +$(1,0)$ + . 现在考虑 +$T$ + 特殊情况 +$p=0$$q=1$ + 在一系列变换中 +$T_{pq}$ + 在 +$T_{pq}$ + 变换序对 +$(a,b)$ + 根据 +$a\leftarrow bq+aq+ap$$b\leftarrow bp+aq$ + 应用这种变换 +$T_{pq}$ + 两次,效果与使用单一变换相同 +$T_{p'q'}$ + 的相同形式,并计算 +$p'$$q'$ + 以...的术语 +$p$ $q$ + 两次变换,这个效果与使用单一变换相同 +$T^n$ + 使用逐次平方法,如在 + + 快速幂函数 + fast_expt + + + 过程。 + 函数。 + + 将所有这些放在一起以完成以下 + + 过程, + 函数, + + 其运行在对数数量的步骤中:此练习是 + + + 提出的 + + + 由 + Stoy, Joseph E. + Joe Stoy,基于 + Kaldewaij, Anne + Kaldewaij 1990 中的一个例子。 + + fib_log_definition + even_definition + fib_example + +(define (fib n) + (fib-iter 1 0 0 1 n)) +(define (fib-iter a b p q count) + (cond ((= count 0) b) + ((even? count) + (fib-iter a + b + ?? ; compute p' + ?? ; compute q' + (/ count 2))) + (else (fib-iter (+ (* b q) (* a q) (* a p)) + (+ (* b p) (* a q)) + p + q + (- count 1))))) + + +function fib(n) { + return fib_iter(1, 0, 0, 1, n); +} +function fib_iter(a, b, p, q, count) { + return count === 0 + ? b + : is_even(count) + ? fib_iter(a, + b, + ??, // compute p' + ??, // compute q' + count / 2) + : fib_iter(b * q + a * q + a * p, + b * p + a * q, + p, + q, + count - 1); +} + + + + + fib_log_solution + example_1.20 + even_definition + +function fib(n) { + return fib_iter(1, 0, 0, 1, n); +} + +function fib_iter(a, b, p, q, count) { + return count === 0 + ? b + : is_even(count) + ? fib_iter(a, + b, + p * p + q * q, + 2 * p * q + q * q, + count / 2) + : fib_iter(b * q + a * q + a * p, + b * p + a * q, + p, + q, + count - 1); +} + + + + + + example_1.20 + fib_log_solution + 5 + +fib(5); + + + + + + + diff --git a/xml/cn/chapter1/section2/subsection5.xml b/xml/cn/chapter1/section2/subsection5.xml new file mode 100644 index 000000000..a29b0070d --- /dev/null +++ b/xml/cn/chapter1/section2/subsection5.xml @@ -0,0 +1,306 @@ + + 最大公约数 + + + 最大公约数 + + + 两个整数的最大公约数 (GCD) 被定义为能够同时整除这两个数 $a$ 和 $b$ 的最大整数,并且没有余数。例如,16 和 28 的 GCD 是 4。在章中,当我们研究如何实现有理数算术时,我们将需要能够计算 GCD,以便将有理数简化为最简形式。(要将有理数简化为最简形式,我们必须将分子和分母都除以它们的 GCD。例如,16/28 简化为 4/7。) 找到两个整数 GCD 的一种方法是对它们进行因子分解并搜索共同因子,但还有一个著名的算法更为高效。 + + + + 欧几里得算法 + 算法的思想基于这样的观察:如果 +$r$ 是余数,当 +$a$ 被除以 +$b$ , 然后 的公因子 +$a$ 和 +$b$ 正是 的公因子 +$b$ 和 +$r$ . 因此,我们可以使用方程 + + \[\begin{array}{lll} + \textrm{GCD} (a, b) &=& \textrm{GCD}(b, r) + \end{array}\] + + 逐步将计算 GCD 的问题简化为计算越来越小的整数对的 GCD 的问题。例如, + + + + \[\begin{array}{lll} + \textrm{GCD}(206,40) & = & \textrm{GCD}(40,6) \\ + & = & \textrm{GCD}(6,4) \\ + & = & \textrm{GCD}(4,2) \\ + & = & \textrm{GCD}(2,0) \\ + & = & 2 + \end{array}\] + + 化简 +$\textrm{GCD}(206, 40)$ 到 +$\textrm{GCD}(2, 0)$ , 其结果是 2。可以显示,从任何两个正整数开始,进行反复化简将始终最终产生一对,其中第二个数字为 0。然后 GCD 就是这一对中的另一个数字。这个计算 GCD 的方法被称为 欧几里得算法欧几里得算法之所以如此称呼,是因为它出现在欧几里得的 欧几里得的几何原本欧几里得的 几何原本 几何原本(书 7,大约公元前 300 年)。根据 Knuth, Donald E. Knuth (1997a) 的说法,可以认为这是已知的最古老的非平凡算法。古埃及的乘法方法(练习)无疑更古老,但正如 Knuth 解释的,欧几里得算法是已知的最古老的被提出作为一般算法的方法,而不是一组插图示例。 + + + + 表达欧几里得算法很容易作为一个 + + 过程: + 函数: + + + gcd + gcd_definition + gcd_example + +(define (gcd a b) + (if (= b 0) + a + (gcd b (余数 a b)))) + + +function gcd(a, b) { + return b === 0 ? a : gcd(b, a % b); +} + + + + + gcd_example + gcd_definition + 4 + +(gcd 20 12) + + +gcd(20, 12); + + + 这生成了一个迭代过程,其步骤数量随着相关数字的对数增长。 + + + + 欧几里得算法所需步骤数量的对数增长与 + 斐波那契数欧几里得的 GCD 算法与 + 斐波那契数之间存在有趣的关系: +
+ 拉梅定理: + 拉梅拉梅定理 + 如果欧几里得的算法需要 +$k$ 步骤来计算某对的 GCD,那么对中的较小数字必须大于或等于 +$k$ +第 th 斐波那契数。 + +这个定理由 +拉梅拉梅 证明于1845年,他是一位法国数学家和工程师,主要以对数学物理的贡献而闻名。为了证明该定理,我们考虑对 +$(a_k ,b_k)$ , 其中 +$a_k\geq b_k$ , 其中欧几里得的算法在 +$k$ +步骤。证明基于这样的说法,如果 +$(a_{k+1},\ b_{k+1}) \rightarrow (a_{k},\ b_{k}) + \rightarrow (a_{k-1},\ b_{k-1})$ +是三个连续对在化简过程中,那么我们必须 +$b_{k+1}\geq b_{k} + b_{k-1}$ +要验证这一说法,考虑到化简步骤是通过应用 +$a_{k-1} = b_{k}$, + $b_{k-1} = + \textrm{remainder of}\ a_{k}\ \textrm{divided by}\ b_{k}$ +步骤。第二个方程意味着 +$a_{k} = qb_{k} + b_{k-1}$ +某个正整数 +$q$ +因为 +$q$ +至少必须为 1,我们有 +$a_{k} = qb_{k} + b_{k-1} \geq b_{k} + b_{k-1}$ +。而在之前的化简步骤中,我们有 +$b_{k+1}= a_{k}$ +。因此, +$b_{k+1} = a_{k}\geq b_{k} + b_{k-1}$ +。这验证了该声称。现在我们可以通过对 +$k$ +算法终止所需的步骤数。结果对于 +$k=1$ +,因为这仅仅要求 +$b$ +至少与 +$\text{Fib}(1)=1$ +。假设结果对于所有小于或等于 +$k$ +并建立结果 +$k+1$ +。设 +$(a_{k+1},\ b_{k+1})\rightarrow(a_{k},\ b_{k}) + \rightarrow(a_{k-1},\ b_{k-1})$ +在化简过程中是连对。根据我们的归纳假设,我们有 +$b_{k-1}\geq {\textrm{Fib}}(k-1)$ +和 +$b_{k}\geq {\textrm{Fib}}(k)$ +因此,将我们刚刚证明的结论与斐波那契数的定义结合起来得到 +$b_{k+1} \geq b_{k} + b_{k-1}\geq {\textrm{Fib}}(k) + + {\textrm{Fib}}(k-1) = {\textrm{Fib}}(k+1)$ +,这完成了拉梅定理的证明。 +
+
+ + + + 我们可以利用这个定理来获得欧几里得算法的增长阶估计。设 $n$ 为两个输入中较小的一个 + + 过程. + 函数. + + 如果该过程需要 $k$ 步骤,则我们必须有 + $n\geq {\textrm{Fib}} (k)\approx\phi^k/\sqrt{5}$。 + 因此步骤数 $k$ 的增长与 + $n$ 的对数(以 $\phi$ 为底)相同。因此,增长阶为 + $\Theta(\log n)$。 + 最大公约数 + 欧几里得算法 + + + + + 由 + + 过程 + 函数 + + 生成的过程当然取决于解释器使用的规则。作为一个例子,考虑迭代 +gcd + +上述 + + 过程 + 函数 + + 。假设我们使用 + 正常序求值应用序 vs. + 应用序求值正常序 vs. + 正常序求值来解释这个 + + 过程 + 函数 + + ,正如在 + 一节中讨论的那样。(正常序求值规则 + + if + 条件表达式 + + 的描述见练习。) + 使用代换法(对于正常序),说明评估过程中的 + + (gcd 206 40) + gcd(206, 40) + + 生成的过程,并指示 +remainder +实际执行的操作。多少 +remainder +在正常序求值中实际执行的操作有多少 + + (gcd 206 40)? + gcd(206, 40)? + + 在应用序求值中又有多少? + + + +
    +
  1. +使用正常序求值,过程经历了 18 次余数操作。在评估条件时有 14 次,最终化简阶段有其余的操作。 + + +gcd(206, 40) +40 === 0 ? 206 : gcd(40, 206 % 40) +gcd(40, 206 % 40) +206 % 40 === 0 ? 40 : gcd(206 % 40, + 40 % (206 % 40)) +// remainder operation (1) +6 === 0 ? 40 : gcd(206 % 40, + 40 % (206 % 40)) +gcd(206 % 40, 40 % (206 % 40)) +40 % (206 % 40) === 0 + ? 206 % 40 + : gcd(40 % (206 % 40), + (206 % 40) % (40 % (206 % 40))) +// remainder operations (2) and (3) +4 === 0 + ? 206 % 40 + : gcd(40 % (206 % 40), + (206 % 40) % (40 % (206 % 40))) +gcd(40 % (206 % 40), (206 % 40) % (40 % (206 % 40))) +(206 % 40) % (40 % (206 % 40)) === 0 + ? 40 % (206 % 40) + : gcd((206 % 40) % (40 % (206 % 40)), + (40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40))) +// remainder operations (4), (5), (6), (7) +2 === 0 + ? 40 % (206 % 40) + : gcd((206 % 40) % (40 % (206 % 40)), + (40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)))) +gcd((206 % 40) % (40 % (206 % 40)), + (40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40))) +(40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40))) === 0 + ? (206 % 40) % (40 % (206 % 40)) + : gcd((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)), + ((206 % 40) % (40 % (206 % 40))) % + ((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)))) +// remainder operations + (8), (9), (10), (11), (12), (13), (14) +0 === 0 + ? (206 % 40) % (40 % (206 % 40)) + : gcd((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)), + ((206 % 40) % (40 % (206 % 40))) % + ((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)))) +(206 % 40) % (40 % (206 % 40)) +// remainder operations (15), (16), (17), (18) +2 + + +
  2. + +使用应用序求值,过程执行了 4 次余数操作。 + + +gcd(206, 40) +40 === 0 ? 206 : gcd(40, 206 % 40) +gcd(40, 206 % 40) +// 余数操作 (1) +gcd(40, 6) +6 === 0 ? 40 : gcd(6, 40 % 6) +gcd(6, 40 % 6) +// 余数操作 (2) +gcd(6, 4) +4 === 0 ? 6 : gcd(4, 6 % 4) +gcd(4, 6 % 4) +// 余数操作 (3) +gcd(4, 2) +2 === 0 ? 4 : gcd(2, 4 % 2) +gcd(2, 4 % 2) +// 余数操作 (4) +gcd(2, 0) +0 === 0 ? 2 : gcd(0, 2 % 0) +2 + + + </LI> +
+
+
+
+
+
diff --git a/xml/cn/chapter1/section2/subsection6.xml b/xml/cn/chapter1/section2/subsection6.xml new file mode 100644 index 000000000..c2d4554f4 --- /dev/null +++ b/xml/cn/chapter1/section2/subsection6.xml @@ -0,0 +1,1166 @@ + + + 示例:素性测试 + + + 素数测试 + 素数 + + + 本节描述了两种检查整数 $n$ 素性的 + 方法,一种增长阶为 $\Theta(\sqrt{n})$, + 另一种是增长阶为 $\Theta(\log n)$ 的 + 概率算法。本节末尾的练习建议了基于这些算法的编程项目。 + + + + 查找因子 + + + + 自古以来,数学家们对素数问题着迷,许多人致力于确定测试数字是否为素数的方法。测试一个数字是否为素数的一种方法是查找该数字的因子。以下程序查找给定数字$n$的最小整数因子(大于 1)。它通过从 2 开始依次测试整数来直接实现n的可除性。 + + find_divisor + divides + smallest_divisor + smallest_divisor_definition + square_definition + smallest_divisor_example + +(define (smallest-divisor n) + (find-divisor n 2)) + +(define (find-divisor n test-divisor) + (cond ((> (square test-divisor) n) n) + ((divides? test-divisor n) test-divisor) + (else (find-divisor n (+ test-divisor 1))))) + +(define (divides? a b) + (= (remainder b a) 0)) + + +function smallest_divisor(n) { + return find_divisor(n, 2); +} +function find_divisor(n, test_divisor) { + return square(test_divisor) > n + ? n + : divides(test_divisor, n) + ? test_divisor + : find_divisor(n, test_divisor + 1); +} +function divides(a, b) { + return b % a === 0; +} + + + + smallest_divisor_example + smallest_divisor_definition + 2 + +(smallest-divisor 42) + + +smallest_divisor(42); + + + + + + 我们可以如下测试一个数字是否为素数: + 当且仅当 $n$ 是其自身的最小因子时, + $n$ 才是素数。 + + is_prime + prime_definition + smallest_divisor_definition + prime_example + +(define (prime? n) + (= n (smallest-divisor n))) + + +function is_prime(n) { + return n === smallest_divisor(n); +} + + + + + prime_example + prime_definition + false + +(prime? 42) + + +is_prime(42); + + + + + + + + 终止测试 + + find-divisor + find_divisor + + 基于这样一个事实:如果 $n$ 不是素数, + 则它必须有一个小于或等于 $\sqrt{n}$ 的因子。如果 + $d$ 是 + $n$ 的因子,那么 $n/d$ 也是。 + 但 $d$ 和 $n/d$ 不能都大于 $\sqrt{n}$。 + 这意味着算法只需测试介于 1 和 $\sqrt{n}$ 之间的因子。因此,识别 $n$ 为素数所需步骤数的增长阶为 $\Theta(\sqrt{n})$。 + + + + 费马测试 + + + + The $\Theta(\log n)$ 素性测试基于数论中的一个结果,称为 + 费马素性测试 + 素数费马测试 + 费马的小定理。皮埃尔 + 费马费马,皮埃尔·德 + 费马(16011665)被认为是现代 + 数论 + 数论的创始人。他取得了许多重要的数论结果,但他通常只公布结果,没有提供证明。 + 费马的费马小定理证明 + 费马的小定理在1640年他写的一封信中被陈述。第一个已发表的证明由 + 欧拉,莱昂哈德费马的小定理的证明 + 欧拉在1736年给出(而在未发表的 + 莱布尼茨,戈特弗里德·威廉·冯费马的小定理的证明 + 莱布尼茨的手稿中找到了更早的相同证明)。费马最著名的结果被称为费马最后定理于1637年在他所持有的书籍算术(由三世纪的希腊数学家 + 丢番图算术,费马的版本 + 丢番图)中写下,并留下旁注我发现了一个真正非凡的证明, 但这段空白不够写下它。找到费马最后定理的证明成为数论中最著名的挑战之一。最终完整的解决方案由 + 威尔斯,安德鲁 + 普林斯顿大学的安德鲁·威尔斯在1995年给出。 + +
+ 费马的小定理: + 费马的费马的小定理 + 如果 $n$ 是一个素数,并且 + $a$ 是小于 $n$ 的任意正整数, + 那么 $a$ 的 $n$ 次幂同余于 + $a$ 模$n$。 +
+ (两个数字被称为 + 模 $n$ 同余 + 同余模 + $n$ 如果它们在除以 时有相同的余数 $n$ 。 一个数字的余数 $a$ 被除以 $n$ 也被称为 + 模 $n$ 余数 + 模 $n$ + 的余数 $a$ + $n$ ,或简称为 $a$ + $n$ 。) +
+ + + 如果 $n$ 不是素数,那么通常大多数小于 + $n$ 的数字 $a$ 将不满足上述关系。这导致了以下用于测试素性的算法: + 给定一个数 $n$,选择一个 + 随机数生成器素性素性测试中使用 + 随机数 $a < n$ 并计算 $a^n$ 模 + $n$ 的余数。如果结果不等于 + $a$,则 $n$ 一定不是素数。如果等于 + $a$,那么 $n$ 很可能是素数。现在选择另一个 + 随机数 $a$ 并用同样的方法测试它。如果它也满足这个方程,那么可以更有信心地认为 + $n$ 是素数。通过尝试越来越多的 $a$ 值,可以增加对结果的信心。这个算法被称为费马测试。 + + + + 要实现费马测试,我们需要一个 + + 过程 + 函数 + + 来计算一个数的 + 幂运算模 $n$ + : + + expmod + expmod_definition + expmod_example + even_definition + square_definition + +(define (expmod base exp m) + (cond ((= exp 0) 1) + ((even? exp) + (remainder + (square (expmod base (/ exp 2) m)) + m)) + (else + (remainder + (* base (expmod base (- exp 1) m)) + m)))) + + +function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? square(expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; +} + + + + + expmod_example + expmod_definition + 4 + +(expmod 4 3 5) + + +expmod(4, 3, 5); + + + 这与 + + fast-expt + fast_expt + + 在第节中的 + + 过程 + 函数 + + 非常相似。它使用逐次平方,因此步骤数与指数的增长呈对数关系。在指数 + $e$ 大于 1 的情况下,简化步骤基于这样的事实:对于任何整数 + $x$, + $y$ 和 $m$,我们可以通过分别计算 + $x$ 和 $y$ 对 $m$ 的余数, + 然后将这些结果相乘,最后取结果对 + $m$ 的余数来找到 + $x \times y$ 对 $m$ 的余数。例如,当 + $e$ 为偶数时,我们计算 $b^{e/2}$ 对 + $m$ 的余数,平方这个值,然后对 $m$ 取余。这种技术很有用,因为它意味着我们可以在计算过程中不用处理比 + $m$ 大得多的数字。(比较练习。) + + + + + 费马测试通过随机选择一个数字来进行 + $a$ 介于 1 和 + $n-1$ 之间,并检查它对 的模余数是否 + $n$ 的 + $n$ 次幂的 $a$ 是否等于 $a$ 。 随机数 + $a$ 是使用 + + + 过程 + random, + + + 原语函数 + math_random, + + + + 我们假设其作为 Scheme 的原语包括在内。 + + + + random (原语函数) + random + Random + 返回一个小于其整数输入的非负整数。因此,要获得 + 一个介于 1 和 $n-1$ 之间的随机数,我们调用 + random 的输入为 $n-1$ 并加 1: + + + math_random (原语函数) + math_randomMath.random + 返回一个小于1的非负数。因此,要获得 + 一个介于 1 和 $n-1$ 之间的随机数,我们将 + math_random 的返回值乘以 + $n-1$,用原语函数 + math_floor (原语函数) + math_floorMath.floor + math_floor 向下取整, + 并加 1: + + + + random_definition + +;; random is predefined in Scheme + + +function random(n) { + return math_floor(math_random() * n); +} + + + + fermat_test + fermat_test_definition + square_definition + expmod_definition + random_definition + fermat_test_example + +(define (fermat-test n) + (define (try-it a) + (= (expmod a n n) a)) + (try-it (+ 1 (random (- n 1))))) + + +function fermat_test(n) { + function try_it(a) { + return expmod(a, n, n) === a; + } + return try_it(1 + math_floor(math_random() * (n - 1))); +} + + + + + fermat_test_example + fermat_test_definition + true + +(fermat-test 97) + + +fermat_test(97); + + + + + + 下面的 + + 过程 + 函数 + + 根据参数指定的次数运行测试。如果测试每次都成功,其值为 true,否则为 false。 + + fast_is_prime + fast_prime_definition + square_definition + expmod_definition + random_definition + fermat_test_definition + fast_prime_example + +(define (fast-prime? n times) + (cond ((= times 0) true) + ((fermat-test n) + (fast-prime? n (- times 1))) + (else false))) + + +function fast_is_prime(n, times) { + return times === 0 + ? true + : fermat_test(n) + ? fast_is_prime(n, times - 1) + : false; +} + + + + fast_prime_example + fast_prime_definition + true + +(fast-prime? 97 3) + + +fast_is_prime(97, 3); + + + + + + 概率方法 + + + 概率算法 + 算法概率 + + + 费马测试与大多数熟悉的算法不同,后者能计算出保证正确的答案。而费马测试得到的答案只是可能正确。更确切地说,如果 + $n$ 在费马测试中失败,我们可确定 + $n$ 不是素数。但 $n$ 通过测试的事实,虽然是一个极强的指示,仍不能保证 + $n$ 是素数。我们想说的是,对于任何数字 + $n$,如果我们进行足够多次测试,并发现 + $n$ 每次都通过测试,那么我们素性测试中出错的概率可以降到任意小。 + + + + + 不幸的是,这一断言并不完全正确。确实存在一些数字能欺骗费马测试:数字 + $n$ 哪些非素数却具有这样的性质 + $a^n$ 同余于 $a$ 模 + $n$ 对于所有整数 + $a < n$ 。这类数字非常罕见,因此在实践中费马测试是相当可靠的。 + + 能欺骗费马测试的数字称为 + 卡迈克尔数 + 卡迈克尔数,对它们的研究不多,只知道它们非常稀少。在一亿以下有 255 个 + 卡迈克尔数。最小的几个是 561, 1105, + 1729, 2465, 2821, 和 6601。在随机选择的非常大的数字的素性测试中,遇到一个欺骗费马测试的值的机会小于 + 宇宙辐射 + 宇宙辐射造成计算机在进行所谓 + 正确算法时出错的几率。认为一个算法因第一原因而不足(而非第二原因)体现了 + 工程学 vs.数学 + 数学工程学 vs. + 数学与工程学的区别。 + + 费马测试有一些变种无法被愚弄。在这些测试中,就像费马方法一样,人们测试一个整数的素性 + $n$ 通过选择一个随机整数 + $a < n$ 并检查一些依赖于 的条件 + $n$ 和 + $a$ 。 (请参见练习 了解此类测试的示例。) + 另一方面,与费马测试相比,可以证明,对于任何 $n$ ,对于大多数整数,该条件不成立 + $a < n$ 除非 + $n$ 是素数。因此,如果 + $n$ 通过某个随机选择的测试 + $a$ ,则 的机会大于一半 + $n$ 是素数。如果 + $n$ 在两个随机选择下通过测试 + $a$ ,则 的机会大于 3/4 + $n$ 是素数。通过对更多随机选择的 值运行测试 + $a$ 我们可以将出错的概率降低到任意小。 + 费马素性测试 + 素数费马测试 + + + + 存在这样一种测试,可以证明其错误的概率变得任意小,这引发了人们对这类算法的兴趣,这些算法被称为概率算法。 +概率算法 +算法概率 +素数 + 在这个领域有大量的研究活动,概率算法已经成功应用于许多领域。 概率素数测试最显著的应用之一是在 + 密码学 + 密码学领域。 + + + 虽然现在仍计算上难以分解一个任意 200 位 + 数的因子,但可以用费马测试在几秒钟内检查这样一个数的素性。 + + + 截至编写时(2021 年),仍然计算困难以分解一个任意 300 位 + 数的因子,但可以用费马测试在几秒钟内检查这样一个数的素性。 + + + 这个事实构成了一种用于构建 + 不可破解的密码的技术基础,它由 + Ronald L. Rivest + Rivest、 + Adi Shamir + Shamir 和 + Leonard Adleman + Adleman (1977) 提出。最终的 + RSA 算法 + RSA 算法 已成为一种广泛用于提升电子通讯安全的技术。由于这一点和相关的发展,研究 + 素数与密码学 + 素数,曾经被认为是“纯”数学中仅为本身而研究的话题的典范,现在发现它对密码学、电子资金转账和信息检索有重要的实际应用。 + + + + + 使用 + + smallest-divisor + smallest_divisor + + + + 过程 + 函数 + + 找出以下数字的最小因子:199、1999、 + 19999。 + + + smallest_division_solution + 199 + smallest_divisor_definition + +smallest_divisor(199); +// smallest_divisor(1999); +// smallest_divisor(19999); + + + + + + + + + + + + + 大多数 Lisp 实现都包含一个名为 + runtime + 原语 过程函数 (用 ns 标记的不是 IEEE Scheme 标准中的)runtimeruntime (ns) + runtime + 的原语,它返回一个整数,指定系统已经运行的时间量(例如,以微秒为单位)。以下是 + timed-prime-test + 过程, + + + 假设一个无参数的原语函数 + get_time (原语函数) + get_timenew Date().getTime + get_time + 返回自协调世界时 1970 年 1 月 1 日星期四 00:00:00 以来的毫秒数。此日期称为 + UNIXepoch + UNIX 纪元,是处理时间的函数规范的一部分 + UNIX$^{\textrm{TM}}$ 操作系统。 + 以下是 + timed_prime_test + 函数, + + + 当用一个整数调用时 $n$ ,打印 + $n$ 并检查是否 + $n$ 是素数。如果 $n$ + 是素数, + + 过程 + 函数 + + 打印三个星号原语函数 display 返回其 + 参数,同时也打印它。在这里 + *** + 是一个 + 字符串,我们将其作为参数传递给 display 函数。 + 第节会更详细地介绍字符串。,然后是执行测试所用的时间。 + + + newline + + + + display (primitive function) + timed_prime_test + timed_prime_definition + prime_definition + timed_prime_example + +(define (timed-prime-test n) + (newline) + (display n) + (start-prime-test n (runtime))) + +(define (start-prime-test n start-time) + (if (prime? n) + (report-prime (- (runtime) start-time)))) + +(define (report-prime elapsed-time) + (display " *** ") + (display elapsed-time)) + + +function timed_prime_test(n) { + display(n); + return start_prime_test(n, get_time()); +} + +function start_prime_test(n, start_time) { + return is_prime(n) + ? report_prime(get_time() - start_time) + : false; +} + +function report_prime(elapsed_time) { + display(" *** "); + display(elapsed_time); + return true; +} + + + + timed_prime_example + timed_prime_definition + +(timed-prime-test 43) + + +timed_prime_test(43); + + + 使用这个 + + 过程, + 函数, + + 编写一个 + + 过程 + 函数 + + + + search-for-primes + + + search_for_primes + + + 用于检查指定范围内连续奇整数的素性。 + 使用您的 + + + 过程 + + + 函数 + + + 找出大于 1000, 大于 10,000, 大于 100,000, 大于 1,000,000 的三个最小素数。注意测试每个素数所需的时间。由于测试算法的增长阶为 + $\Theta(\sqrt{n})$ ,您应该预期测试大约 10,000 附近的素数需要大约 + $\sqrt{10}$ 是测试大约 1000 附近的素数的时间的几倍。您的计时数据是否支持这一点?10,000 和 1,000,000 的数据支持这个 + $\sqrt{n}$ + 预测?您的结果是否与程序在您的计算机上按与计算所需步骤数成比例的时间运行的概念兼容? + + + search_for_primes_definition + search_for_primes_example + timed_prime_definition + +function search_for_primes(start, times) { + return times === 0 + ? true + : start > 2 && start % 2 === 0 + ? search_for_primes(start + 1, times) + // if we get true, it's a prime + : timed_prime_test(start) + ? search_for_primes(start + 2, times - 1) + : search_for_primes(start + 2, times); +} + + + + + + search_for_primes_example + search_for_primes_definition + +search_for_primes(10000, 3); +// search_for_primes(10000000, 3); +// search_for_primes(10000000000, 3); + + + + + 时间数据相当清晰地支持了对于预测足够大的 $\sqrt{n}$,如 100,000 和 1,000,000。 + + + + + + 更高效版本的 + smallest_divisor更高效版本 + + + smallest-divisor + + + smallest_divisor + + + 过程函数 + 在本节开头展示的进行许多不必要的测试:在检查数字是否能被 2 整除后,就没有必要再检查它是否能被更大的偶数整除。这表明用于 + + + test-divisor + + + test_divisor + + + 的值不应该是 2、3、4、5、6、,而应该是 2、3、5、7、9、 + 。要实施这一更改, + + 定义一个过程 + 声明一个函数 + + next + 返回 3 如果其输入等于 2 + 否则返回其输入加2。修改 + + + smallest-divisor + + + smallest_divisor + + + + 过程 + 函数 + + 以使用 + + + (next test-divisor) + + + next(test_divisor) + + + 而非 + + (+ test-divisor 1). + test_divisor + 1. + + 拼入该修改版本的 + + + timed-prime-test + + + timed_prime_test + + + 的 + + smallest-divisor, + smallest_divisor, + + + 对在练习中找到的 12 个素数运行测试。 + 由于此修改将测试步骤减半,您应该预期其运行速度大约提高一倍。如果没有确认这一预期,观察到的两种算法速度的比率是什么,您如何解释它与 2 的差异? + + + better_smallest_divisor_solution + +function next(input) { + return input === 2 + ? 3 + : input + 2; +} + +function find_divisor(n, test_divisor) { + return square(test_divisor) > n + ? n + : divides(test_divisor, n) + ? test_divisor + : find_divisor(n, next(test_divisor)); +} + + +function square(x) { + return x * x; +} +function smallest_divisor(n) { + return find_divisor(n, 2); +} +function divides(a, b) { + return b % a === 0; +} +function is_prime(n) { + return n === smallest_divisor(n); +} +function timed_prime_test(n) { + display(n); + return start_prime_test(n, get_time()); +} +function start_prime_test(n, start_time) { + return is_prime(n) + ? report_prime(get_time() - start_time) + : true; +} +function report_prime(elapsed_time) { + display(" *** "); + display(elapsed_time); +} +function next(input) { + return input === 2 + ? 3 + : input + 2; +} +function find_divisor(n, test_divisor) { + return square(test_divisor) > n + ? n + : divides(test_divisor, n) + ? test_divisor + : find_divisor(n, next(test_divisor)); +} + +timed_prime_test(43); + + + + + 两种算法的速度比不完全是 2,但这可能是由于硬件/网络问题。与以前的解决方案相比,大约快 1.5 倍。 + + + + + 修改练习中的 + + + timed-prime-test + + + timed_prime_test + + + + 过程 + 函数 + + 以使用 + + + fast-prime? + + + fast_is_prime + + + (费马方法),并测试您在该练习中找到的每个 12 个素数。由于费马测试具有 + $\Theta(\log n)$ 增长,您会如何预期测试 1,000,000 附近素数的时间与测试 1000 附近素数的时间相比?您的数据是否支持这一点?您能解释发现的任何差异吗? + + + + mod_timed_prime_test_example + mod_timed_prime_test_solution + fast_prime_definition + +function timed_prime_test(n) { + display(n); + return start_prime_test(n, get_time()); +} +function start_prime_test(n, start_time) { + return fast_is_prime(n, math_floor(math_log(n))) + ? report_prime(get_time() - start_time) + : true; +} +function report_prime(elapsed_time) { + display(" *** "); + display(elapsed_time); +} + + + + + + mod_timed_prime_test_example + mod_timed_prime_test_solution + +timed_prime_test(10007); +// timed_prime_test(10009); +// timed_prime_test(10037); +// timed_prime_test(100003, 3); +// timed_prime_test(100019, 3); +// timed_prime_test(100043, 3); +// timed_prime_test(1000003, 3); +// timed_prime_test(1000033, 3); +// timed_prime_test(1000037, 3); + + + + + 使用 fast_is_prime 测试接近 1,000,000 附近素数的时间 + 大约为 4 毫秒,是测试接近 1,000 附近素数时间的 4 倍。对比使用 is_prime 达到的 8 毫秒, + 这是更快的。然而,尽管慢了 4 倍,这一事实不能让我们相信它具有比 $\Theta(\log n)$ 更大的增长率, + 因为这需要通过更大的数字进行测试以获得对函数增长更准确的理解。 + + + + + Alyssa P. Hacker 抱怨说我们在编写时做了很多额外的工作 + expmod 。毕竟,她说,既然我们已经知道如何计算指数,我们本可以简单地写 + + expmod + no_extra_work + expt_log_definition + expmod_example + +(define (expmod base exp m) + (remainder (fast-expt base exp) m)) + + +function expmod(base, exp, m) { + return fast_expt(base, exp) % m; +} + + + 她说得对吗? + 这个 + + 过程 + 函数 + + 能否同样用于我们的快速素数测试器?请解释。 + + + 从表面上看,Alyssa 的建议是正确的:她的 + expmod 函数计算 + $\textit{base}^{\textit{exp}}$,然后根据费马测试要求找到其模 $m$ 的余数。 +

+ 然而,对于较大的底数,Alyssa 的方法很快就会遇到限制,因为 JavaScript 使用 64 位表示数字,遵循双精度浮点标准。当数字变得如此之大以至于无法在此标准下精确表示时,结果变得不可靠。更糟糕的是,该方法可能超出了该标准中可表示的最大数字,导致计算错误。 +

+ 然而,对于较小的底数,Alyssa 的方法可能比原始 expmod 函数更快,因为它只会执行一次余数运算。 +
+
+ + + + + Louis Reasoner 很难完成练习。 + 他的 + + + fast-prime? + + + fast_is_prime + + + 测试似乎比他的 + + + prime? + + + is_prime + + + 测试运行得更慢。Louis 叫来他的朋友 Eva Lu Ator 来帮忙。当他们检查 Louis的代码时,他们发现他重写了 + expmod + + 过程 + 函数 + + 使用显式乘法,而不是调用 + square : + + expmod + even_definition + expmod_example + +(define (expmod base exp m) + (cond ((= exp 0) 1) + ((even? exp) + (remainder + (* (expmod base (/ exp 2) m) + (expmod base (/ exp 2) m)) + m)) + (else + (remainder + (* base (expmod base (- exp 1) m)) + m)))) + + +function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? ( expmod(base, exp / 2, m) + * expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; +} + + + “我看不出这样做有什么区别,” + Louis 说。“但我看出来了。” Eva 说。“通过那样编写 + + 过程 + 函数 + , + 你已经将 + $\Theta(\log n)$ 过程转换为 + $\Theta(n)$ 过程。” 请解释。 + + + Eva 是对的:通过计算表达式: + + +(expmod(base, exp / 2, m) * expmod(base, exp / 2, m)) % m + + + 表达式 + expmod(base, exp / 2, m) + 在指数为偶数时的每一步计算中被重复计算,消除了快速幂算法的好处——即在指数为偶数时将指数减半——因此消除了使算法更快的特性。 + + + + + 证明脚注中列出的 + 卡迈克尔数 + 卡迈克尔数确实能欺骗费马测试。也就是说,编写一个 + + 过程 + 函数 + , + 接收一个整数 $n$ 并测试对于每一个 $a < n$, + $a^n$ 是否同余于 + $a$ 模 $n$,并在给定的卡迈克尔数上试验您的 + + 过程 + 函数 + 。 + + + carmichael + example_1.29 + even_definition + square_definition + +function carmichael(n) { + function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? square(expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; + } + function fermat_test(n, a) { + return expmod(a, n, n) === a; + } + function iter(n, i) { + return i === n + ? true + : fermat_test(n, i) + ? iter(n, i + 1) + : false; + } + return iter(n, 2); +} + + + + + + + example_1.29 + carmichael + true + +carmichael(561); +// carmichael(1105); +// carmichael(1729); +// carmichael(2465); +// carmichael(2821); +// carmichael(6601); + + + + + + + + + 费马测试的一种变体,它不能被欺骗,被称为 + 素数米勒–拉宾测试 + 费马素性测试的变体 + 米勒–拉宾素性测试 + 米勒,Gary L. + 拉宾,Michael O. + 米勒–拉宾测试米勒 1976; + 拉宾 1980)。它从 + 费马的费马小定理替代形式 + 费马小定理的一个替代形式开始,该定理指出如果 + $n$ 是素数且 + $a$ 是小于的任意正整数 + $n$ ,那么 $a$ 次幂的 $(n-1)$ 次方同余于 1 模 $n$ 。 要测试一个数字的素性 $n$ 通过米勒–拉宾测试,我们选择一个随机数 $a < n$ 并将其提升为 + $a$$(n-1)$ 次方模 $n$ 使用 + expmod + + 过程。 + 函数。 + + 然而,每当我们在 + expmod ,我们检查是否发现了一个 + 1 的非平凡平方根模$n$, + 也就是不等于 1 或的一个数字 $n-1$ 其平方等于 1 模 $n$ 。可以证明,如果存在这样一个 1 的非平凡平方根,那么 + $n$ 不是素数。也可以证明如果 $n$ 是一个不是素数的奇数,那么至少对半数的数字 $a < n$ ,计算 $a^{n-1}$ 以这种方式将揭示 1 的非平凡平方根模 $n$ 。 + (这就是米勒–拉宾测试无法被欺骗的原因。)修改 + expmod + + 过程 + 函数 + + 以发出信号如果它发现 1 的非平凡平方根,并使用它来 + 实现米勒–拉宾测试,通过一个 + + 过程 + 函数 + + 类似于 + + fermat-test. + fermat_test. + + 检查您的 + + 过程 + 函数 + 通过测试各种已知的素数和非素数。提示:一种方便的方法是生成一个 +expmod 信号是让它返回0。 + + + + miller_rabin_test + example_1.30 + even_definition + square_definition + +function random(n) { + return math_floor(math_random() * n); +} +function miller_rabin_test(n) { + function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? square(trivial_test(expmod(base, + exp / 2, + m), + m)) + % m + : (base * expmod(base, exp - 1, m)) + % m; + } + function trivial_test(r, m) { + return r === 1 || r === m - 1 + ? r + : square(r) % m === 1 + ? 0 + : r; + } + function try_it(a) { + return expmod(a, n - 1, n) === 1; + } + return try_it(1 + random(n - 1)); +} +function do_miller_rabin_test(n, times) { + return times === 0 + ? true + : miller_rabin_test(n) + ? do_miller_rabin_test(n, times - 1) + : false; +} + + + + + + + example_1.30 + miller_rabin_test + true + +do_miller_rabin_test(104743, 1000); + + + + + + 素数测试 +
diff --git a/xml/cn/chapter1/section3/section3.xml b/xml/cn/chapter1/section3/section3.xml new file mode 100644 index 000000000..0bd6b0faa --- /dev/null +++ b/xml/cn/chapter1/section3/section3.xml @@ -0,0 +1,139 @@ +
+ + 形成高阶抽象 + + 过程 + 函数 + + + + + + + + +我们已经看到 + + 过程 + 函数 + +实际上是描述对数字进行复合操作的抽象,与特定数字无关。例如,当我们 + + 声明 + + + cube + cube_definition + cube_example + +(define (cube x) (* x x x)) + + +function cube(x) { + return x * x * x; +} + + + + cube_example + 27 + cube_definition + +(cube 3) + + +cube(3); + + +我们并不是在讨论某个特定数字的立方,而是关于获得任何数字的立方的方法。当然,我们可以不通过 + + 定义这个过程, + 声明这个函数, + +来实现,始终写出这样的表达式 + + +(* 3 3 3) +(* x x x) +(* y y y) + + +3 * 3 * 3 +x * x * x +y * y * y + + +并且从未提及 +cube +明确这一点。这 +会使我们处于严重劣势,迫使我们始终在语言中作为原语的特定操作(在这种情况下是乘法)层面上工作,而不是在更高层次的操作方面。我们的程序可以计算立方,但我们的语言缺乏表达立方概念的能力。我们应该从强大的编程语言中要求的一个功能是通过为常见模式赋予名称来构建抽象的能力,接着直接在抽象方面工作。 + + 过程 + 函数 + +提供了这种能力。这就是为什么几乎所有编程语言都包括用于 + + 定义过程。 + 声明函数。 + + + +即使在数值处理方面,如果我们仅限于 + + 过程 + 函数 + +其参数必须是数字,我们的抽象创建能力也将受到严重限制。通常,相同的编程模式将与多种不同的 + + 过程。 + 函数。 + +一起使用。为了将这些模式表达为概念,我们需要构造 + + 过程 + 函数 + +能够接受 + + 过程 + 函数 + +作为参数或返回 + + 过程 + 函数 + +作为值。 + + 过程 + 函数 + +用于操作 + + 过程 + 函数 + +的称为 +高阶 过程函数 +高阶 过程。函数。 +本节展示了高阶 + + 过程 + 函数 + +如何作为强大的抽象机制,极大地增强我们语言的表达能力。 + + + + &subsection1.3.1; + + + &subsection1.3.2; + + + &subsection1.3.3; + + + &subsection1.3.4; + +
diff --git a/xml/cn/chapter1/section3/subsection1.xml b/xml/cn/chapter1/section3/subsection1.xml new file mode 100644 index 000000000..e561d68f0 --- /dev/null +++ b/xml/cn/chapter1/section3/subsection1.xml @@ -0,0 +1,1112 @@ + + + + Procedures + Functions + + as Arguments + + + + higher-order proceduresfunctionsprocedurefunction as argument + argumentas argument + + Consider the following three + + procedures. + functions. + + The first computes the sum of the integers from + a through b: + + sum_integers + sum_integers_definition + sum_integers_example + 55 + +(define (sum-integers a b) + (if (> a b) + 0 + (+ a (sum-integers (+ a 1) b)))) + + +function sum_integers(a, b) { + return a > b + ? 0 + : a + sum_integers(a + 1, b); +} + + + + sum_integers_example + +(sum-integers 1 10) + + + 55 + + +sum_integers(1, 10); + + + The second computes the sum of the cubes of the integers in the given range: + + sum_cubes + sum_cubes_definition + cube_definition + sum_cubes_example + 775 + +(define (sum-cubes a b) + (if (> a b) + 0 + (+ (cube a) (sum-cubes (+ a 1) b)))) + + +function sum_cubes(a, b) { + return a > b + ? 0 + : cube(a) + sum_cubes(a + 1, b); +} + + + + sum_cubes_example + +(sum-cubes 3 7) + + +sum_cubes(3, 7); + + + The 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):This series, + $\pi$ (pi)piLeibnizs series for + Leibniz, Baron Gottfried Wilhelm vonseries for $\pi$ + usually written in the equivalent form + $\frac {\pi}{4} = 1-\frac{1} + {3}+\frac{1}{5}-\frac{1}{7}+\cdots$, + is due to Leibniz. Well see how to use this as the basis for some + fancy numerical tricks in + section. + + pi_sum + pi_sum_definition + pi_sum_example + 3.139592655589783 + +(define (pi-sum a b) + (if (> a b) + 0 + (+ (/ 1.0 (* a (+ a 2))) + (pi-sum (+ a 4) b)))) + + +function pi_sum(a, b) { + return a > b + ? 0 + : 1 / (a * (a + 2)) + pi_sum(a + 4, b); +} + + + + + + pi_sum_example + +(* 8 (pi-sum 1 1000)) + + +8 * pi_sum(1, 1000); + + + + + + These three + + procedures + functions + + clearly share a common underlying pattern. They are for the most part + identical, differing only in the name of the + + procedure, + function, + + the function of a used to compute the term to + be added, and the function that provides the next value of + a. We could generate each of the + + procedures + functions + + by filling in slots in the same template: + + + +(define ($\langle name \rangle$ a b) + (if (> a b) + 0 + (+ ($\langle term \rangle$ a) + ($\langle name \rangle$ ($\langle next \rangle$ a) b)))) + + +function name(a, b) { + return a > b + ? 0 + : term(a) + name(next(a), b); +} + + + + + + The presence of such a common pattern is strong evidence that there is a + useful + abstractioncommon pattern and + abstraction waiting to be brought to the surface. Indeed, + mathematicians long ago identified the abstraction of + series, summation of + summation of a series + sigma$\Sigma$ (sigma) notation + 0s$\Sigma$ (sigma) notation + summation of a series and invented sigma + notation, for example + + \[\begin{array}{lll} + \displaystyle\sum_{n=a}^{b}\ f(n)&=&f(a)+\cdots+f(b) + \end{array}\] + + to express this concept. The power of sigma notation is that it allows + mathematicians to deal with the concept of summation itself rather than only + with particular sumsfor example, to formulate general results about + sums that are independent of the particular series being summed. + + + + Similarly, as program designers, we would like our language to be powerful + enough so that we can write a + + procedure + function + + that expresses the concept of summation itself rather than only + + procedures + functions + + that compute particular sums. We can do so readily in our + + procedural + functional + + language by taking the common template shown above and transforming the + slots into + + formal parameters: + + parameters: + + + + sum + sum_definition + +(define (sum term a next b) + (if (> a b) + 0 + (+ (term a) + (sum term (next a) next b)))) + + +function sum(term, a, next, b) { + return a > b + ? 0 + : term(a) + sum(term, next(a), next, b); +} + + + Notice that sum takes as its arguments the + lower and upper bounds a and + b together with the + + procedures + functions + + term and next. + We can use sum just as we would any + + procedure. + function. + + For example, we can use it (along with a + + procedure + function + + inc that increments its argument by 1) to define + + sum-cubes: + + sum_cubes: + + + inc_definition + +(define (inc n) (+ n 1)) + + +function inc(n) { + return n + 1; +} + + + + inc + sum_cubeswith higher-order functionshigher + sum_example + cube_definition + sum_definition + sum_example_example + 3025 + +(define (inc n) (+ n 1)) + +(define (sum-cubes a b) + (sum cube a inc b)) + + +function inc(n) { + return n + 1; +} +function sum_cubes(a, b) { + return sum(cube, a, inc, b); +} + + + Using this, we can compute the sum of the cubes of the integers from 1 to 10: + + sum_example_example + sum_example + +(sum-cubes 1 10) + + +3025 + + +sum_cubes(1, 10); + + +3025 + + + With the aid of an identity + + procedure + function + + to compute the term, we can define + + sum-integers + sum_@integers + + in terms of sum: + + identity + identity + identity_example + +(define (identity x) x) + + +function identity(x) { + return x; +} + + + + identity_example + +(identity 42) + + +identity(42); + + + + sum_integerswith higher-order functionshigher + sum_integers_definition2 + sum_definition + inc_definition + identity + sum_integers_example2 + 55 + +(define (sum-integers a b) + (sum identity a inc b)) + + +function sum_integers(a, b) { + return sum(identity, a, inc, b); +} + + + Then we can add up the integers from 1 to 10: + + sum_integers_example2 + sum_integers_definition2 + +(sum-integers 1 10) + + +55 + + +sum_integers(1, 10); + + +55 + + + We can also + + define pi-sum + define pi_sum + + + in the same way:Notice that we have used block structure + (section) to embed the + + definitions of pi-next + declarations of pi_next + + + and + pi-term + pi_term + + within + + pi-sum, + pi_sum, + + since these + + procedures + functions + + are unlikely to be useful for any other purpose. We will see how to get rid + of them altogether in section. + + pi_sumwith higher-order functionshigher + pi_sum_definition2 + sum_definition + pi_sum_example2 + 3.139592655589783 + +(define (pi-sum a b) + (define (pi-term x) + (/ 1.0 (* x (+ x 2)))) + (define (pi-next x) + (+ x 4)) + (sum pi-term a pi-next b)) + + +function pi_sum(a, b) { + function pi_term(x) { + return 1 / (x * (x + 2)); + } + function pi_next(x) { + return x + 4; + } + return sum(pi_term, a, pi_next, b); +} + + + Using these + + procedures, + functions, + + we can compute an approximation to $\pi$: + + pi_sum_example2 + pi_sum_definition2 + +(* 8 (pi-sum 1 1000)) + + +3.139592655589783 + + +8 * pi_sum(1, 1000); + + +3.139592655589783 + + + + + + Once we have sum, we can use it as a building + block in formulating further concepts. For instance, the + definite integral + definite integral of a function $f$ between the + limits $a$ and $b$ can + be approximated numerically using the formula + + \[ + \begin{array}{lll} + \displaystyle\int_{a}^{b}f & = & + \left[\,f\!\left( a+\dfrac{dx}{2} \right)\,+\,f\!\left(a+dx+\dfrac{dx}{2} + \right)\,+\,f\!\left( a+2dx+\dfrac{dx}{2}\right)\,+\,\cdots + \right] dx + \end{array} + \] + + for small values of $dx$. We can express this + directly as a + + procedure: + function: + + + integral + integral_definition + sum_definition + integral_example + 0.24998750000000042 + +(define (integral f a b dx) + (define (add-dx x) (+ x dx)) + (* (sum f (+ a (/ dx 2)) add-dx b) + dx)) + + +function integral(f, a, b, dx) { + function add_dx(x) { + return x + dx; + } + return sum(f, a + dx / 2, add_dx, b) * dx; +} + + + + + integral_example + cube_definition + integral_definition + +(integral cube 0 1 0.01) + + +0.24998750000000042 + + +integral(cube, 0, 1, 0.01); + + +0.24998750000000042 + + + + + integral_example2 + cube_definition + integral_definition + + +(integral cube 0 1 0.001) + + +integral(cube, 0, 1, 0.001); + + +// dare to try 0.001 with in browser? +integral(cube, 0, 1, 0.002); + + +0.249999875000001 + + + + (The exact value of the integral of cube between + 0 and 1 is 1/4.) + + + + + Simpsons Rule is a more accurate method of numerical integration than + +Simpsons Rule for numerical integration + the method illustrated above. Using Simpsons Rule, the integral of a + function $f$ between + $a$ and $b$ is + approximated as + + \[ + \frac{h}{3}[ y_0 +4y_1 +2y_2 +4y_3 +2y_4 +\cdots+2y_{n-2} + +4y_{n-1}+y_n ] + \] + + where $h=(b-a)/n$, for some even integer + $n$, and + $y_k =f(a+kh)$. (Increasing + $n$ increases the accuracy of the approximation.) + + Define a procedure + Declare a function + + that takes as arguments $f$, + $a$, $b$, and + $n$ and returns the value of the integral, + computed using Simpsons Rule. Use your + + procedure + function + + to integrate cube between 0 and 1 (with + $n=100$ and $n=1000$), + and compare the results to those of the integral + + procedure + function + + shown above. + + + simpsons_definition + cube_definition + sum_definition + example_1.31 + 0.24999999999999992 + +function inc(k) { + return k + 1; +} +function simpsons_rule_integral(f, a, b, n) { + function helper(h) { + function y(k) { + return f((k * h) + a); + } + function term(k) { + return k === 0 || k === n + ? y(k) + : k % 2 === 0 + ? 2 * y(k) + : 4 * y(k); + } + return sum(term, 0, inc, n) * (h / 3); + } + return helper((b - a) / n); +} + + + + + + + example_1.31 + cube_definition + simpsons_definition + +simpsons_rule_integral(cube, 0, 1, 100); + + + + + + + definite integral + + + + The + sumiterative version + sum + + procedure + function + + above generates a linear recursion. The + + procedure + function + + can be rewritten so that the sum is performed iteratively. Show how to do + this by filling in the missing expressions in the following + + definition: + declaration: + + + +(define (sum term a next b) + (define (iter a result) + (if ?? + ?? + (iter ?? ??))) + (iter ?? ??)) + + +function sum(term, a, next, b) { + function iter(a, result) { + return ?? + ? ?? + : iter(??, ??); + } + return iter(??, ??); +} + + + + + sum_example_iter + cube_definition + sum_example_iter_example + 3025 + +function sum(term, a, next, b) { + function iter(a, result) { + return a > b + ? result + : iter(next(a), result + term(a)); + } + return iter(a, 0); +} + + + + + sum_example_iter_example + +(define (inc n) (+ n 1)) + +(define (sum-cubes a b) + (sum cube a inc b)) +(sum-cubes 1 10) + + +function inc(n) { + return n + 1; +} +function sum_cubes(a, b) { + return sum(cube, a, inc, b); +} +sum_cubes(1, 10); + + + + + + + + +
    +
  1. + The + sum + + procedure + function + + is only the simplest of a vast number of similar abstractions that can + be captured as higher-order + proceduresfunctions.The intent of exercises is to demonstrate the expressive + power that is attained by using an appropriate abstraction to + consolidate many seemingly disparate operations. However, though + accumulation and filtering are elegant ideas, our hands are somewhat + tied in using them at this point since we do not yet have data + structures to provide suitable means of combination for these + abstractions. We will return to these ideas in + section when + we show how to use sequences as interfaces for combining + filters and accumulators to build even more powerful abstractions. We + will see there how these methods really come into their own as a + powerful and elegant approach to designing programs. + Write an analogous + + procedure + function + + called + product + product that returns the product of + the values of a function at points over a given range. Show how to + define + factorialwith higher-order functions + factorial in terms of + product. Also use + product to compute approximations to + pi$\pi$ (pi)Walliss formula for + $\pi$ using the formulaThis formula + was discovered by the seventeenth-century + English mathematician + Wallis, John + John Wallis. + + \[ + \begin{array}{lll} + \dfrac{\pi}{4} & = & \dfrac{2 \cdot 4\cdot 4\cdot 6\cdot 6\cdot 8\cdots}{3\cdot + 3\cdot 5\cdot 5\cdot 7\cdot 7\cdots} + \end{array} + \] + +
  2. +
  3. + If your product + + procedure + function + + generates a recursive process, write one that generates an iterative + process. If it generates an iterative process, write one that generates + a recursive process. +
  4. +
+ + + + factorial_product + +function factorial(n) { + function term(i) { + return i; + } + function next(i) { + return i + 1; + } + return product_r(term, 1, next, n); +} + + + + pi_product + +//pi product +function pi(n) { + function is_odd(i) { + return i % 2 === 1; + } + function term(i) { + return is_odd(i) + ? (i + 1) / (i + 2) + : (i + 2) / (i + 1); + } + function next(i) { + return i + 1; + } + return 4 * (product_i(term, 1.0, next, n)); +} + + + + + product_r + factorial_product + pi_product + factorial_product_example + 120 + +//recursive process +function product_r(term, a, next, b) { + return a > b + ? 1 + : term(a) * product_r(term, next(a), next, b); +} + +//iterative process +function product_i(term, a, next, b) { + function iter(a, result) { + return a > b + ? result + : iter(next(a), term(a) * result); + } + return iter(a, 1); +} + + + + + + + factorial_product_example + factorial_product + pi_product + +factorial(5); +// pi(17); + + + + + +
+ + +
    +
  1. + Show that sum and + product + sumas accumulationaccumulation + productas accumulationaccumulation + (exercise) are both special cases of a + still more general notion called + accumulate + accumulate + that combines a collection of terms, using some general accumulation + function: + + + (accumulate combiner null-value term a next b) + + + accumulate(combiner, null_value, term, a, next, b); + + + + Accumulate + + The function accumulate + + + takes as arguments the same term and range specifications as + sum and + product, together with a + combiner + + procedure + function + + (of two arguments) that specifies how the current term is to be combined + with the accumulation of the preceding terms and a + + null-value + null_value + + + that specifies what base value to use when the terms run out. Write + accumulate and show how + sum and product + can both be + + defined + declared + + as simple calls to accumulate. +
  2. +
  3. + If your accumulate + + procedure + function + + generates a recursive process, write one that generates an iterative + process. If it generates an iterative process, write one that generates + a recursive process. +
  4. +
+ + + + factorial_product + pi_product + example_accumulate_numbers + 120 + +//recursive process +function accumulate_r(combiner, null_value, term, a, next, b) { + return a > b + ? null_value + : combiner(term(a), + accumulate_r(combiner, + null_value, + term, next(a), next, b)); +} +function sum_r(term, a, next, b) { + function plus(x, y) { + return x + y; + } + return accumulate_r(plus, 0, term, a, next, b); +} +function product_r(term, a, next, b) { + function times(x, y) { + return x * y; + } + return accumulate_r(times, 1, term, a, next, b); +} + +//iterative process +function accumulate_i(combiner, null_value, term, a, next, b) { + function iter(a, result) { + return a > b + ? result + : iter(next(a), combiner(term(a), result)); + } + return iter(a, null_value); +} +function sum_i(term, a, next, b) { + function plus(x, y) { + return x + y; + } + return accumulate_i(plus, 0, term, a, next, b); +} +function product_i(term, a, next, b) { + function times(x, y) { + return x * y; + } + return accumulate_i(times, 1, term, a, next, b); +} + + + + + + example_accumulate_numbers + factorial_product + +factorial(5); + + + + + +
+ + + You can + obtain an even more general version of + filtered_accumulate + accumulate + (exercise) + by introducing the notion of a + filter + filter on the terms to be combined. That is, combine only those + terms derived from values in the range that satisfy a specified condition. + The resulting + filtered_accumulate + + filtered-accumulate + filtered_@accumulate + + + abstraction takes the same arguments as accumulate, together with an + additional predicate of one argument that specifies the filter. Write + + filtered-accumulate + filtered_@accumulate + + + as a + + procedure. + function. + + Show how to express the following using + + + filtered-accumulate: + + + filtered_@accumulate: + + +
    +
  1. + the sum of the squares of the prime numbers in the interval + $a$ to $b$ + (assuming that you have an + + + prime? + is_prime + + + predicate already written) +
  2. +
  3. + the product of all the positive integers less than + $n$ + that are + relatively prime + relatively prime to$n$ (i.e., + all positive integers $i < n$ such that + $\textrm{GCD}(i,n)=1$). +
  4. +
+ + + + + smallest_divisor_definition + prime_definition + square_definition + inc_definition + gcd_definition + example_filtered_accumulate + 74 + +function filtered_accumulate(combiner, null_value, + term, a, next, b, filter) { + return a > b + ? null_value + : filter(a) + ? combiner(term(a), + filtered_accumulate(combiner, null_value, + term, next(a), next, + b, filter)) + : filtered_accumulate(combiner, null_value, + term, next(a), next, + b, filter); +} + + + + + + example_filtered_accumulate + prime_definition + square_definition + inc_definition + gcd_definition + +function prime_squares_sum(a, b) { + function plus(x, y) { + return x + y; + } + return filtered_accumulate(plus, 0, + square, a, inc, b, + is_prime); +} +function relative_prime_product(n) { + function times(x, y) { + return x * y; + } + function identity(x) { + return x; + } + function test(i) { + return gcd(i, n) === 1; + } + return filtered_accumulate(times, 1, + identity, 1, inc, n, + test); +} + +prime_squares_sum(4, 10); // 74 +// relative_prime_product(8); // 105 + + + + + +
+ higher-order proceduresfunctionsprocedurefunction as argument + argumentas argument +
diff --git a/xml/cn/chapter1/section3/subsection2.xml b/xml/cn/chapter1/section3/subsection2.xml new file mode 100644 index 000000000..c72c4bcf4 --- /dev/null +++ b/xml/cn/chapter1/section3/subsection2.xml @@ -0,0 +1,964 @@ + + + + 使用 Lambda 构造程序 + 使用 Lambda 表达式构造函数 + + + + + + + + + + In using sum as in + section, + it seems terribly awkward to have to define trivial procedures such as + pi-term and + pi-next just so we can use them as + arguments to our higher-order procedure. + Rather than define pi-next and + pi-term, it would be more convenient to + have a way to directly specify the procedure that returns its + input incremented by 4 and the procedure that returns the + reciprocal of its input times its input plus 2. We can do this + by introducing the special form lambda, + which creates procedures. Using lambda we + can describe what we want as + + +(lambda (x) (+ x 4)) + + + and + + +(lambda (x) (/ 1.0 (* x (+ x 2)))) + + + + +在使用 sum 时,如 + 第 节,声明像 + pi_term 和 + pi_next 这样的简单函数为了能将它们作为 + 高阶函数的参数,似乎非常尴尬。与其声明 + pi_next 和 + pi_term,更方便的方式是 + 直接指定 返回输入加 4 的函数返回其输入的倒数乘以输入加 2 的函数。 我们可以通过引入 lambda 表达式 作为创建函数的语法形式来实现这一点。 + 使用 lambda 表达式,我们可以将我们想要的描述为 + + +x => x + 4 + + + 和 + + +x => 1 / (x * (x + 2)) + + + + + + + 那么我们的 + pi-sum + 过程可以表示为 + + + 那么我们可以将我们的 + pi_sum + 函数表示为 + + + 而无需 + + 定义任何辅助过程,如 + 声明任何辅助函数: + + + pi_sumlambdawith lambda expression + pi_sum_definition3 + 3.139592655589783 + sum_definition + pi_sum_example + 3.139592655589783 + +(define (pi-sum a b) + (sum (lambda (x) (/ 1.0 (* x (+ x 2)))) + a + (lambda (x) (+ x 4)) + b)) + + +function pi_sum(a, b) { + return sum(x => 1 / (x * (x + 2)), + a, + x => x + 4, + b); +} + + + + + 再次使用 + + lambda, + lambda 表达式, + + 我们可以编写 integral + + 过程 + 函数 + + 而无需 + + 定义辅助过程 + 声明辅助函数: + + + add-dx: + add_dx: + + + integral_example_2 + +(integral cube 0 1 0.01) + + +integral(cube, 0, 1, 0.01); + + + + integrallambda与 lambda 表达式 + integral_definition2 + sum_definition + cube_definition + integral_example_2 + 0.24998750000000042 + +(define (integral f a b dx) + (* (sum f + (+ a (/ dx 2.0)) + (lambda (x) (+ x dx)) + b) + dx)) + + +function integral(f, a, b, dx) { + return sum(f, + a + dx / 2, + x => x + dx, + b) + * + dx; +} + + + + + + + + In general, lambda is used to create + creating with lambdalambda expression + lambda expression + syntactic formslambda expression + lambda expressionfunction declaration vs. + function declarationlambda expression vs. + 0a1=> + procedures in the same way as define, + except that + procedureanonymous + no name is specified for the procedure: + + + (lambda ($formal-parameters$) $body$) + + + The resulting procedure is just as much a procedure + as one that is created using define. + The only difference is that it has not been associated with any name in the + environment. + + +一般来说,lambda 表达式用于以与函数声明相同的方式创建函数, + 使用 lambdalambda 表达式 + lambda 表达式 + 语法形式lambda 表达式 + lambda 表达式函数声明与 + 函数声明lambda 表达式与 + 0a1=> + 匿名 +只不过没有为该函数指定名称,并且省略了 + return关键字和大括号 + (如果只有一个参数, + 括号lambda 参数包围 lambda 表达式的参数 + 参数列表周围的括号也可以被省略,如我们所见的例子一样)。在第 节中,我们将扩展 + lambda 表达式的语法,允许区块作为主体,而不仅仅是表达式,如在函数声明语句中。 + + +(parameters) => expression + + + 得到的函数与使用函数声明语句创建的函数一样。 + 函数声明lambda 表达式与 + lambda 表达式函数声明与 + 唯一的区别在于它没有与环境中的任何名称关联。 + + + + 事实上, + 我们认为 + + + plus4_definition_1 + plus4_example + +(define (plus4 x) (+ x 4)) + + +function plus4(x) { + return x + 4; +} + + + + plus4_example + +(plus4 3) + + +plus4(3); + + + + 相当于 + + 等同于在 JavaScript 中,这两个版本之间存在微妙的差异: + 函数声明语句会被自动“提升”(移动)到周围块的起始位置, + 或者如果它出现在任何块之外,则移动到程序的开头,而常量声明则不会被移动。 + 用函数声明声明的名称可以通过赋值被重新分配(第 节),而用常量声明声明的名称则不能。在本书中,我们避免这些特性,并将 + 函数声明视为与对应的常量声明相等。 + + + lambda 表达式函数声明 vs. + 函数声明lambda 表达式 vs. + + plus4_definition_2 + plus4_example + +(define plus4 (lambda (x) (+ x 4))) + + +const plus4 = x => x + 4; + + + + + 我们可以这样读取 lambda 表达式: + + +( lambda (x) (+ x 4) +// 读取为: ^ ^ ^ ^ ^ +// 为 x 的过程,它将 x 和 4 相加 + + + + + 我们可以这样读取 lambda 表达式: + + + +// x => x + 4 +// ^ ^ ^ ^ ^ +// 为 x 的函数,结果是值加 4 + + + + + + + +\begin{flushleft}\normalcodesize +\begin{tabular}{@{}ccccc} + \tt x & \tt => & \tt x & \tt + & \tt 4 \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 参数 \small\tt x & \normalsize 的函数,结果是 & \normalsize 值 & \normalsize 加上 & \normalsize 4. \\ +\end{tabular} +\end{flushleft} + + + + +\begin{flushleft}\normalcodesize +\begin{tabular}{@{}cccccc@{\quad\qquad}c} + \tt (lambda & \tt (x) & \tt ( & \tt + & \tt x & \tt 4 & \tt ) \\ + $\Big\uparrow$ & $\Big\uparrow$ & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize 参数 \small\tt x 的过程 & \normalsize 将 & \normalsize x 加上 & \small\tt 4. \ +\end{tabular} +\end{flushleft} + + + + + + + + + + +像任何具有 + + 过程 + 函数 + + + + 组合lambdalambda 表达式作为运算符 + lambda表达式运算符作为组合的运算符 + + + lambda 表达式函数作为应用的函数表达式 + 函数表达式作为 lambda 表达式 + + + 作为其值,一个 + + lambda + lambda + + 表达式可以在应用的函数表达式中用作 + 组合,例如 + + square_definition + 12 + +((lambda (x y z) (+ x y (square z))) + 1 2 3) + + +12 + + +((x, y, z) => x + y + square(z))(1, 2, 3); + + +12 + + +或者更一般地,在任何我们通常会使用 + 过程函数 + 名称的上下文中。对于学习 + + Lisp + JavaScript + + 的人来说,如果使用一个比 + + + lambda更明显的名称,例如 + make-procedure, + + + lambda 表达式,例如 函数定义, + + + 那样会更清晰,也更不令人生畏。但是这一约定 + + + 深入人心。 + + + 非常深入人心,不仅在LispScheme 中,也在JavaScriptJava 和其他语言中,这无疑部分归因于本书的Scheme版的影响。 + Scheme使用 lambda + + + 该符号来自于 + 0l$\lambda$ 演算 (lambda 演算) + lambda$\lambda$ 演算 (lambda 演算) + $\lambda$演算,阿隆佐·丘奇(Alonzo Church)在1941年引入的数学形式主义。丘奇发展了 + $\lambda$演算,以为研究 + 函数和函数应用的概念提供严格的基础。$\lambda$演算已成为对编程语言语义的数学研究的基本工具。 + + 请注意 => + 的优先级 + 优先级lambda 表达式的 + lambda 表达式的优先级 + 低于函数应用,因此这里需要使用 + 括号lambda 表达式包围 lambda 表达式 + 包围 lambda 表达式。 + + + + + 使用 + + let 创建局部变量 + + const 创建局部名称 + + + + + 自由名 + +另一个 + + + lambda 的用途是在创建局部变量。 + + + lambda 表达式的用途是在创建局部名称。 + + + + + 我们在程序中常常需要局部变量, + 而这些变量不同于作为正式参数的变量。 + + + 我们在函数中常常需要局部名称, + 而这些名称不同于作为参数的名称。 + + + 例如,假设我们希望计算函数 + + \[\begin{array}{lll} + f(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y) + \end{array}\] + + 我们可以将其表达为 + + \[\begin{array}{rll} + a &=& 1+xy\\ + b &=& 1-y\\ + f(x, y) &= &x a^2 +y b + a b + \end{array}\] + +在编写一个 + + 过程 + 函数 + + 来计算 +$f$ +我们希望不仅包括 + + + 局部变量 + + + 局部名称 + + +$x$ +和 +$y$ +但也包括中间量的名称,例如 +$a$ +和 +$b$ +实现这一点的一种方法是使用一个辅助 + + 过程来绑定局部变量: + 函数来绑定局部名称: + + + f_helper_definition + square_definition + f_helper_example + 456 + +(define (f x y) + (define (f-helper a b) + (+ (* x (square a)) + (* y b) + (* a b))) + (f-helper (+ 1 (* x y)) + (- 1 y))) + + +function f(x, y) { + function f_helper(a, b) { + return x * square(a) + y * b + a * b; + } + return f_helper(1 + x * y, 1 - y); +} + + + + f_helper_example + +(f 3 4) + + +f(3, 4); + + + + +当然,我们可以使用一个 + + lambda + lambda + + 表达式来指定一个匿名 + + 用于绑定我们的局部变量的过程。 + 用于绑定我们的局部名称的函数。 + + 然后 + + + f 的主体 + + + 函数体 + + + 就成为对该 + + 过程的单次调用: + 函数: + + + f_helper_definition2 + square_definition + f_2_helper_example + 456 + +(define (f x y) + ((lambda (a b) + (+ (* x (square a)) + (* y b) + (* a b))) + (+ 1 (* x y)) + (- 1 y))) + + +function f_2(x, y) { + return ( (a, b) => x * square(a) + y * b + a * b + )(1 + x * y, 1 - y); +} + + + + f_2_helper_example + +f_2(3, 4); + + + + + This construct is so useful that there is a special form called + let to make its use more convenient. + Using let, the + f procedure could be written as + + f_helper_definition3 + square_definition + f_helper_example + +(define (f x y) + (let ((a (+ 1 (* x y))) + (b (- 1 y))) + (+ (* x (square a)) + (* y b) + (* a b)))) + + + + + +JavaScript 的 const 不能被解释为 lambda 表达式/应用的简写形式。因此,我们使用“更方便的方式”这一较弱的表述;因此在 JavaScript 中这一节要比在 Scheme 中短很多。 +一种更方便的方式是在函数主体内使用常量声明来声明局部名称。使用 const,函数 + + + f + + +可以写成 + + 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); + + +在一个块内用 const 声明的名称,其作用域为立即包围的块的主体。 + + 声明在前使用名称 + 请注意,在块中声明的名称在声明完全计算之前不能使用,无论是否在块之外声明了同名变量。因此,在下面的程序中,尝试使用在全局作用域中声明的 a 为在 f 中声明的 b 计算提供值是行不通的。 + + h_error_example + +const a = 1; +function f(x) { + const b = a + x; + const a = 5; + return a + b; +} +f(10); + + + 这个程序导致错误,因为 aa + x 中在其声明被计算之前就被使用了。我们将在学习更多关于求值的内容后,回到这个程序,见第 节(练习)。 +$^,$ 代换模型可以扩展为这样表述:对于常量声明,在 = 后面的表达式值替代 = 前面的名称,在块主体的其余部分中(在声明之后),类似于在函数应用的求值中用参数替代实参。 + 局部名称 + + + + + + let + special forms (those marked ns are not in the IEEE Scheme standard)letlet + + + The general form of a let expression is + + +(let (($var_1$ $exp_1$) + ($var_2$ $exp_2$) + $\vdots$ + ($var_n$ $exp_n$)) + $body$) + + + which can be thought of as saying + + \begin{array}{ll} + \mbox{let}\ &var_1\ \mbox{have the value}\ exp_\ \mbox{and}\\ + &var_2\ \mbox{have the value}\ exp_2\ \mbox{and}\\ + &\vdots\\ + &var_n\ \mbox{have the value}\ exp_n\\ + \mbox{in}\ & body + \end{array} + + + + The first part of the let expression is a + list of name-expression pairs. When the let + is evaluated, each name is associated with the value of the + corresponding expression. The body of the + let is evaluated with these names bound as + local variables. The way this happens is that the + let expression is interpreted as an + alternate syntax for + + +((lambda ($var_1$ $\ldots$ $var_n$) + $body$) + $exp_1$ + $\vdots$ + $exp_n$) + + + No new mechanism is required in the interpreter in order to + provide local variables. A + syntactic sugarletlet as + letsyntacticas syntactic sugar + let expression is simply syntactic sugar for + the underlying lambda application. + letscope of variables + scope of a variableletin let + We can see from this equivalence that the scope of a variable specified + by a let expression is the body of the + let. This implies that: +
    +
  • + Let allows one to bind variables as + locally as possible to where they are to be used. For example, if + the value of x is 5, the value of the + expression + + x_definition + +(define x 5) + + + + + x_definition + +(+ (let ((x 3)) + (+ x (* x 10))) + x) + + + is 38. Here, the x in the body of the + let is 3, so the value of the + let expression is 33. On the other + hand, the x that is the second argument + to the outermost + is still5. +
  • +
  • + The variables values are computed outside the + let. This matters when the expressions + that provide the values for the local variables depend upon + variables having the same names as the local variables themselves. + For example, if the value of x is 2, + the expression + + x_definition2 + +(define x 2) + + + + + x_definition2 + +(let ((x 3) + (y (+ x 2))) + (* x y)) + + + will have the value 12 because, inside the body of the + let, x + will be 3 and y will be 4 (which is the + outer x plus 2). +
  • +
+
+ + Sometimes we can use internal definitions to get the same effect as with + internal definitionletlet vs. + letinternal definition vs. + let. For example, we could have defined + the procedure f above as + + square_definition + f_helper_example + +(define (f x y) + (define a (+ 1 (* x y))) + (define b (- 1 y)) + (+ (* x (square a)) + (* y b) + (* a b))) + + + We prefer, however, to use let in + situations like this and to use internal + define + only for internal procedures.Understanding internal + definitions well enough to be sure a program means what we intend it to + mean requires a more elaborate model of the evaluation process than we + have presented in this chapter. The subtleties do not arise with + internal definitions of procedures, however. We will return to this + issue in sections + and, after + we learn more about the evaluation of blocks. + local name + +
+
+ + + + + +这是引入条件语句的完美时机,条件语句在本书的其余部分中当然被大量使用。它们的适当代换模型可能有点技术性,并且解释起来相当没有意义,因此我们将代换模型的讨论限制在条件表达式上。 + + + 条件语句 + + + + + 我们已经看到,在函数声明中声明局部名称通常是有用的。当函数变得庞大时,我们应该将名称的作用域保持尽可能窄。例如,考虑练习中的 expmod。 + + expmod_definition_2 + even_definition + expmod_example_2 + 4 + +function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? ( expmod(base, exp / 2, m) + * expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; +} + + +这个函数效率低下,因为它包含两个相同的调用: + + expmod_example_2 + even_definition + expmod_definition_2 + +expmod(base, exp / 2, m); + + +expmod(4, 3, 5); + + +这个函数在此例中不必要地低效,因为它包含两个相同的调用: + + even_definition + expmod_example + +function expmod(base, exp, m) { + const half_exp = expmod(base, exp / 2, m); + return exp === 0 + ? 1 + : is_even(exp) + ? (half_exp * half_exp) % m + : (base * expmod(base, exp - 1, m)) % m; +} + + + +条件语句的必要性 + 这将使该函数不仅低效,实际上还会导致不终止!问题在于常量声明出现在条件表达式之外,这意味着即使在满足基本情况 exp === 0 时,它也会被执行。为避免这种情况,我们提供 + 条件语句 + 语法形式条件语句 + if (关键字)if + else (关键字)else + 关键字ifif + 关键字elseelse + 谓词条件条件语句的 + 条件语句谓词、后续部分和替换部分 + 条件语句,并允许返回语句出现在语句的分支中。使用条件语句,我们可以将函数 + expmod 编写如下: + + even_definition + expmod_example + 4 + +function expmod(base, exp, m) { + if (exp === 0) { + return 1; + } else { + if (is_even(exp)) { + const half_exp = expmod(base, exp / 2, m); + return (half_exp * half_exp) % m; + } else { + return (base * expmod(base, exp - 1, m)) % m; + } + } +} + + + + +条件语句的一般形式为 + + +if (谓词) { 后续语句 } else { 替换语句 } + + + 对于条件表达式,解释器首先评估 + 括号条件语句的谓词包围条件语句的谓词 + 谓词。如果它评估为真, + 解释器将按顺序评估 + 后续条件语句条件语句的 + 条件语句后续语句 + 后续语句,如果它 + 评估为假,解释器将评估 + 替换条件语句条件语句的 + 条件语句替换语句 + 替换语句,按顺序进行评估。返回 + 语句的评估将从周围的函数返回,忽略任何 + 在返回语句之后和条件语句之后的语句 + 语句序列条件语句的条件语句中的语句。 + 请注意,在任一部分中出现的任何常量声明对于该 + 部分都是局部的,因为每一部分都被大括号括起来,因此形成自己的 + 块。 + 条件语句 + + + + + + +假设我们 + + 定义该过程 + 声明 + + + fg_definition + +(define (f g) + (g 2)) + + +function f(g) { + return g(2); +} + + + 然后我们有 + + fg_definition + square_definition + +(f square) + + +4 + + +f(square); + + +4 + + + + + fg_definition + +(f (lambda (z) (* z (+ z 1)))) + + + 6 + + +f(z => z * (z + 1)); + + +6 + + + 如果我们(任性地)请求解释器评估 + + 组合 (f f)? + 应用 f(f)? + + + 会发生什么?请解释。 + + + 让我们使用代换模型来说明会发生什么: + + +f(f) +f(2) +2(2) + + + 应用 2(2) 会导致错误,因为 2 既不是原语函数也不是复合函数。 + + + + + +
diff --git a/xml/cn/chapter1/section3/subsection3.xml b/xml/cn/chapter1/section3/subsection3.xml new file mode 100644 index 000000000..49ef6b18b --- /dev/null +++ b/xml/cn/chapter1/section3/subsection3.xml @@ -0,0 +1,988 @@ + + + + Procedures + Functions + as General Methods + + + + higher-order proceduresfunctionsprocedurefunction as general method + generalas general method + + + We introduced compound + + procedures + functions + + in section as a mechanism for + abstracting patterns of numerical operations so as to make them independent + of the particular numbers involved. With higher-order + + procedures, + functions, + + such as + the integral + + procedure + function + + of section, we began to + see a more powerful kind of abstraction: + + procedures + functions + + used to express general methods of computation, independent of the + particular functions involved. In this section we discuss two more elaborate + examplesgeneral methods for finding zeros and fixed points of + functionsand show how these methods can be expressed directly as + + procedures. + functions. + + + + + Finding roots of equations by the half-interval method + + + + The + half-interval method + half-interval method is a simple but powerful technique for + finding roots of an equation $f(x)=0$, where + $f$ is a continuous function. The idea is that, + if we are given points $a$ and + $b$ such that + $f(a) < 0 < f(b)$, then + $f$ must have at least one zero between + $a$ and $b$. To locate + a zero, let $x$ be the average of + $a$ and $b$ and + compute $f(x)$. If + $f(x) > 0$, then + $f$ must have a zero between + $a$ and $x$. If + $f(x) < 0$, then + $f$ must have a zero between + $x$ and $b$. + Continuing in this way, we can identify smaller and smaller intervals on + which $f$ must have a zero. When we reach a + point where the interval is small enough, the process stops. Since the + interval of uncertainty is reduced by half at each step of the process, the + maximal number of steps required grows as + $\Theta(\log( L/T))$, where + $L$ is the length of the original interval and + $T$ is the error tolerance (that is, the size of + the interval we will consider small enough). + Here is a + + procedure that implements this strategy: + function that implements this strategy: + + + positive_negative_definition + +;; positive? and negative? are predefined in Scheme + + +function positive(x) { return x > 0; } +function negative(x) { return x < 0; } + + + + search + search_definition + average_definition + positive_negative_definition + close_enough_definition + search_example + 1 + +(define (search f neg-point pos-point) + (let ((midpoint (average neg-point pos-point))) + (if (close-enough? neg-point pos-point) + midpoint + (let ((test-value (f midpoint))) + (cond ((positive? test-value) + (search f neg-point midpoint)) + ((negative? test-value) + (search f midpoint pos-point)) + (else midpoint)))))) + + +function search(f, neg_point, pos_point) { + const midpoint = average(neg_point, pos_point); + if (close_enough(neg_point, pos_point)) { + return midpoint; + } else { + const test_value = f(midpoint); + return positive(test_value) + ? search(f, neg_point, midpoint) + : negative(test_value) + ? search(f, midpoint, pos_point) + : midpoint; + } +} + + + + + + search_example + + + +search(x => x * x - 1, 0, 2); + + + + + We assume that we are initially given the function + $f$ together with points at which its values are + negative and positive. We first compute the midpoint of the two given + points. Next we check to see if the given interval is small enough, and if + so we simply return the midpoint as our answer. Otherwise, we compute as a + test value the value of $f$ at the midpoint. If + the test value is positive, then we continue the process with a new interval + running from the original negative point to the midpoint. If the test value + is negative, we continue with the interval from the midpoint to the positive + point. Finally, there is the possibility that the test value is0, in + which case the midpoint is itself the root we are searching for. + + To test whether the endpoints are close enough we can use a + + procedure + function + + similar to the one used in section for + computing square roots:We have used 0.001 as a representative + small number to indicate a tolerance for the acceptable error + in a calculation. The appropriate tolerance for a real calculation depends + upon the problem to be solved and the limitations of the computer and the + algorithm. This is often + a very subtle consideration, requiring help from a + numerical analyst + numerical analyst or some + other kind of magician. + + close_enough_definition + abs_definition + close_enough_example + false + +(define (close-enough? x y) + (< (abs (- x y)) 0.001)) + + +function close_enough(x, y) { + return abs(x - y) < 0.001; +} + + + + close_enough_example + +(close-enough? 7.7654 7.7666) + + +close_enough(7.7654, 7.7666); + + + + + + + Search + + + The function + search + + + is awkward to use directly, because we can accidentally give it points at + which $f$s values do not have the required + sign, in which case we get a wrong answer. Instead we will use + search via the following + + procedure, + function, + + which checks to see which of the endpoints has a negative function value and + which has a positive value, and calls the search + + procedure + function + + accordingly. If the function has the same sign on the two given points, the + half-interval method cannot be used, in which case the + + procedure + function + + signals an error.This + can be accomplished using + error (primitive function) + error (\textit{ns}) + error, + + which takes as + arguments a number of items that are printed as error messages. + + + which takes as + argument a string that is printed as error message along + with the number of the program line that gave rise to the call of + error. + + + + + half_definition + search_definition + half_example + 3.14111328125 + +(define (half-interval-method f a b) + (let ((a-value (f a)) + (b-value (f b))) + (cond ((and (negative? a-value) (positive? b-value)) + (search f a b)) + ((and (negative? b-value) (positive? a-value)) + (search f b a)) + (else + (error "Values are not of opposite sign" a b))))) + + +function half_interval_method(f, a, b) { + const a_value = f(a); + const b_value = f(b); + return negative(a_value) && positive(b_value) + ? search(f, a, b) + : negative(b_value) && positive(a_value) + ? search(f, b, a) + : error("values are not of opposite sign"); +} + + + + + The following example uses the + + + half-interval methodhalfhalf-interval-method + + + half-interval methodhalfhalf_interval_method + + + pi$\pi$ (pi)approximation with half-interval method + half-interval method to approximate + $\pi$ as the root between 2 and 4 of + $\sin\, x = 0$: + + half_example + half_definition + +(half-interval-method sin 2.0 4.0) + + +half_interval_method(math_sin, 2, 4); + + +3.14111328125 + + + + + Here is another example, using the half-interval method to search for a root + of the equation $x^3 - 2x - 3 = 0$ between 1 + and 2: + + half_example2 + half_definition + 1.89306640625 + +(half-interval-method (lambda (x) (- (* x x x) (* 2 x) 3)) + 1.0 + 2.0) + + +half_interval_method(x => x * x * x - 2 * x - 3, 1, 2); + + +1.89306640625 + + + half-interval method + + + + Finding fixed points of functions + + + + + A number $x$ is called a + fixed point + function (mathematical)fixed point of + fixed point of a + function $f$ if $x$ + satisfies the equation $f(x)=x$. For some + functions $f$ we can locate a fixed point by + beginning with an initial guess and applying $f$ + repeatedly, + + \[ + \begin{array}{l} + f(x), \ f(f(x)), \ f(f(f(x))), \ \ldots + \end{array} + \] + + until the value does not change very much. Using this idea, we can devise a + + procedure + function + + + + fixed-point + fixed_point + + that takes as inputs a function and an initial guess and produces an + approximation to a fixed point of the function. We apply the function + repeatedly until we find two successive values whose difference is less + than some prescribed tolerance: + + + fixed_point + fixed_definition + abs_definition + fixed_example + 0.7390822985224023 + +(define tolerance 0.00001) + +(define (fixed-point f first-guess) + (define (close-enough? v1 v2) + (< (abs (- v1 v2)) tolerance)) + (define (try guess) + (let ((next (f guess))) + (if (close-enough? guess next) + next + (try next)))) + (try first-guess)) + + +const tolerance = 0.00001; +function fixed_point(f, first_guess) { + function close_enough(x, y) { + return abs(x - y) < tolerance; + } + function try_with(guess) { + const next = f(guess); + return close_enough(guess, next) + ? next + : try_with(next); + } + return try_with(first_guess); +} + + + For example, we can use this method to approximate the fixed point of the + fixed pointcosineof cosine + cosinefixed point of + math_cos (primitive function) + math_cosMath.cos + cosine function, starting with 1 as an initial approximation: + + + Try this during a boring lecture: Set your calculator to + fixed pointcomputing with calculator + calculator, fixed points with + lecture, something to do during + radians mode and then repeatedly press the + $\cos$ + button until you obtain the fixed point. + + + To obtain a + fixed pointcomputing with calculator + calculator, fixed points with + fixed point of cosine on a calculator, + set it to radians mode and then repeatedly press the + $\cos$ + button until the value does not change any longer. + + + + + fixed_example + fixed_definition + +(fixed-point cos 1.0) + + +0.7390822985224023 + + +fixed_point(math_cos, 1); + + +0.7390822985224023 + + + Similarly, we can find a solution to the equation + $y=\sin y + \cos y$: + math_sin (primitive function) + math_sinMath.sin + + fixed_example2 + fixed_definition + 1.2587315962971173 + +(fixed-point (lambda (y) (+ (sin y) (cos y))) + 1.0) + + +1.2587315962971173 + + +fixed_point(y => math_sin(y) + math_cos(y), 1); + + +1.2587315962971173 + + + + + The fixed-point process is reminiscent of the process we used for finding + square roots in section. Both are based on + the idea of repeatedly improving a guess until the result satisfies some + criterion. In fact, we can readily formulate the + fixed pointsquare root as + square-root computation as a fixed-point search. Computing the square root + of some number $x$ requires finding a + $y$ such that + $y^2 = x$. Putting this equation into the + equivalent form $y = x/y$, we recognize that we + are looking for a fixed point of the function + $\mapsto$ + 0a2$\mapsto$ notation for mathematical function + function (mathematical)$\mapsto$ notation for + (pronounced maps to) is the mathematicians way of + writing + + lambda. + lambda expressions. + + $y \mapsto x/y$ means + + (lambda(y) (/ x y)), + y => x / y, + + that is, the function whose value at $y$ is + $x/y$. + $y \mapsto x/y$, and we can therefore try to + compute square roots as + + sqrtfixedas fixed point + sqrt_definition2 + fixed_definition + sqrt_example7 + +(define (sqrt x) + (fixed-point (lambda (y) (/ x y)) + 1.0)) + + +function sqrt(x) { + return fixed_point(y => x / y, 1); +} + + + + sqrt_example7 + +sqrt(5); + + + Unfortunately, this fixed-point search does not converge. Consider an + initial guess$y_1$. The next guess is + $y_2 = x/y_1$ and the next guess is + $y_3 = x/y_2 = x/(x/y_1) = y_1$. This results + in an infinite loop in which the two guesses + $y_1$ and $y_2$ repeat + over and over, oscillating about the answer. + + + + One way to control such oscillations is to prevent the guesses from changing + so much. Since the answer is always between our guess + $y$ + and $x/y$, we can make a new guess that is not as + far from $y$ as $x/y$ + by averaging $y$ with + $x/y$, so that the next guess after + $y$ is + $\frac{1}{2}(y+x/y)$ instead of + $x/y$. The process of making such a sequence of + guesses is simply the process of looking for a fixed point of + $y \mapsto \frac{1}{2}(y+x/y)$: + + + sqrt_definition3 + fixed_definition + average_definition + sqrt_example7 + 2.236067977499978 + +(define (sqrt x) + (fixed-point (lambda (y) (average y (/ x y))) + 1.0)) + + +function sqrt(x) { + return fixed_point(y => average(y, x / y), 1); +} + + + (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 by2.) + + + + With this modification, the square-root + + procedure + function + + works. In fact, if we unravel the definitions, we can see that the sequence + of approximations to the square root generated here is precisely the same as + the one generated by our original square-root + + procedure + function + + of section. This approach of averaging + successive approximations to a solution, a technique we call + average damping + average damping, often aids the convergence of fixed-point searches. + fixed point + function (mathematical)fixed point of + + + + Show that the + golden ratiofixedas fixed point + fixed pointgolden ratio as + golden ratio $\phi$ + (section) is a fixed point of the + transformation $x \mapsto 1 + 1/x$, and use this + fact to compute $\phi$ by means of the + + + fixed-point procedure. + + + fixed_point function. + + + + The fixed point of the function is + \[ 1 + 1 / x = x \] + Solving for x, we get + \[ x^2 = x + 1 \] + \[ x^2 - x - 1 = 0 \] + Using the quadratic equation to solve for $x$, + we find that one of the roots of this equation is the golden ratio + $(1+\sqrt{5})/2$. + + example_1.37 + fixed_definition + +fixed_point(x => 1 + (1 / x), 1); + + + + + + + + + + Modify + + fixed-point + fixed_point + + so that it prints the sequence of approximations it generates, using the + primitive function display shown in + exercise. Then find a solution to + $x^x = 1000$ by finding a fixed point of + $x \mapsto \log(1000)/\log(x)$. + + + (Use Schemes primitive log procedure + which computes natural logarithms.) + + + math_log (primitive function) + math_logMath.log + (Use the primitive function math_log, + which computes natural logarithms.) + + + Compare the number of steps this takes with and without average damping. + (Note that you cannot start + + fixed-point + fixed_point + with a guess of 1, as this would cause division by + $\log(1)=0$.) + + + + We modify the function fixed_point + as follows: + + modified_fixed_definition + abs_definition + example_1.36_1 + +(define tolerance 0.00001) + +(define (fixed-point f first-guess) + (define (close-enough? v1 v2) + (< (abs (- v1 v2)) tolerance)) + (define (try guess) + (let ((next (f guess))) + (if (close-enough? guess next) + next + (try next)))) + (try first-guess)) + + +const tolerance = 0.00001; +function fixed_point(f, first_guess) { + function close_enough(x, y) { + return abs(x - y) < tolerance; + } + function try_with(guess) { + display(guess); + const next = f(guess); + return close_enough(guess, next) + ? next + : try_with(next); + } + return try_with(first_guess); +} + + + Here is a version with average dampening built-in: + + modified_fixed_definition_average_dampening + abs_definition + example_1.36_2 + +(define tolerance 0.00001) + +(define (fixed-point f first-guess) + (define (close-enough? v1 v2) + (< (abs (- v1 v2)) tolerance)) + (define (try guess) + (let ((next (f guess))) + (if (close-enough? guess next) + next + (try next)))) + (try first-guess)) + + +function fixed_point_with_average_dampening(f, first_guess) { + function close_enough(x, y) { + return abs(x - y) < tolerance; + } + function try_with(guess) { + display(guess); + const next = (guess + f(guess)) / 2; + return close_enough(guess, next) + ? next + : try_with(next); + } + return try_with(first_guess); +} + + + + example_1.36_1 + modified_fixed_definition + +fixed_point(x => math_log(1000) / math_log(x), 2.0); + + + + + + example_1.36_2 + modified_fixed_definition_average_dampening + +fixed_point_with_average_dampening(x => math_log(1000) / math_log(x), 2.0); + + + + + + + + + An infinite + continued fraction + continued fraction is an expression of the form + + \[ + \begin{array}{lll} + f & = & {\dfrac{N_1}{D_1+ + \dfrac{N_2}{D_2+ + \dfrac{N_3}{D_3+\cdots }}}} + \end{array} + \] + + As an example, one can show that the infinite continued fraction + expansion with the $N_i$ and the + $D_i$ all equal to 1 produces + $1/\phi$, where + $\phi$ is the + continued fractiongolden ratio as + golden ratiocontinuedas continued fraction + golden ratio (described in + section). One way to approximate + an infinite continued fraction is to truncate the expansion after a given + number of terms. Such a truncationa so-called + $k$-term finite continued + fractionhas the form + + \[ + {\dfrac{N_1}{D_1 + + \dfrac{N_2}{\ddots + + \dfrac{N_K}{D_K}}}} + \] + +
    +
  1. + Suppose that n and + d are + + procedures + functions + + of one argument (the term index $i$) that + return the $N_i$ and + $D_i$ of the terms of the continued fraction. + + Define a procedure + Declare a function + + + cont-frac + cont_frac + + such that evaluating + + (cont-frac n d k) + cont_frac(n, d, k) + + + computes the value of the $k$-term finite + continued fraction. Check your + + procedure + function + + by approximating $1/\phi$ using + + cont_definition + +;; cont-frac to be written by student; see exercise 1.37 + + +// cont_frac to be written by student; see exercise 1.37 + + + + cont_example + cont_definition + +(cont-frac (lambda (i) 1.0) + (lambda (i) 1.0) + k) + + +cont_frac(i => 1, i => 1, k); + + + for successive values of k. How large must + you make k in order to get an approximation + that is accurate to 4 decimal places? +
  2. +
  3. + If your + cont-frac + cont_frac + + + procedure + function + + generates a recursive process, write one that generates an iterative + process. If it generates an iterative process, write one that generates + a recursive process. +
  4. +
+ + + + cont_example_1 + 0.6180339850173578 + +//recursive process +function cont_frac(n, d, k) { + function fraction(i) { + return i > k + ? 0 + : n(i) / (d(i) + fraction(i + 1)); + } + return fraction(1); +} + + + + cont_frac + cont_example_1 + 0.6180339850173578 + +//iterative process +function cont_frac(n, d, k) { + function fraction(i, current) { + return i === 0 + ? current + : fraction(i - 1, n(i) / (d(i) + current)); + } + return fraction(k, 0); +} + + + + + cont_example_1 + cont_definition + +(cont-frac (lambda (i) 1.0) + (lambda (i) 1.0) + 20) + + +cont_frac(i => 1, i => 1, 20); + + + +
+ + In 1737, the Swiss mathematician + Euler, Leonhard + Leonhard Euler published a memoir + De Fractionibus Continuis, which included a + continued fractione$e$ as + e$e$as continued fraction + continued fraction expansion for $e-2$, where + $e$ is the base of the natural logarithms. In + this fraction, the $N_i$ are all 1, and the + $D_i$ are successively + 1,2,1, 1, 4, 1, 1, 6, 1, 1, 8, . Write a program that uses your + + cont-frac procedure + cont_frac function + + from exercise to approximate + $e$, based on Eulers expansion. + + + cont_frac + 2.718281828459045 + +2 + cont_frac(i => 1, + i => (i + 1) % 3 < 1 ? 2 * (i + 1) / 3 : 1, + 20); + + + + + + + A continued fraction representation of the tangent function was + published in 1770 by the German mathematician + Lambert, J.H. + continued fractiontangent as + tangentas continued fraction + J.H. Lambert: + + \[ + \begin{array}{lll} + \tan x & = & {\dfrac{x}{1- + \dfrac{x^2}{3- + \dfrac{x^2}{5- + \dfrac{x^2}{ \ddots }}}}} + \end{array} + \] + + where $x$ is in radians. + + Define a procedure (tan-cf x k) + + Declare a function + tan_cf(x, k) + + + that computes an approximation to the tangent function based on + Lamberts formula. + + + K specifies the number of terms to compute, + as in exercise. + + + As in exercise, + k specifies the number of terms + to compute. + + + + + cont_frac + tan_ex + -2.8271597168564594e-16 + +function tan_cf(x, k) { + return cont_frac(i => i === 1 ? x : - x * x, + i => 2 * i - 1, + k); +} + + + + tan_ex + +tan_cf(math_PI, 14); +// math_tan(math_PI); + + + + + + higher-order proceduresfunctionsprocedurefunction as general method + generalas general method +
diff --git a/xml/cn/chapter1/section3/subsection4.xml b/xml/cn/chapter1/section3/subsection4.xml new file mode 100644 index 000000000..7c8d6fe6e --- /dev/null +++ b/xml/cn/chapter1/section3/subsection4.xml @@ -0,0 +1,1319 @@ + + + + 过程 + 函数 + + 作为返回值 + + + + + 高阶 过程函数过程函数 作为返回值 + 返回作为返回值 + + + 上述示例演示了将 + + 过程 + 函数 + + 作为参数传递的能力如何显著增强了我们编程语言的表达能力。我们可以通过创建返回值本身就是 + + 过程 + 函数 + + 的 + + 过程。 + 函数。 + + 实现更强的表达能力。 + + + 我们可以通过再次查看在 + section结束处描述的不动点示例来说明这个想法。我们将平方根的 + + 过程 + 函数 + + 版本公式化为不动点搜索,首先观察到 + $\sqrt{x}$ 是函数 + $y\mapsto x/y$ 的不动点。然后我们使用平均阻尼来使近似收敛。平均阻尼本身就是一种有用的通用技术。也就是说,给定一个 + function$f$,我们考虑在 $x$ 处的值等于 + $x$ 和 $f(x)$ 的平均值的函数。 + + + 我们可以通过以下 + + 过程: + 函数: + + 来表达平均阻尼的思想 + + average_damp + average_damp_definition + average_definition + average_damp_example + 55 + +(define (average-damp f) + (lambda (x) (average x (f x)))) + + +function average_damp(f) { + return x => average(x, f(x)); +} + + + + + 平均阻尼 + 是一个过程 + + + 函数 + average_damp + + + 的参数为一个 + + 过程 + 函数 + + f 并返回一个 + + 过程 + 函数 + + + (由 lambda 生成) + (由 lambda 表达式生成) + + 当应用于一个数字时 x ,生成 + 平均值 x 和 + + (f x). + f(x). + + 例如,应用 + + + 平均阻尼 + + + average_damp + + + 到 square + + 过程 + 函数 + + 生成一个 + + 过程 + 函数 + + 在某个数字处的值 $x$ 是 + 平均值 $x$$x^2$ 。 + 应用这个结果的 + + 过程 + 函数 + + 到 10 返回 10 和 100 的平均值,即 55: + + + 注意这是一个运算符本身是 + 组合组合作为运算符的 + 组合运算符作为组合的运算符 + 组合的运算符作为的组合 + 一个组合。练习已经展示了形成这种组合的能力,但那只是一个玩具示例。这里我们开始看到这种组合的实际需求当应用一个过程时,该过程是由高阶过程返回的值获得的。 + + + 注意这是一个函数表达式本身是 + 函数应用函数作为应用的函数表达式 + 函数表达式应用作为 + 一个应用。练习已经展示了形成这种应用的能力,但那只是一个玩具示例。这里我们开始看到这种应用的实际需要当应用一个函数时,该函数是由高阶函数返回的值获得的。 + + + + + average_damp_example + average_damp_definition + square_definition + +((average-damp square) 10) + + +55 + + +average_damp(square)(10); + + +55 + + + + + + 使用 + + 平均阻尼, + average_damp, + + 我们可以重新表述 + 不动点平方根作为 + 平方根 + + 过程 + 函数 + + 如下: + + sqrtfixedas fixed point + sqrt_definition4 + average_damp_definition + fixed_definition + sqrt_example3 + +(define (sqrt x) + (fixed-point (average-damp (lambda (y) (/ x y))) + 1.0)) + + +function sqrt(x) { + return fixed_point(average_damp(y => x / y), 1); +} + + + + sqrt_example3 + sqrt_definition4 + 2.4494897427875517 + +sqrt(6); + + + 注意这种表述如何明确了方法中的三个概念: + 不动点搜索、平均阻尼和函数 + $y\mapsto x/y$ 。 比较平方根方法的这种表述与在 section中给出的原始版本是很有启发的。请记住,这些 + + 过程 + 函数 + + 表达相同的过程,并注意当我们用这些抽象表达过程时,概念变得多么清晰。一般来说,有许多方法可以将一个过程表述为一个 + + 过程。 + 函数。 + + 经验丰富的程序员知道如何选择特别清晰的 + + 过程 + 过程 + + 表述方式,并在此过程中有用的元素被作为独立实体暴露出来,能够在其他应用中重复使用。作为实现重用的一种简单示例,请注意立方根 + $x$ 是函数的一个不动点 + $y\mapsto x/y^2$ ,因此我们可以立即 + 将平方根的 + + 过程 + 函数 + + 推广到一个提取 + 立方根固定作为不动点 + 不动点立方根作为 + 立方根的过程:参见练习 + 以获得进一步的推广。 + cube_root + + cube_root + cube_root_definition + average_damp_definition + fixed_definition + square_definition + cube_root_example + +(define (cube-root x) + (fixed-point (average-damp (lambda (y) (/ x (square y)))) + 1.0)) + + +function cube_root(x) { + return fixed_point(average_damp(y => x / square(y)), 1); +} + + + + cube_root_example + cube_root_definition + 2.9999972321057697 + +(cube-root 27) + + +cube_root(27); + + + + + + 牛顿法 + + + + 当我们第一次在 section中介绍平方根 + + 过程, + 函数, + + 我们提到这是 + 牛顿法可微用于可微函数 + 牛顿法的一个特例。如果 + $x\mapsto g(x)$ 是可微函数, + 那么方程的一个解 $g(x)=0$ 是函数的 + 不动点 $x\mapsto f(x)$ 在 + + \[ + \begin{array}{lll} + f(x) & = & x - \dfrac{g(x)}{Dg(x)} + \end{array} + \] + + 和 $Dg(x)$ 是 + 的导数 + $g$ 在 处求值 $x$. + 不动点牛顿在牛顿法中 + 牛顿法是在上面看到的不动点方法中使用不动点来通过寻找函数的不动点来逼近方程的解 + $f$. 初等微积分书籍通常用逼近序列 $x_{n+1}=x_n-g(x_n)/Dg(x_n)$ 来描述牛顿法。拥有描述过程的语言并使用不动点的概念可以简化方法的描述。 + 对于许多函数 $g$ 并且对于足够好的初始猜测 $x$ ,牛顿法 + 非常快速地收敛到 + $g(x)=0$. 牛顿法并不总是收敛到一个解,但可以证明,在有利情况下,每次迭代都会使逼近解的数字位数加倍。在这种情况下, + 牛顿法与半区间法比较 + 半区间法与牛顿法比较 + 牛顿法将比半区间法收敛得快得多。 + + + + + 为了将牛顿法实现为一个 + + 过程, + 函数, + + 我们首先必须表达 + 函数的导数 + 函数 (数学)导数 + 微分数值 + 导数的概念。请注意,“导数”就像平均阻尼,是将一个函数变换为另一个函数的东西。例如,函数的导数 + $x\mapsto x^3$ 是函数 + $x \mapsto 3x^2$ 。一般来说,如果 + $g$ 是一个函数并且 + $dx$ 是一个小数,那么导数 + $Dg$$g$ 是在任何数字处的值为 + 的函数 + $x$ 给出 + (在小 $dx$ )由 + + \[ + \begin{array}{lll} + Dg(x) & = & \dfrac {g(x+dx) - g(x)}{dx} + \end{array} + \] + + 因此,我们可以表达导数的概念(取 + $dx$ 设置为0.00001)为 + + 过程 + 函数 + + + deriv_definition + dx + deriv_example + +(define (deriv g) + (lambda (x) + (/ (- (g (+ x dx)) (g x)) + dx))) + + +function deriv(g) { + return x => (g(x + dx) - g(x)) / dx; +} + + + 以及 + + 定义 + 声明 + + + dx + +(define dx 0.00001) + + +const dx = 0.00001; + + + + + 像 +deriv (数值) + + 平均阻尼, + average_damp, + + deriv (数值) + deriv 是一个 + + 过程 + 函数 + + 接受一个 + + 过程 + 函数 + + 作为参数并返回一个 + + 过程 + 函数 + + 作为值。例如,要近似 + $x \mapsto x^3$ 在 5 处的导数(其确切值是 75) + 我们可以计算 + + 立方 + deriv_example + deriv_definition + 75.00014999664018 + +(define (cube x) (* x x x)) + +((deriv cube) 5) + + +75.00014999664018 + + +function cube(x) { return x * x * x; } + +deriv(cube)(5); + + +75.00014999664018 + + + + + + 借助 + deriv ,我们可以将牛顿法表示为一个不动点过程: + + + newton_transform + newtons_method + newtons_method_definition + fixed_definition + deriv_definition + sqrt_example4 + 2.4494897427970397 + +(define (newton-transform g) + (lambda (x) + (- x (/ (g x) ((deriv g) x))))) + +(define (newtons-method g guess) + (fixed-point + (newton-transform g) guess)) + + +function newton_transform(g) { + return x => x - g(x) / deriv(g)(x); +} +function newtons_method(g, guess) { + return fixed_point(newton_transform(g), guess); +} + + + 这个 + + newton_transform + newton_transform + + + + 过程 + 函数 + + 表达了本节开头的公式,而 + + + newtons_method + newtons_method + + + 可以由此定义。它接受一个 + + 过程 + 函数 + + 作为参数,该过程计算出我们想要找零的函数,以及一个初始猜测。例如,要找 的平方根【180:0†cn.txt】 + $x$ ,我们可以使用 + 牛顿法平方用于平方根 + 牛顿法 + 来找到函数的零点 + $y\mapsto y^2-x$ ,我们可以使用 + 牛顿法平方用于平方根 + 牛顿法找到该函数的零点,初始猜测为1。对于寻找平方根,牛顿法从任何起始点快速收敛到正确的解决方案。 + 这提供了平方根的又一种形式 + + 过程: + 函数: + + + sqrtNewtonwith Newtons method + sqrtfixedas fixed point + sqrt_definition5 + newtons_method_definition + square_definition + sqrt_example4 + +(define (sqrt x) + (newtons-method (lambda (y) (- (square y) x)) + 1.0)) + + +function sqrt(x) { + return newtons_method(y => square(y) - x, 1); +} + + + + sqrt_example4 + sqrt_definition5 + +sqrt(6); + + + 牛顿法可微用于可微函数 + + + + + 抽象与一等 + + 过程 + 函数 + + + + + + 我们已经看到两种将平方根计算表示为更一般方法实例的方式,一种是作为不动点搜索,另一种是使用牛顿法。由于牛顿法本身被表达为一个不动点过程,我们实际上看到了两种以不动点计算平方根的方法。每种方法都从一个函数开始,并找到该函数某种变换的不动点。我们可以将这个一般的思想本身表达为一个 + + 过程: + 函数: + + + fixed_point_of_transform + fixed_point_of_transform_definition + fixed_definition + sqrt_example5 + 2.4494897427875517 + +(define (fixed-point-of-transform g transform guess) + (fixed-point (transform g) guess)) + + +function fixed_point_of_transform(g, transform, guess) { + return fixed_point(transform(g), guess); +} + + + 这个非常通用的 + + 过程 + 函数 + + 接受一个 + + 过程 + 函数 + + g 作为参数,它计算某个函数,一个 + + 过程 + 函数 + + 转换g,以及一个初始猜测。返回的结果是不动点的变换函数。 + + + + 使用这个抽象,我们可以重铸本节中第一个平方根的计算 + 不动点平方根作为 + (我们在平均阻尼版本的 $y \mapsto x/y$ ) 作为这个一般方法的一个实例: + + sqrtfixedas fixed point + sqrt_definition6 + fixed_point_of_transform_definition + average_damp_definition + sqrt_example5 + 2.4494897427875517 + +(define (sqrt x) + (fixed-point-of-transform (lambda (y) (/ x y)) + average-damp + 1.0)) + + +function sqrt(x) { + return fixed_point_of_transform( + y => x / y, + average_damp, + 1); +} + + + + sqrt_example5 + sqrt_definition6 + +sqrt(6); + + + 类似地,我们可以将本节中的第二个平方根计算(一个 + 牛顿法平方用于平方根 + 牛顿法的实例,用于找到牛顿变换的 + $y\mapsto y^2-x$ ) 表示为 + + sqrtfixedas fixed point + sqrtNewtonwith Newtons method + sqrt_definition7 + fixed_point_of_transform_definition + square_definition + newtons_method_definition + sqrt_example6 + +(define (sqrt x) + (fixed-point-of-transform (lambda (y) (- (square y) x)) + newton-transform + 1.0)) + + +function sqrt(x) { + return fixed_point_of_transform( + y => square(y) - x, + newton_transform, + 1); +} + + + + sqrt_example6 + sqrt_definition7 + +sqrt(6); + + + + + 我们开始在 section观察到复合 + + 过程 + 函数 + + 是一个关键的抽象机制,因为它们允许我们将计算的一般方法表示为在我们的编程语言中明确的元素。现在我们已经看到,高阶 + + 过程 + 函数 + + 允许我们操作这些一般方法来创建进一步的抽象。 + + + 我们在 section 开始时观察到复合 + + 过程 + 函数 + + 是一个关键的抽象机制,因为它们允许我们将计算的一般方法表示为在我们的编程语言中明确的元素。现在我们已经看到,高阶 + + 过程 + 函数 + + 允许我们操作这些一般方法来创建进一步的抽象。 + + + 通常,编程语言对计算元素的操作方式施加限制。 限制最少的元素则被称为 + 语言中的一等公民 + 一等公民。 一等公民的一些权利和特权包括:编程语言元素的一等公民概念由英国计算机科学家 + Strachey, Christopher + Christopher Strachey (19161975) 提出。 +
    +
  • + + 它们可以通过变量命名。 + 它们可以通过名称引用。 + +
  • +
  • + 它们可以作为参数传递给 + + 过程。 + 函数。 + +
  • +
  • + 它们可以作为返回结果从 + + 过程。 + 函数。 + + 返回 +
  • +
  • + 它们可以被包含在数据结构中。我们将在引入数据结构后,在第章中看到这一点的例子。 +
  • +
+ + Lisp, + JavaScript, + + LispJavaScript中的一等过程函数 + 一等公民 + + + 与其他编程语言不同, + + + 作为其他高级编程语言中的一部分 + + + 的编程语言 + + 过程 + 函数 + + 完全的一等地位。这对高效实现提出了挑战,但由此带来的表达能力的提升是巨大的。一等 + + 过程 + 函数 + + 主要的实现代价在于允许 + + 过程 + 函数 + + 作为值返回,需要为 + + 过程的自由变量 + 函数的自由名称 + + 保留存储空间,即使 + + 过程 + 函数 + + 没有执行。 + + + 在我们将在 + section中研究的 Scheme 实现中,这些变量存储在过程的 + + + 在我们将在 + section中研究的 JavaScript 实现中,这些名称存储在函数的 + + 环境中。 +
+ + + + 定义一个过程 + 声明一个函数 + + cubic ,它可以与 + + newtons_method + newtons_method + + + + 过程 + 函数 + + 一起在以下形式的表达式中使用 + + cubic_definition + +;; cubic to be written by student; see EXERCISE 1.40 + + +// cubic to be written by student; see EXERCISE 1.40 + + + + cubic_example + newtons_method_definition + cubic_definition + +(newtons-method (cubic a b c) 1) + + +newtons_method(cubic(a, b, c), 1) + + + 来近似多项式的零点 + $x^3 +ax^2 +bx +c$. + + + cubic_definition_2 + newtons_method_definition + cube_definition + square_definition + cubic_example_2 + 1.5615528128102987 + +function cubic(a, b, c) { + return x => cube(x) + a * square(x) + b * x + c; +} + + + + + + cubic_example_2 + newtons_method_definition + cubic_definition + +(newtons-method (cubic a b c) 1) + + +newtons_method(cubic(1, -4, 0), 1); + + + + + + + + + 定义一个过程 + 声明一个函数 + + double 接受一个 + + 单参数过程 + 单参数函数 + + 作为参数并返回一个 + + 过程 + 函数 + + 该过程应用原始的 + + 过程 + 函数 + + 两次。例如,若【250:0†source】 +inc 接受一个 + + 过程 + 函数 + + 作为参数并返回一个 + + 过程 + 函数 + + 该过程将原始的 + + 过程 + 函数 + + 应用两次。例如,若【250:4†cn.txt】 + double_definition + +;; double to be written by student; see EXERCISE 1.41 + + +// double to be written by student; see EXERCISE 1.41 + + + + double_example + double_definition + inc_definition + +(((double (double double)) inc) 5) + + +double(double(double))(inc)(5); + + + + + inc_definition + 21 + double_example_solution + +function double(f) { + return x => f(f(x)); +} + + + + + + double_example_solution + inc_definition + +double(double(double))(inc)(5); // returns 21 + + + + + + + + + + + 让 + $f$$g$ 是两个单参数 + + 过程 + 函数 + + 。 + 函数复合 + 函数 (数学)复合 + 复合 + $f$ 在 之后 $g$ 被定义为函数 + $x\mapsto f(g(x))$. + + 定义一个过程 + 声明一个函数 + + compose 实现复合的 + + 过程。对于 + 函数。例如,若 + + inc 是一个 + + 过程 + 函数 + + 可以对其参数加1 + + compose_definition + +;; compose to be written by student; see EXERCISE 1.42 + + +// compose to be written by student; see EXERCISE 1.42 + + + + compose_example + compose_definition + square_definition + inc_definition + +((compose square inc) 6) + + +49 + + +compose(square, inc)(6); + + +49 + + + 返回 49。 + + + compose_definition_solution + compose_example_solution + 49 + +function compose(f, g) { + return x => f(g(x)); +} + + + + + + compose_example_solution + inc_definition + square_definition + +compose(square, inc)(6); // 返回 49 + + + + + + + + + 如果 + $f$ 在 + +函数(数学)重复应用 + $n$ 是正整数,然后我们可以形成 + $n$ 是一个正整数,那么我们可以构造 + 函数 (数学)重复应用 + 函数的重复应用 + $f$ ,其定义为值在某个数处的函数 + $x$ 是 + $f(f(\ldots(f(x))\ldots))$ 。例如,如果 + $f$ 是函数 + $x \mapsto x+1$ ,那么 + $n$ ,定义为函数的值在 + $f$ 是函数 + $x \mapsto x+n$ 。如果 + $f$ 是将数平方的运算,那么 + 【320:7†source】 + $n$ ,n 次重复应用【324:0†cn.txt】 + $f$ 是将其参数提升到 + 【328:7†cn.txt】$2^n$ n 次幂。编写一个 + + 过程 + 函数 + + ,该过程将接受一个计算 + + 过程 + 函数 + + 【8:0†source】$f$ 和一个正整数 + $n$ 和返回一个 + + 过程 + 函数 + + ,该过程计算 + $n$ n 次重复应用【8:2†source】 + $f$ 你的 + + 过程 + 函数 + + 应该可以按如下方式使用 + + repeated_definition + +;; repeated to be written by student; see EXERCISE 1.43 + + +// repeated to be written by student; see EXERCISE 1.43 + + + + repeated_example + square_definition + repeated_definition + +((repeated square 2) 5) + + +625 + + +repeated(square, 2)(5); + + +625 + + + 提示:您可能会发现使用 + compose 从 + 练习。 + + + + repeated_definition_solution + compose_definition_solution + repeated_example_solution + 625 + +function repeated(f, n) { + return n === 0 + ? x => x + : compose(f, repeated(f, n - 1)); +} + + + + + + repeated_example_solution + square_definition + +((repeated square 2) 5) + + +repeated(square, 2)(5); // 625 + + + + + + + 平滑一个函数的概念在信号处理中是一个重要的概念。 + 函数 (数学)平滑 + 平滑一个函数 + 平滑 + 信号处理平滑一个函数 + 如果【360:2†source】 +$f$ 是一个函数并且 + $dx$ 是某个小数,那么 + 的平滑版本【8:0†cn.txt】 + $f$ 是在某点处的值为该点的函数【370:0†cn.txt】 + $x$ + 的平均值【374:5†cn.txt】 + $f(x-dx)$, $f(x)$ ,和 + $f(x+dx)$ 。编写一个 + + 过程 + 函数 + + smooth ,它将作为输入的一个计算 + + 过程 + 函数 + 【8:0†source】 +$f$ ,其作为输入接受一个计算 + + 过程 + 函数 + + 的过程,并返回一个计算平滑【388:1†cn.txt】 +$f$ 。有时 + 重复平滑一个函数(即,平滑已平滑的函数,依此类推)以获取 $n$ 次 + 平滑的函数 是有价值的。展示如何生成 + $n$ -次平滑函数。使用 + smooth 和 + repeated 从 + 练习。 + + + n_fold_smooth_solution + cube_definition + repeated_definition_solution + example_1.44 + 64.00000000399997 + +const dx = 0.00001; +function smooth(f) { + return x => (f(x - dx) + f(x) + f(x + dx)) / 3; +} +function n_fold_smooth(f, n) { + return repeated(smooth, n)(f); +} + + + + + + + example_1.44 + +n_fold_smooth(cube, 5)(4); + + + + + + + + + 我们在 section 中看到, + 通过简单地寻找不动点来尝试计算平方根 + $y\mapsto x/y$ 从 + 练习。 + + + n_fold_smooth_solution + cube_definition + repeated_definition_solution + example_1.44 + 64.00000000399997 + +const dx = 0.00001; +function smooth(f) { + return x => (f(x - dx) + f(x) + f(x + dx)) / 3; +} +function n_fold_smooth(f, n) { + return repeated(smooth, n)(f); +} + + + + + + $y\mapsto x/y^2$ 。但是,不幸的是,该过程不适用于 + 四次方根,作为不动点 + 不动点四次方根作为 + 四次方根单一的平均阻尼不足以进行 + $y\mapsto x/y^3$ + 收敛。 另一方面,如果我们平均阻尼两次 (即,使用两次平均阻尼的 + $y\mapsto x/y^3$ ,搜索会 + 收敛。做一些实验来确定计算需要多少平均阻尼 + n次方根,作为不动点 + 不动点n次方根作为 + $n$ 次方根作为基于重复平均阻尼的不动点搜索【424:17†source】。 + $y\mapsto x/y^{n-1}$ 。 + 使用这个来实现一个简单的 + + 过程 + 函数 + + 来计算 + $n$ 次方根。 + 使用 + + fixed_point, + fixed_point, + + + average_damp, + average_damp, + + 和 + repeated + + + 练习中的过程 + 练习中的函数 + + 。假设您需要的任何算术运算都作为原语可用。 + + + + nth_root_solution + fixed_definition + average_damp_definition + repeated_definition_solution + expt_log_definition + example_1.45 + 2.000001512995761 + +function nth_root(n, x) { + return fixed_point(repeated(average_damp, + math_floor(math_log2(n))) + (y => x / fast_expt(y, n - 1)), + 1); +} + + + + example_1.45 + +nth_root(5, 32); + + + + + + + + + 本章中描述的几种数值方法是一个非常通用的计算策略的实例,称为迭代改进 + 平方根迭代作为迭代改进 + 不动点迭代作为迭代改进 + 不动点迭代作为迭代改进 + 迭代改进。迭代改进表示,要计算某个东西,我们从初始猜测开始,测试猜测是否足够好,否则改进猜测并使用改进的方法继续该过程,以此作为新的猜测。编写一个 + + 过程 + 函数 + + + iterative-improve + iterative_improve + + + 接受两个 + + 过程 + 函数 + + 作为参数:一个判断猜测是否足够好的方法和一个改进猜测的方法。 + + Iterative-improve + 函数 + iterative_improve + + + 应返回一个其值为接受一个猜测作为参数并不断改进猜测直到它足够好的 + + 过程 + 函数 + + 。重写 sqrt + + 过程 + 函数 + + section和 + + fixed_point + fixed_point + + section中的 + + iterative-improve + iterative_improve + + + 中的 + + + iterative_improve + example_1.46_1 + 7.000000141269659 + +function iterative_improve(is_good_enough, improve) { + function iterate(guess) { + return is_good_enough(guess) + ? guess + : iterate(improve(guess)); + } + return iterate; +} + + + + example_1.46_1 + square_definition + improve + is_good_enough + iterative_improve + +function sqrt(x) { + return iterative_improve(y => is_good_enough(y, x), + y => improve(y, x))(1); +} + +sqrt(49); + + + + + 【4:0†cn.txt】 + + + 高阶 过程函数过程函数 作为返回值 + 返回 作为返回值 + +
diff --git a/xml/cn/chapter2/chapter2.xml b/xml/cn/chapter2/chapter2.xml index e0de31be5..265ab0f65 100644 --- a/xml/cn/chapter2/chapter2.xml +++ b/xml/cn/chapter2/chapter2.xml @@ -1,179 +1,182 @@ - 用数据建立抽象 + 通过数据构建抽象 - \addtocontents{toc}{\protect\enlargethispage{-\baselineskip}} + \addtocontents{toc}{\protect\enlargethispage{-\baselineskip}} - - % 秋'97 添加 tex '\label' 用于手动引用。 - % 8/26/97 修正第三次印刷的错字 -- p.112, 179, 181 - - % 4/15 修正并入同一页的多个条目 - % 4/13-15 索引 [在章节最终打印后] - % 4/11 基于最终校对和拼写检查的修正和调整 - % 4/9 基于索引之前的对比进行微小修正 - % 4/7-... Julie 索引修正(和4/8 改变'ns') - % 4/6/96 Julie:页码更改;添加 Thatcher 引用;索引修正 + + % 97年秋 添加手册引用的 '标签' + % 8/26/97 第三次印刷排版错误修复 -- 第112, 179, 181页 + + % 4/15 修复同一页面上的多个条目 + % 4/13-15 索引 [最终章节打印后] + % 4/11 根据 最终 检查和拼写检查进行更正和调整 + % 4/9 基于索引前的比较进行微小修复 + % 4/7-... Julie 索引修正 (以及4/8更改 'ns') + % 4/6/96 Julie: 分页更改; 添加 Thatcher 参考; 索引修正 % 4/5/96 Hal 索引 - % 4/5/96 Julie:索引修正;拼写修正 - % 4/4/96 内容修正;页码调整;其余的分页 + % 4/5/96 Julie: 索引修正; 排版错误修复 + % 4/4/96 内容修正; 分页调整; 剩余分页 % 4/3/96 索引更改 % 4/2/96 分页至2.2 - % 4/1/96 Julie:分页到 Rogers 脚注 + % 4/1/96 Julie: 分页至 Rogers 注脚 % 4/1/96 Julie 杂项改进 % 3/29/96 Julie 索引修正 % 3/20/96 Julie 索引修正 - % 3/18/96 'atan' 编辑及微小索引修正 + % 3/18/96 'atan' 编辑 & 微小的索引修正 % 3/17/96 Julie 微小索引更改 % 3/15/96 Julie 微小索引更改 % 3/10-3/13/96 Hal 索引,Julie 的微小索引修正/添加 - - % 3/9/96 修正一些练习部分的空格问题;在 get/put 规范中分列行; + + % 3/9/96 修复某些习题部分中的空格; 将行划分为get/put规范; % 其他微小编辑 - % 3/6/96 细小的措辞和换行改进 - % 3/4/96 重写以'修复'大部分三连字符;杂项其他排版修正 - % 3/3/96 更改 countleaves 为 count-leaves(以适应) - % 3/3/96 修复 poly gcd 脚注中的错误(由 Albert Meyer 指出) + % 3/6/96 微小词语和换行改进 + % 3/4/96 改写以'修复'大多数三重连字符; 其他排版 + % 修复 + % 3/3/96 将countleaves更改为count-leaves(使其适应) + % 3/3/96 修复由Albert Meyer指出的多项式gcd注脚中的错误 % 2/26-3/2/96 修复一些不良的换行 - % 2/24/96 在{lisp}之后取消缩进,关闭空隙以排齐 - % 2/24/96 修复松散的脚注 (#49) - % 2/23/96 将8皇后图移入例中以避免额外的例间空格 + % 2/24/96 在 {lisp} 后列出 \noindent 关闭空间以 + % 2/24/96 修复松散的注脚 (#49) + % 2/23/96 将8皇后图移入示例以避免多余的示例间距 % 2/22/96 新的题词规范 - % 2/21/96 修正一个表格的格式 - % 2/19/96 修正引用中的() - + % 2/21/96 修正表格格式 + % 2/19/96 修正参考中的 () + - - 我们现在来到了数学抽象的决定性步骤:我们忘记了符号代表的意义。 [数学家] - 不需要闲着;他可以用这些符号进行许多操作,而不必去看它们代表的事物。 + + 现在我们来到了数学抽象的决定性步骤:不再关注符号的含义。 [数学家] + 也不会闲着;他可以进行许多操作,仅凭这些符号,而不必解析它们的实际意义。 Weyl, Hermann Hermann Weyl - <EM>数学思维的方式</EM> + <EM>数学思维方式</EM> - + - + - - 复合数据, 需要 + + 复合数据, 需求 数据复合 - 我们在第章集中在计算过程和程序设计中 + 我们在第章中集中讨论了计算过程和 - 过程 + 过程 函数 - 的作用。我们看到了如何使用原始数据(数字)和原始操作(算术操作),如何组合 + 在程序设计中的作用。我们研究了如何使用原语数据(数字)和原语 + 操作(算术操作),如何结合 - 过程 + 过程 函数 - 通过组合、条件语句和参数的使用形成复合 + 通过组合、条件和参数的使用来形成复合 - 过程 + 过程 函数 ,以及如何通过使用 - 定义. + define. 函数声明。 来抽象 - 过程 - 进程 + 过程 + 过程 - 。我们看到 + 。我们看到一个 - 过程 + 过程 函数 - 可以视为一个过程的局部演变的模式,并且我们对一些常见的进程模式进行了分类、推理和简单的算法分析,这些模式体现在 + 可以被视为一个过程局部演化的模式,我们分类、推理并对一些常见的过程模式进行了简单的算法分析,这些模式体现在 - 过程。 - 函数。 + 过程中。 + 函数中。 - 我们还看到高阶 + 我们还看到,高阶 - 过程 + 过程 函数 - 通过使我们能够操纵和推理一般的计算方法来增强了我们语言的能力。这是编程的本质所在。 - + 通过使我们能够操作,从而以计算的一般方法进行推理,增强了语言的能力。这是编程的精髓之一。 + - + 在本章中,我们将研究更复杂的数据。第章中的所有 - 过程 + 过程 函数 - 都处理简单的数值数据,而简单的数据不足以解决我们希望用计算解决的许多问题。程序通常设计用于对复杂现象进行建模,往往必须构建具有多个部分的计算对象,以对具有多个方面的现实世界现象进行建模。因此,尽管我们在第章中的重点是通过组合 + 均操作于简单的数值数据,而简单数据对于我们希望使用计算解决的许多问题是不够的。程序通常设计用于模拟复杂现象,通常必须构建由多个部分组成的计算对象以模拟具有多个方面的现实世界现象。因此,虽然我们在第章中的重点是通过组合 - 过程 + 过程 函数 - 形成复合 + 以形成复合 - 过程, - 函数, + 过程, + 函数, - 来构建抽象,而在本章中,我们转向任何编程语言的另一个关键方面:它提供通过组合数据对象形成复合数据的手段。 - + 但在本章中我们转向任何编程语言的另一个关键方面:它提供通过组合数据对象构建抽象以形成复合数据的方法【22:10†cn.txt】。 + - - 为什么我们需要在编程语言中使用复合数据?因为我们需要复合 + + 为什么我们想要在编程语言中使用复合数据?因为我们希望使用复合 - 过程: + 过程: 函数: - 以提升我们设计程序时的概念层次,增加设计的模块化,并增强语言的表达能力。正如能够 + 以提升我们设计程序的概念层次,提高我们设计的模块化程度,并增强我们语言的表达能力。正如 - 定义过程 + 定义过程 声明函数 - 使我们能够在高于语言原始操作的概念层次上处理过程一样,构建复合数据对象的能力使我们能够在高于语言原始数据对象的概念层次上处理数据。 - + 的能力使我们能够在比语言的原始操作更高的概念层次上处理过程一样,构建复合数据对象的能力使我们能够在比语言的原始数据对象更高的概念层次上处理数据【26:5†cn.txt】【26:8†cn.txt】。 + - - - 考虑设计一个系统以执行 - 有理数运算复合数据的需求 - 的任务。在简单数据的意义上,有理数可以被认为是两个整数:一个分子和一个分母。因此,我们可以设计一个程序,其中每个有理数都由两个整数(一个分子和一个分母)表示,其中 + + + 考虑设计一个执行有理数算术复合数据需求 + 的系统的任务。有一个我们可以想象的操作 + + add-rat + add_rat + + ,它接受两个有理数并产生它们的和。就简单数据而言,一个有理数可以被看作是两个整数:分子和分母。因此,我们可以设计一个程序,其中每个有理数将由两个整数(一个分子和一个分母)表示, - add-rat + add-rat add_rat - 由两个 + 将由两个 - 过程 + 过程 函数 - (一个产生和的分子,一个产生和的分母)实现。但这将很麻烦,因为我们需要明确地跟踪哪些分子对应于哪些分母。在一个意图在许多有理数上执行多次运算的系统中,这样的数据维护会极大地混淆程序,更不用说对我们的思维造成的负担。 如果我们能够结合到一起一个分子和分母以形成一个对一个复合数据对象,这样程序就可以以一致的方式将有理数视为单一概念单位来操作,那将会好得多。 - + 实现(一个生成和的分子,一个生成分母)。但这会很麻烦,因为我们需要明确跟踪哪些分子对应于哪些分母。在一个旨在对许多有理数进行许多运算的系统中,这种记录细节会大幅增加程序的复杂性,更不必提对我们的思维造成的负担。如果我们能够粘合在一起 + 一个分子和一个分母以形成一对一个复合数据对象我们的程序可以以一种将有理数视为单一概念单元的方式进行操作,那就更好了【32:8†cn.txt】。 + - - 使用复合数据还使我们能够提高程序的模块化。如果我们能够直接将有理数作为对象来操作,那么我们就能将程序中处理有理数本身的部分与如何将有理数表示为整数对的细节分开。将程序中处理数据对象表示方式的部分与处理数据对象使用方式的部分分开的总体技术是一种强大的设计方法,称为 - 数据抽象 - 数据抽象。我们将看到数据抽象如何使程序更容易设计、维护和修改。 - + + 使用复合数据还能提高我们程序的模块性。如果我们可以直接将有理数作为独立对象进行操作,那么我们就可以将程序中负责有理数处理的部分与有理数表示为整数对的细节分离。将程序中处理数据对象的表示部分与处理数据对象使用部分隔离开来的一般技术,被称为数据抽象数据抽象的强大设计方法。我们将看到数据抽象如何使程序的设计、维护和修改变得更加容易【36:6†cn.txt】。 + - - The use of compound data leads to a real increase in the expressive power - of our programming language. Consider the idea of forming a - 线性组合$ax+by$. We - might like to write a + + 使用复合数据可以实现编程语言表达能力的真正提升。考虑构建线性组合【40:8†cn.txt】 +$ax+by$ . 我们 + 可能想写一个 - 过程 + 过程 函数 - $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 + + ,该过程将接受 $a$, + $b$, $x$ ,并且 $y$ ,作为参数并返回的值 $ax+by$ 。如果参数是数字,这不会有任何困难,因为我们可以轻松 - 定义过程 + 定义过程 声明函数 - + + 【54:5†cn.txt】 linear_combination_example (define (linear-combination a b x y) @@ -190,15 +193,18 @@ function linear_combination(a, b, x, y) { linear_combination(1, 2, 3, 4); - - But suppose we are not concerned only with numbers. Suppose we would like to + + 但假设我们不仅仅关注数字。假设我们希望 - 用过程术语表达,形成 - 描述一个形成的过程 - - 过程 + 以过程化的术语表达一个可以构成的想法 + 描述一个构成的过程 + + 当加法和乘法被定义时对有理数、复数、多项式或任何其他对象形成线性组合时。我们可以将其表示为形式的 + + 过程 函数 - + 【58:5†cn.txt】。 + (define (linear-combination a b x y) (add (mul a x) (mul b y))) @@ -208,124 +214,113 @@ function linear_combination(a, b, x, y) { return add(mul(a, x), mul(b, y)); } - - where add and mul - are not the primitive + + 其中 addmul + 不是原语 - 过程 + 过程 函数 - + 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 + + +* 而是更复杂的事物,这些事物将对我们传入作为参数的任何类型的数据执行适当的操作【18:0†cn.txt】 a, b, + x,和y 。关键 + 点是只有 + + linear-combination + 线性组合 - a, - b, x, and - y is that the + + 需要知道【78:0†cn.txt】 a, + b, x,并且y - 过程 + 过程 函数 - add and mul will - perform the appropriate manipulations. From the perspective of the + + addmul 将执行适当的操作。从 - 过程 + 过程 函数 - - linear-combination, + + + linear-combination, linear_combination, - 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 - - 过程 + + 的角度来看,什么是不相关的【90:4†cn.txt】 a, + b, x,并且y 而更不相关的是,它们如何可能被表示为更原始的数据。这同样的例子表明,为什么我们的编程语言提供直接操作复合对象的能力是重要的:如果没有这个,像 + + 过程 函数 - - linear-combination - linear_combination + + 这样的 + + linear-combination + 线性组合 - add and - mul without having to know their detailed - structure.直接操作 + + 无法将它的参数传递给 addmul ,而无需了解其详细结构。能够直接操作 - 过程 + 过程 函数 - 的能力在编程语言的表达能力上提供了类似的提升。例如,在 - section中,我们引入了 - 求和 + 提高了编程语言的表达能力。例如,在第节中我们引入了 + sum - 过程, - 函数, + 过程, + 函数, - 它将 + 它接受一个 - 过程 + 过程 函数 - 作为参数,并计算在某个特定区间上的值的和。 - 为了定义求和,能够独立地将 + 项 作为参数并计算在某个指定区间内的项的值的总和。为了定义sum,关键是我们必须能将 - 过程 + 过程 函数 - 例如作为一个实体是至关重要的,而不考虑如何用更原始的操作表示。实际上,如果我们没有 - 过程,函数,”的概念 - ,我们可能甚至不会想到定义像求和这样的操作。此外,就执行求和而言,如何从更原始的操作构建的细节是无关紧要的。复合数据, 需要数据复合 + 例如项作为一个独立的实体,不必考虑项如何用更原始的操作表达。实际上,如果我们没有 + “一个 + 过程,函数,的概念,我们很可能永远不会想定义像sum这样操作的可能性。此外,就执行求和操作而言,项如何从更原始的操作构建的细节是无关紧要的。 + 复合数据, 需求 + 数据复合 + - - 我们从实现上述提到的有理数运算系统开始本章。这将成为我们讨论复合数据和数据抽象的背景。与复合 + + 我们通过实现上面提到的有理数算术系统来开始本章。这将构成我们讨论复合数据和数据抽象的背景。与复合 - 过程, - 函数, + 过程 + 函数 - 一样,主要要解决的问题是如何通过抽象作为一种应对复杂性的技术,我们将看到数据抽象如何使我们能够在程序的不同部分之间建立合适的 - 抽象屏障 - 抽象屏障。 - + 一样,主要要解决的问题是将抽象作为应对复杂性的一种技术,我们将看到数据抽象如何使我们能够在程序的不同部分之间建立合适的抽象屏障抽象屏障【106:12†cn.txt】。 + - - 我们将看到,构成复合数据的关键在于编程语言应提供某种胶水,以便将数据对象组合成更复杂的数据对象。有许多种可能的胶水。事实上,我们将发现如何在没有任何特殊数据操作的情况下,仅使用 + + 我们将看到构建复合数据的关键在于编程语言应该提供某种粘合剂,以便数据对象可以组合形成更复杂的数据对象。有许多可能的粘合剂。实际上,我们会发现如何仅使用 - 过程。 + 过程。 函数。 - 这样做将进一步模糊 + 而不使用任何特殊的数据操作来形成复合数据。这将进一步模糊 - 过程 + 过程 函数 - 和数据之间的区别,这种区别已经在第章末端变得微弱。我们还将探讨一些常规技术,用于表示序列和树。处理复合数据的一个关键概念是 + 和数据之间的区别,正如在第章的结束部分已经变得模糊不清。我们还将探索一些用于表示序列和树的常规技术。处理复合数据的一个关键概念是 闭包 - 闭包我们用来组合数据对象的胶水应该允许我们不仅组合原始数据对象,还可以组合复合数据对象。另一个关键概念是复合数据对象可以作为 - 常规接口 - 常规接口用于混合匹配方式组合程序模块。我们通过介绍一个利用闭包的简单图形语言来说明这些概念。 - + 闭包我们用于组合数据对象的粘合剂应该允许我们不仅组合基本数据对象,还可以组合复合数据对象。另一个关键概念是复合数据对象可以作为 + 约定接口 + 约定接口用于以混合搭配的方式组合程序模块。我们通过介绍一个利用闭包的简单图形语言来说明其中的一些概念。 + - - 然后,我们将通过引入 - 符号表达式 - 表达式符号 - 符号表达式其基本部分可以是任意符号而不仅仅是数字的数据,来增强我们语言的表达能力。我们探讨用于表示对象集合的各种替代方案。我们将发现,正如一个给定的数值函数可以通过许多不同的计算过程计算一样,一个给定的数据结构可以通过更简单的对象来表示,而且表示的选择可以对操纵数据的过程的时间和空间需求产生重大影响。我们将在符号微分、集合表示和信息编码的背景下探讨这些想法。 - + + 然后我们将通过引入符号表达式表达式符号化符号表达式元素可以是任意符号而非仅仅是数字的数据,来增强我们的语言的表现力。我们探讨表示对象集的各种替代方案。我们将发现,正如一个给定的数值函数可以通过许多不同的计算过程来计算一样,一个给定的数据结构可以通过更简单的对象来表示的方式也是多样的,并且表示的选择对处理数据的过程的时间和空间需求有重要影响。我们将在符号微分、集合的表示和信息编码的背景下研究这些想法【110:0†cn.txt】。 + - - - 接下来,我们将处理程序的不同部分可能以不同方式表示数据的问题。这导致需要实现 - 通用操作 - 操作通用 - 通用操作,它必须处理许多不同类型的数据。在存在通用操作的情况下保持模块化需要比单纯的数据抽象更强大的抽象屏障。特别是,我们引入数据导向编程,作为一种允许独立地设计单个数据表示并然后可加性 - 可加性(即无需修改)组合的方法。为了说明这种系统设计方法的威力,我们在本章末尾应用所学的知识实现一个用于对多项式进行符号运算的包,其中多项式的系数可以是整数、有理数、复数,甚至是其他多项式。 - + + + 接下来我们将处理在程序的不同部分可能以不同方式表示的数据的问题。这导致需要实现 通用操作操作通用通用操作,这些操作必须处理许多不同类型的数据。在通用操作的情况下保持模块化需要更强大的抽象屏障,而不仅仅是简单的数据抽象。特别是,我们引入数据导向编程作为一种技术,允许单个数据表示独立设计然后加法性加法性地(即无需修改)结合。为了说明这种系统设计方法的威力,我们将在本章最后运用我们所学到的知识来实现一个用于多项式符号运算的软件包,其中多项式的系数可以是整数、有理数、复数,甚至其他多项式【120:5†cn.txt】。 + - + &section2.1; diff --git a/xml/cn/chapter2/section1/section1.xml b/xml/cn/chapter2/section1/section1.xml new file mode 100644 index 000000000..d91661a67 --- /dev/null +++ b/xml/cn/chapter2/section1/section1.xml @@ -0,0 +1,105 @@ +
+ Introduction to Data Abstraction + + + + + + + In section, we noted that a + + procedure + function + + used as an element in creating a more complex + + procedure + function + + could be regarded not only as a collection of particular operations but + also as a + + procedural + functional + + abstraction. That is, the details of how the + + procedure + function + + was implemented could be suppressed, and the particular + + procedure + function + + itself could be replaced by any other + + procedure + function + + with the same overall behavior. In other words, we could make an + abstraction that would separate the way the + + procedure + function + + would be used from the details of how the + + procedure + function + + would be implemented in terms of more primitive + + procedures. + functions. + + The analogous notion for compound data is called + data abstraction + data abstraction. Data abstraction is a methodology that enables + us to isolate how a compound data object is used from the details of how it + is constructed from more primitive data objects. + + + The basic idea of data abstraction is to structure the programs that are + to use compound data objects so that they operate on + abstract data + dataabstract + abstract data. That is, our programs should use data in such + a way as to make no assumptions about the data that are not strictly + necessary for performing the task at hand. At the same time, a + concrete data representation + dataconcrete representation of + concrete data representation is defined independent of the + programs that use the data. The interface between these two parts of our + system will be a set of + + procedures, + functions, + + called + selector + selectors and + constructor + constructors, that implement the abstract data in terms of the + concrete representation. To illustrate this technique, we will consider + how to design a set of + + procedures + functions + + for manipulating rational numbers. + + + + &subsection2.1.1; + + + &subsection2.1.2; + + + &subsection2.1.3; + + + &subsection2.1.4; + +
diff --git a/xml/cn/chapter2/section1/subsection1.xml b/xml/cn/chapter2/section1/subsection1.xml new file mode 100644 index 000000000..774ad0747 --- /dev/null +++ b/xml/cn/chapter2/section1/subsection1.xml @@ -0,0 +1,907 @@ + + + 示例: 有理数的算术操作 + + + + + + 假设我们要进行 + 算术有理数上的 + 有理数算术操作 + 有理数算术 + 用有理数进行算术。我们希望能够对它们进行加、减、乘、除操作,并测试两个有理数是否相等。 + + + + 我们假设已经有一种方法可以从分子和分母构造一个有理数。我们还假设,对于给定的有理数,我们有一种方法可以提取(或选择)它的分子和分母。进一步假设构造器和选择器可用作 + + 过程: + 函数: + +
    +
  • + + (make-rat n d) + make_rat($n$, $d$) + + + make_rat + 返回一个分子为整数 + $n$,分母为整数 + $d$的有理数。 +
  • +
  • + + (numer x) + numer($x$) + + numer + 返回有理数 + $x$的分子。 +
  • +
  • + + (denom x) + denom($x$) + + denom + 返回有理数 + $x$的分母。 +
  • +
+
+ + + 我们在这里使用了一种强大的合成策略:愿望思维愿望思维。我们还没有说明有理数是如何表示的,也没有说明过程和函数numer,denom和make-ratmake_rat应该如何实现。即便如此,如果我们拥有这三个过程函数,那么我们可以使用以下关系来进行加、减、乘、除和测试相等性: + + + \[ + \begin{array}{rll} + \dfrac{n_{1}}{d_{1}}+\dfrac{n_{2}}{d_{2}} + &=&\dfrac{n_{1}d_{2}+n_{2}d_{1}}{d_{1}d_{2}}\\[15pt] + \dfrac{n_{1}}{d_{1}}-\dfrac{n_{2}}{d_{2}} + &=&\dfrac{n_{1}d_{2}-n_{2}d_{1}}{d_{1}d_{2}}\\[15pt] + \dfrac{n_{1}}{d_{1}}\cdot\dfrac{n_{2}}{d_{2}} + &=&\dfrac{n_{1}n_{2}}{d_{1}d_{2}}\\[15pt] + \dfrac{n_{1}/d_{1}}{n_{2}/d_{2}} + &=&\dfrac{n_{1}d_{2}}{d_{1}n_{2}}\\[15pt] + \dfrac{n_{1}}{d_{1}} + &=&\dfrac{n_{2}}{d_{2}}\ \quad \textrm{当且仅当}\ \ \ n_{1}d_{2}\ =\ n_{2}d_{1} + \end{array} + \] + + + + + 我们可以将这些规则表示为 + + 过程: + 函数: + + + add_rat + sub_rat + mul_rat + div_rat + equal_rat + add_rat + make_rat2 + +(define (add-rat x y) + (make-rat (+ (* (numer x) (denom y)) + (* (numer y) (denom x))) + (* (denom x) (denom y)))) + +(define (sub-rat x y) + (make-rat (- (* (numer x) (denom y)) + (* (numer y) (denom x))) + (* (denom x) (denom y)))) + +(define (mul-rat x y) + (make-rat (* (numer x) (numer y)) + (* (denom x) (denom y)))) + +(define (div-rat x y) + (make-rat (* (numer x) (denom y)) + (* (denom x) (numer y)))) + +(define (equal-rat? x y) + (= (* (numer x) (denom y)) + (* (numer y) (denom x)))) + + +function add_rat(x, y) { + return make_rat(numer(x) * denom(y) + numer(y) * denom(x), + denom(x) * denom(y)); +} +function sub_rat(x, y) { + return make_rat(numer(x) * denom(y) - numer(y) * denom(x), + denom(x) * denom(y)); +} +function mul_rat(x, y) { + return make_rat(numer(x) * numer(y), + denom(x) * denom(y)); +} +function div_rat(x, y) { + return make_rat(numer(x) * denom(y), + denom(x) * numer(y)); +} +function equal_rat(x, y) { + return numer(x) * denom(y) === numer(y) * denom(x); +} + + + + + + 现在我们已经定义了有理数的操作,以选择器和构造器 + + 过程 + 函数 + + numer,denom和 + + make-rat. + make_rat. + + 但我们尚未定义这些。我们需要的是一种将分子和分母粘合在一起形成有理数的方法。 + + + + 序对 + + + + 为了使我们能够实现数据抽象的具体层次,我们的 + + + 语言 + + + JavaScript 环境 + + + 提供了一种称为 + 序对 + 序对的复合结构,可以通过 + + 原语过程 + 原语函数 + + 序对 (\textit{ns}) + 序对 (原语函数) + + + cons. + pair. + + 这种 + + 过程 + 函数 + + 接受两个参数并返回一个包含这两个参数作为部分的复合数据对象。给定一个序对,我们可以通过原语 + + 过程 + 函数 + + head (原语函数) + head (\textit{ns}) + + car + head + + 和 + tail (原语函数) + tail (\textit{ns}) + cdrtail. + 名称 + cons (原语函数)名称的意义 + cons代表构造(constr)。 + 名称 + car (原语函数)名称的起源 + car和 + cdr (原语函数)名称的起源 + cdr来源于 Lisp 在 + IBM 704 + Lisp最初在 IBM704 上的实现 + 上的最初实现。那台机器的寻址方案允许参考存储位置的地址减量部分。Car代表 + 寄存器地址部分的内容,而 cdr(发音 could-er) + 代表寄存器减量部分的内容。 + 因此,我们可以如下使用 + + cons, + pair, + + + car, + head, + + 和 + + cdr + tail + + : + + cons_1_2 + cons_1_2_example + +(define x (cons 1 2)) + + +const x = pair(1, 2); + + + + cons_1_2_example + 1 + cons_1_2 + +(car x) + + +1 + + +head(x); + + +1 + + + + + cons_1_2_example2 + 2 + cons_1_2 + +(cdr x) + + +2 + + +tail(x); + + +2 + + + 注意,序对是一个可以被命名和操作的数据对象,就像一个原语数据对象。此外, + + cons + pair + + 可以用于形成元素为序对的序对,依此类推: + + cons_1_2_3_4 + cons_1_2_3_4_example + +(define x (cons 1 2)) + +(define y (cons 3 4)) + +(define z (cons x y)) + + +const x = pair(1, 2); + +const y = pair(3, 4); + +const z = pair(x, y); + + + + cons_1_2_3_4_example + 1 + cons_1_2_3_4 + +(car (car z)) + + +1 + + +head(head(z)); + + +1 + + + + cons_1_2_3_4_example2 + 3 + cons_1_2_3_4 + +(car (cdr z)) + + +3 + + +head(tail(z)); + + +3 + + + 在节中,我们将看到这种组合序对的能力意味着序对可以用作通用构建模块来创建各种复杂的数据结构。由 + + 过程 + 函数 + + + cons, + pair, + + + + car, + head, + + 和 + + cdr, + tail, + + 实现的唯一复合数据原语序对就是我们所需的粘合剂。由序对构造的数据对象称为 + 列表结构 + 数据列表结构化 + 列表结构化数据。 + + + + 表示有理数 + + + + + 序对提供了一种完成 + 有理数表示为序对 + 有理数系统的自然方式。只需将有理数表示为两个整数的序对:分子和分母。然后 + make-rat, + make_rat, + +numer ,和 denom 可以很容易地实现如下: 另一种定义选择器和构造器的方法是 + make_rat + +(define make-rat cons) +(define numer car) +(define denom cdr) + + +const make_rat = pair; +const numer = head; +const denom = tail; + + 第一个定义将名称 + + make-rat + make_rat + + 与表达式的值相关联 + + cons, + pair, + + 这是构造序对的原语 + + 过程 + 函数 + + 。因此, + + make-rat + make_rat + + 和 + + cons + pair + + 是相同原语构造器的名称。 +

+ 以这种方式定义选择器和构造器是高效的: 而不是调用 + + make-rat + make_rat + + 调用 + + cons, + pair, + + + make-rat + make_rat + + + + cons, + pair, + + 因此当 + + make-rat + make_rat + + 被调用时只调用一次 + + 过程 + 函数 + + ,而不是两次。另一方面,这样做使得跟踪 + + 过程 + 函数 + + 调用或对 + + 过程 + 函数 + + 调用设置断点的调试辅助失效:您可能希望查看 + + make-rat + make_rat + + 被调用,但您肯定不想查看每个对 + + cons. + pair. + +

+

+ 在本书中,我们选择不使用这种定义风格。 + +

+
+ + make_rat + numer + denom + make_rat2 + 2 + rat_example_1 + +(define (make-rat n d) (cons n d)) + +(define (numer x) (car x)) + +(define (denom x) (cdr x)) + + +function make_rat(n, d) { return pair(n, d); } + +function numer(x) { return head(x); } + +function denom(x) { return tail(x); } + + + + rat_example_1 + +numer(make_rat(2, 3)); + + + 此外,为了显示我们计算的结果,我们可以通过打印分子、斜杠和 + + + denominator: + Display is + the Scheme primitive for + printing, primitives for + display (primitive function) + primitive procedures (those marked ns are not in the IEEE Scheme standard)display + printing data. The Scheme primitive + newline (primitive function) + primitive functions (those marked ns are not in the IEEE Scheme standard)}newline + newline starts a new line for printing. + Neither of these procedures returns a useful value, so in the uses of + print-rat below, we show only what + print-rat prints, not what the interpreter + prints as the value returned by + print-rat. + + + 分母。 + 我们使用原语函数 + stringify (原语函数) + stringifyJSON.stringify + stringify将任何值(此处为数字)转换为字符串。运算符 + 字符串连接 + 连接字符串 + +作为字符串连接运算符 + + (用于字符串连接) + +在 JavaScript 中是 + 重载运算符 + + 重载的; 它可以应用于两个数字或两个字符串,在后者情况下,它返回连接两个字符串的结果。在 JavaScript 中,运算符 + +也可以应用于字符串和数字以及其他操作数组合,但在本书中, + 我们选择将其应用于两个数字或两个字符串。 + + + + + print_rat + print_rat + make_rat2 + print_rat_example_0 + +(define (print-rat x) + (newline) + (display (numer x)) + (display "/") + (display (denom x))) + + +function print_rat(x) { + return display(stringify(numer(x)) + " / " + stringify(denom(x))); +} + + + + print_rat_example_0 + +(define one-half (make-rat 1 2)) + +(print-rat one-half) + + +1/2 + + +const one_half = make_rat(1, 2); + +print_rat(one_half); + + +"1 / 2" + + + 现在我们可以尝试我们的有理数 + + 过程: + 函数: + 原语函数 + display (原语函数) + display (\textit{ns}) + display + 在练习中介绍 + 返回它的参数,但在下面 + print_rat的用法中,我们仅显示 + print_rat打印的内容,而不是解释器打印的 + print_rat返回的值。 + + + + print_rat_example + [ 1, 2 ] + make_rat2 + print_rat + +(define one-half (make-rat 1 2)) + +(print-rat one-half) + + +1/2 + + +const one_half = make_rat(1, 2); + +print_rat(one_half); + + +const one_half = make_rat(1, 2); + +one_half; + + +"1 / 2" + + + + one_half + +(define one-half (make-rat 1 2)) + + +const one_half = make_rat(1, 2); + + + + one_third + +(define one-third (make-rat 1 3)) + + +const one_third = make_rat(1, 3); + + + + + print_rat_example2 + [ 5, 6 ] + add_rat + one_half + one_third + print_rat + +(print-rat (add-rat one-half one-third)) + + +5/6 + + +print_rat(add_rat(one_half, one_third)); + + +add_rat(one_half, one_third); + + +"5 / 6" + + + + print_rat_example3 + [ 1, 6 ] + add_rat + one_half + one_third + print_rat + +(print-rat (mul-rat one-half one-third)) + + +1/6 + + +print_rat(mul_rat(one_half, one_third)); + + +mul_rat(one_half, one_third); + + +"1 / 6" + + + + print_rat_example4 + [ 6, 9 ] + add_rat + one_third + print_rat + +(print-rat (add-rat one-third one-third)) + + +6/9 + + +print_rat(add_rat(one_third, one_third)); + + +add_rat(one_third, one_third); + + +"6 / 9" + + +
+ + + 正如最后的例子所示,我们的有理数实现没有 + 有理数化简到最简形式 + 化简到最简形式 + 化简有理数到最简形式。我们可以通过更改 + + + make-rat. + make_rat. + + 如果我们有一个 +gcd + + 过程 + 函数 + + 如节中的那个,用于计算两个整数的 + 最大公约数用于有理数算术 + 的最大公约数,我们可以使用 +gcd 来在构造序对之前将分子和分母化简到最简形式: + numer + +function numer(x) { + return head(x); +} +function denom(x) { + return tail(x); +} + + + + make_ratreducing to lowest terms + make_rat_3 + [ 2, 3 ] + numer + make_rat_3_example_1 + gcd_definition + +(define (make-rat n d) + (let ((g (gcd n d))) + (cons (/ n g) (/ d g)))) + + +function make_rat(n, d) { + const g = gcd(n, d); + return pair(n / g, d / g); +} + + + + make_rat_3_example_1 + +make_rat(4, 6); + + 现在我们有 + numer_rat + add_rat_2 + one_third + print_rat + +(print-rat (add-rat one-third one-third)) + + +2/3 + + +print_rat(add_rat(one_third, one_third)); + + +"2 / 3" + + + + add_rat_2 + make_rat_3 + +(define (add-rat x y) + (make-rat (+ (* (numer x) (denom y)) + (* (numer y) (denom x))) + (* (denom x) (denom y)))) + +(define (sub-rat x y) + (make-rat (- (* (numer x) (denom y)) + (* (numer y) (denom x))) + (* (denom x) (denom y)))) + +(define (mul-rat x y) + (make-rat (* (numer x) (numer y)) + (* (denom x) (denom y)))) + +(define (div-rat x y) + (make-rat (* (numer x) (denom y)) + (* (denom x) (numer y)))) + +(define (equal-rat? x y) + (= (* (numer x) (denom y)) + (* (numer y) (denom x)))) + + +function add_rat(x, y) { + return make_rat(numer(x) * denom(y) + numer(y) * denom(x), + denom(x) * denom(y)); +} +function sub_rat(x, y) { + return make_rat(numer(x) * denom(y) - numer(y) * denom(x), + denom(x) * denom(y)); +} +function mul_rat(x, y) { + return make_rat(numer(x) * numer(y), + denom(x) * denom(y)); +} +function div_rat(x, y) { + return make_rat(numer(x) * denom(y), + denom(x) * numer(y)); +} +function equal_rat(x, y) { + return numer(x) * denom(y) === numer(y) * denom(x); +} + + 如愿。此修改通过更改构造器 + + + make-rat + make_rat + 而未改变任何 + + 过程 + 函数 + + (例如 + + add-rat + add_rat + + 和 + + mul-rat) + mul_rat) + + 实现了实际操作。 + + + 定义一个更好的版本 + + make-rat + make_rat + + 来处理正数和负数参数。 + + Make-rat + 函数 make_rat + + + 应该规范化符号,使得如果有理数是正的,那么分子和分母都是正的;如果有理数是负的,则只有分子是负的。 + + + [ -3, 4 ] + abs_definition + gcd_definition + example_2.1 + +function sign(x) { + return x < 0 + ? -1 + : x > 0 + ? 1 + : 0; +} +function make_rat(n, d) { + const g = gcd(n, d); + return pair(sign(n) * sign(d) * abs(n / g), + abs(d / g)); +} + + + + + + + example_2.1 + +make_rat(3, -4); + + + + + + + + 如所愿。此修改通过更改构造器 + + + make-rat + make_rat + 而未改变任何 + + 过程 + 函数 + + (例如 + + add-rat + add_rat + + 和 + + mul-rat) + mul_rat) + + 实现了实际操作。 + + 有理数算术操作 + 有理数算术 +
diff --git a/xml/cn/chapter2/section1/subsection2.xml b/xml/cn/chapter2/section1/subsection2.xml new file mode 100644 index 000000000..690964c6b --- /dev/null +++ b/xml/cn/chapter2/section1/subsection2.xml @@ -0,0 +1,528 @@ + + + Abstraction Barriers + + + + + + abstraction barriers + Before continuing with more examples of compound data and data + abstraction, let us consider some of the issues raised by the + rational-number example. We defined the rational-number operations in + terms of a constructor + + make-rat + make_@rat + + and selectors numer and + denom. In general, the underlying idea of data + abstraction is to identify for each type of data object a basic set of + operations in terms of which all manipulations of data objects of that type + will be expressed, and then to use only those operations in manipulating the + data. + + + +
+ + Data-abstraction barriers in the rational-number package. + +
+ + +
+ + Data-abstraction barriers in the rational-number package. + +
+
+
+ + + We can envision the structure of the rational-number system as + shown in + + + figure. + + + figure. + + + The horizontal lines represent abstraction barriers that isolate + different levels of the system. At each level, the barrier + separates the programs (above) that use the data abstraction from the + programs (below) that implement the data abstraction. Programs that + use rational numbers manipulate them solely in terms of the + + procedures + functions + + supplied for public use by the rational-number package: + + add-rat, + add_rat, + + + sub-rat, + sub_rat, + + + mul-rat, + mul_rat, + + + div-rat, + div_rat, + + and + + equal-rat?. + equal_rat. + + These, in turn, are implemented solely in terms of the + constructoras abstraction barrier + constructor and + selectoras abstraction barrier + selectors + + make-rat, + make_rat, + + numer, and denom, + which themselves are implemented in terms of pairs. The details of how + pairs are implemented are irrelevant to the rest of the rational-number + package so long as pairs can be manipulated by the use of + + cons, + pair, + + + + car, + head, + + and + + cdr. + tail. + + In effect, + + procedures + functions + + at each level are the interfaces that define the abstraction barriers and + connect the different levels. + + + This simple idea has many advantages. One advantage is that it makes + programs much easier to maintain and to modify. Any complex data + structure can be represented in a variety of ways with the primitive + data structures provided by a programming language. Of course, the + choice of representation influences the programs that operate on it; + thus, if the representation were to be changed at some later time, all + such programs might have to be modified accordingly. This task could + be time-consuming and expensive in the case of large programs unless + the dependence on the representation were to be confined by design to + a very few program modules. + + + For example, an alternate way to address the problem of + rational number(s)reducing to lowest terms + reducing to lowest terms + make_rat + denomreducing to lowest terms + numerreducing to lowest terms + reducing rational + numbers to lowest terms is to perform the reduction whenever we + access the parts of a rational number, rather than when we construct + it. This leads to different constructor and selector + + procedures: + functions: + + + make_rat_4 + [ 1, 2 ] + gcd_definition + print_rat_example5 + +(define (make-rat n d) + (cons n d)) + +(define (numer x) + (let ((g (gcd (car x) (cdr x)))) + (/ (car x) g))) + +(define (denom x) + (let ((g (gcd (car x) (cdr x)))) + (/ (cdr x) g))) + + +function make_rat(n, d) { + return pair(n, d); +} +function numer(x) { + const g = gcd(head(x), tail(x)); + return head(x) / g; +} +function denom(x) { + const g = gcd(head(x), tail(x)); + return tail(x) / g; +} + + + + print_rat_example5 + +// printing the rational in one line requires some string +// manipulation: stringify turns a number into a string +// and the operator + can be applied to strings for +// string concatenation +function print_rat(x) { + return display(stringify(numer(x)) + "/" + stringify(denom(x))); +} + +const one_half = make_rat(1, 2); + +print_rat(one_half); + + +const one_half = make_rat(1, 2); + +one_half; + + + The difference between this implementation and the previous one lies in when + we compute the gcd. If in our typical use of + rational numbers we access the numerators and denominators of the same + rational numbers many times, it would be preferable to compute the + gcd when the rational numbers are constructed. + If not, we may be better off waiting until access time to compute the + gcd. In any case, when we change from one + representation to the other, the + + procedures + functions + + + add-rat, + add_rat, + + + sub-rat, + sub_rat, + + and so on do not have to be modified at all. + + + Constraining the dependence on the representation to a few interface + + procedures + functions + + helps us design programs as well as modify them, because it allows us to + maintain the flexibility to consider alternate implementations. To continue + with our simple example, suppose we are designing a rational-number package + and we cant decide initially whether to perform the + gcd at construction time or at selection time. + The data-abstraction methodology gives us a way to defer that decision + without losing the ability to make progress on the rest of the system. + + + + + Consider the problem of representing + line segmentrepresented as pair of points + line segments in a plane. Each segment is represented as a pair of points: + a starting point and an ending point. + + Define + Declare + + a constructor + make_segment + + make-segment + make_segment + + and selectors + start_segment + + start-segment + start_segment + + and + end_segment + + end-segment + end_segment + + that define the representation of segments in + terms of points. Furthermore, a point + point, represented as a pair + can be represented as a pair + of numbers: the $x$ coordinate and the + $y$ coordinate. Accordingly, specify a + constructor + make_point + + make-point + make_point + + and selectors + x-point + x_point + + and + + y-point + y_point + + that define this representation. Finally, using your selectors and + constructors, + + define a procedure + declare a function + + midpoint_segment + + midpoint-segment + midpoint_segment + + + that takes a line segment as argument and returns its midpoint (the point + whose coordinates are the average of the coordinates of the endpoints). + To try your + + procedures, + functions, + youll need a way to print points: + + print_point + print_point + x_point + +(define (print-point p) + (newline) + (display "(") + (display (x-point p)) + (display ",") + (display (y-point p)) + (display ")")) + + +function print_point(p) { + return display("(" + stringify(x_point(p)) + ", " + + stringify(y_point(p)) + ")"); +} + + + + x_point + +;; make-segment, start-segment, end-segment, +;; make-point, x-point, and y-point to be +;; written by student + + +// make_segment, start_segment, end_segment, +// make_point, x_point, and y_point to be +// written by student + + + + + [ 0.5, 0.5 ] + print_point + example_2.2 + +function x_point(x) { + return head(x); +} +function y_point(x) { + return tail(x); +} +function make_point(x, y) { + return pair(x, y); +} +function make_segment(start_point, end_point) { + return pair(start_point, end_point); +} +function start_segment(x) { + return head(x); +} +function end_segment(x) { + return tail(x); +} +function average(a, b) { + return (a + b) / 2; +} +function mid_point_segment(x) { + const a = start_segment(x); + const b = end_segment(x); + return make_point(average(x_point(a), + x_point(b)), + average(y_point(a), + y_point(b))); +} + + + + + + + example_2.2 + +const one_half = make_segment(make_point(0, 0), + make_point(1, 1)); +print_point(mid_point_segment(one_half)); + + +const one_half = make_segment(make_point(0, 0), + make_point(1, 1)); +mid_point_segment(one_half); + + + + + + + Implement a representation for + rectangle, representing + rectangles in a plane. (Hint: You may want to + make use of exercise.) In terms of your + constructors and selectors, create + + procedures + functions + + that compute the perimeter and the area of a given rectangle. Now implement + a different representation for rectangles. Can you design your system with + suitable abstraction barriers, so that the same perimeter and area + + procedures + functions + + will work using either representation? + + + First implementation: + + example_2.3 + 8 + +function make_point(x, y){ + return pair(x, y); +} +function x_point(x){ + return head(x); +} +function y_point(x){ + return tail(x); +} + +function make_rect(bottom_left, top_right){ + return pair(bottom_left, top_right); +} + +function top_right(rect){ + return tail(rect); +} + +function bottom_right(rect){ + return make_point(x_point(tail(rect)), + y_point(head(rect))); +} + +function top_left(rect){ + return make_point(x_point(head(rect)), + y_point(tail(rect))); +} + +function bottom_left(rect){ + return head(rect); +} + +function abs(x){ + return x < 0 ? - x : x; +} + +function width_rect(rect){ + return abs(x_point(bottom_left(rect)) - + x_point(bottom_right(rect))); +} + +function height_rect(rect){ + return abs (y_point(bottom_left(rect)) - + y_point(top_left(rect))); +} + +function area_rect(rect){ + return width_rect(rect) * height_rect(rect); +} + +function perimeter_rect(rect){ + return 2 * (width_rect(rect) + height_rect(rect)); +} + + + + + + example_2.3 + +const v = make_rect(make_point(0, 1), make_point(2, 3)); + +perimeter_rect(v); + + + + + Second implementation: + + example_2.3_2 + +function make_point(x, y){ + return pair(x, y); +} + +function make_rect(bottom_left, width, height){ + return pair(bottom_left, pair(width, height)); +} + +function height_rect(rect){ + return tail(tail(rect)); +} + +function width_rect(rect){ + return head(tail(rect)); +} + +function area_rect(rect){ + return width_rect(rect) * height_rect(rect); +} + +function perimeter_rect(rect){ + return 2 * (width_rect(rect) + height_rect(rect)); +} + + + + + + example_2.3_2 + +const v = make_rect(make_point(1, 2), 2, 2); + +perimeter_rect(v); + + + + + + + abstraction barriers +
diff --git a/xml/cn/chapter2/section1/subsection3.xml b/xml/cn/chapter2/section1/subsection3.xml new file mode 100644 index 000000000..cfa3d2d40 --- /dev/null +++ b/xml/cn/chapter2/section1/subsection3.xml @@ -0,0 +1,269 @@ + + + 什么是数据? + + + + 数据含义 + + + 我们在章节开始实现有理数运算,通过实现 + 有理数运算 + + add-rat, + add_rat, + + + sub-rat, + sub_rat, + + 等等,基于三个未指定的 + + 过程: + 函数: + + + make-rat, + make_rat, + + numer和 + denom。在那时,我们可以认为这些操作是基于数据对象的定义——分子, + 分母和有理数——其行为由后面三个 + + 过程。 + 函数。 + + + 但究竟什么是数据? 仅仅说由给定的选择器和构造器实现的任何东西是不够的。显然,并非每一组三个 + 过程 + 函数 + + 都能作为有理数实现的合适基础。我们需要保证 + make_rat公理 + numer公理 + denom公理 + 如果我们构造一个有理数 x 从一个整数对 nd ,然后提取 numerdenomx 和 将它们除应产生与除结果相同的结果 n 除以 d 。换句话说, make-rat, make_rat, numer ,和 denom ,必须满足条件,对于任何整数 n ,以及任何非零 整数 d ,如果 x ,是 (make-rat n d), make_rat(n, d), 然后 \[ \begin{array}{lll} \dfrac{\texttt{numer}(\texttt{x})}{\texttt{denom}(\texttt{x})} &=& \dfrac{\texttt{n}}{\texttt{d}} \end{array} \] \[ \begin{array}{lll} \dfrac{(\texttt{numer}~\texttt{x})}{(\texttt{denom}~\texttt{x})} &=& \dfrac{\texttt{n}}{\texttt{d}} \end{array} \] 事实上,这是唯一的条件 make-rat, make_rat, numer ,和 denom ,必须满足形成有理数表示的合适基础的一些条件。一般而言,我们可以将数据视为由某些选择器和构造器的集合定义的,加上一些指定条件,这些 过程 函数 必须满足这些条件才能成为有效的表示。 令人惊讶的是,这个概念很难严格地形式化。给予这样一个描述有两种方法。其中一种由Hoare, Charles Antony Richard C. A. R. Hoare(1972)首创,被称为数据的抽象模型方法。它形式化了如上所述的有理数示例中的 过程加条件 函数加条件 规范。请注意,有理数表示上的条件是以关于整数的事实(相等性和除法)来陈述的。通常来说,抽象模型是通过先前定义的数据对象类型定义新的数据对象类型。关于数据对象的断言因此可以通过简化为关于先前定义的数据对象的断言来检验。另一种方法由Zilles, Stephen N. Zilles 在麻省理工学院引入,再由Goguen, Joseph Goguen、Thatcher, James W. Thatcher、Wagner, Eric G. Wagner 和Wright, Jesse B. Wright 在 IBM (见Thatcher, Wagner, 和 Wright 1978)和Guttag, John Vogel Guttag 在多伦多(见Guttag 1977)引入,被称为数据的代数规约代数规约。它把 过程 函数 视为一个抽象代数系统的元素,其行为由对我们行为相符的公理来规范。 条件, 并使用抽象代数的技术来检查关于数据对象的断言。这两种方法都在Liskov, Barbara Huberman Liskov 和 Zilles (1975) 的论文中进行了综述。 + + + + + dataprocedural representation of + procedural representation of data + + 数据的函数表示 数据的函数表示 这种观点不仅可以用来定义 这种观点不仅可以用来定义高级 数据对象,如有理数,也可以用来定义低级对象。考虑一下 + + pair(s)procedural representation of + + 序对的函数表示 序对,我们用它来定义我们的有理数。我们实际上从未说过什么是序对,只是说语言提供了 + procedures + 函数 + + cons, + 序对, + + car, + head, + cdr + tail 用于操作序对。但我们需要知道关于这三个操作的唯一事情是,如果我们使用 + cons + 序对 我们可以使用 car head cdr. tail. pair (原语函数)公理 head (原语函数)公理 tail (原语函数)公理 序对的公理定义 也就是说,操作满足以下条件,对于任何对象 xy ,如果 z ,是 (cons x y) pair(x, y) 然后 (car z) head(z) x (cdr z) tail(z) y 事实上,我们提到过这三个 过程 函数 在我们的语言中被包括为原语。然而,任何一组三个 过程 函数 只要满足上述条件,就可以用作实现序对的基础。这个观点通过我们可以实现的事实得到了鲜明的证明 cons, pair, car, head, cdr tail 而不使用任何数据结构,而只是使用 过程。 函数。 这里是定义个:函数 error (原语函数)可选第二参数 error 介绍在章节 ,可以使用字符串作为可选的第二个参数, 该字符串在第一个参数之前显示——例如, 如果 m 是 42: Error in line 7: argument not 0 or 1 -- pair: 42 + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + cons_with_dispatch + cons_1_2_run + 1 + +(define (cons x y) + (define (dispatch m) + (cond ((= m 0) x) + ((= m 1) y) + (else (error "Argument not 0 or 1 -- CONS" m)))) + dispatch) + +(define (car z) (z 0)) + +(define (cdr z) (z 1)) + + +function pair(x, y) { + function dispatch(m) { + return m === 0 + ? x + : m === 1 + ? y + : error(m, "argument not 0 or 1 -- pair"); + } + return dispatch; +} +function head(z) { return z(0); } + +function tail(z) { return z(1); } + + + + cons_1_2_run + +const x = pair(1, 2); +head(x); + + 这种使用 过程 函数 并不符合我们对数据应该是什么的直观概念。然而,我们需要做的只是验证这些 过程 函数 满足上面给出的条件,以证明这是表示序对的一种有效方法。 + 这里要注意的微妙之处在于 (cons x y) pair(x, y) 返回的值是一个 过程也就是说 函数也就是说 内部定义的 过程 函数 dispatch ,它接受一个参数并返回任意一个 xy 取决于参数是 0 还是 1。因此, (car z) head(z) 被定义为应用 z 取决于 0。因此,如果 z 是由 过程 函数 通过 (cons x y), pair(x, y), 形成的,然后 z 应用到 0 将产生 x 。因此,我们已经证明 (car (cons x y)) head(pair(x, y)) 产生了 x 。因此,我们已经证明 (cdr (cons x y)) tail(pair(x, y)) 应用返回的 过程 函数 (cons x y) pair(x, y) 到 1,这将返回 y 。因此,这种 过程型 函数型 实现的序对是一种有效的实现,如果我们仅通过 cons, pair, car, head, cdr tail 访问序对,我们就无法将这种实现与使用真实数据结构的实现区分开来。 + 显示 过程 函数 表示的目的是为了说明我们的语言虽然不是这样工作的 (Scheme 和 Lisp 系统一般直接实现序对,为了效率) (序对的高效实现可能会使用 JavaScript 的本地 向量 数据结构) 但它可以这样工作。尽管这种 过程型 函数型 表示不常用,它却是一种完全足够的表示序对的方法,因为它满足序对需要满足的唯一条件。这个例子还演示了操作 过程 函数 可以自动提供表示复合数据的能力。现在可能看起来有点好奇,但 过程型 函数型 数据的表示将在我们的编程曲目中起到核心作用。这种编程风格通常被称为 消息传递 消息传递, 当我们在章节 处理建模和仿真问题时,我们将其作为基础工具来使用。 + + 这里是一个替代的 + 过程型 函数型 + 表示的序对。 对于这种表示,验证 (car (cons x y)) head(pair(x, y)) 产生了 【216:13†cn.txt】 x 对任何对象 x 对任何对象和 y. + + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + cons_lambda + 1 + cons_1_2_run + +(define (cons x y) + (lambda (m) (m x y))) + +(define (car z) + (z (lambda (p q) p))) + + +function pair(x, y) { + return m => m(x, y); +} +function head(z) { + return z((p, q) => p); +} + + + 什么是对应的 cdr (原语函数)过程实现 tail (原语函数)函数实现 cdr? tail? 提示:为了验证这个方法,请使用节的代换模型 。 + + cons_lambda 2 cons_1_2_run_2 function tail(z) { return z((p, q) => q); } cons_1_2_run_2 const x = pair(1, 2); tail(x); + + + 证明我们可以仅使用数字和算术运算来表示非负整数的序对,如果我们将序对 $a$ 和 $b$ 表示为整数,其为积 $2^a 3^b$。给出对应的 过程 函数 的定义 cons, pair, car, head, cdr. tail. pair_with_fast_expt 4 example_2.5 expt_log_definition function pair(a, b) { return fast_expt(2, a) * fast_expt(3, b); } function head(p) { return p % 2 === 0 ? head(p / 2) + 1 : 0; } function tail(p) { return p % 3 === 0 ? tail(p / 3) + 1 : 0; } example_2.5 tail(pair(3, 4)); + 序对序对的过程表示的函数表示 序对序对的函数表示的函数表示 + + + 如果将序对表示为以 过程 函数 (练习) 的思想还不够令人费解的话,请考虑在一个可以操作 过程, 函数, 的语言中,我们可以通过实现 0 和加 1 的运算来解决没有数字 (至少在涉及非负整数时) 的问题 。 + zero + +(define zero (lambda (f) (lambda (x) x))) + +(define (add-1 n) + (lambda (f) (lambda (x) (f ((n f) x))))) + + +const zero = f => x => x; + +function add_1(n) { + return f => x => f(n(f)(x)); +} + + + 这种表示称为丘奇数丘奇数,以其发明者逻辑学家丘奇, 阿隆佐阿隆佐·丘奇命名,他发明了【250:0†cn.txt】 。$\lambda$ + 这种表示法被称为丘奇数丘奇数,其发明者是逻辑学家丘奇,阿隆佐,他发明了演算 + + 直接定义one和two(不是以zero为基础和 + + add-1)。 + add_1)。 + + (提示:使用替换法来评估 + + (add-1 zero))。 + add_1(zero))。 + + + 直接给出加法的定义 + + 过程+ + 函数plus + + + (不是以 + add-1连续应用的)。 + add_1)。 + + + + + church_solution + 3 + +const one = f => x => f(x); +const two = f => x => f(f(x)); + +function plus(n, m) { + return f => x => n(f)(m(f)(x)); +} + +// testing + +const three = plus(one, two); + +function church_to_number(c) { + return c(n => n + 1)(0); +} +church_to_number(three); + + + + + + + + + + 如果将序对表示为 过程 函数 (练习) 的思想还不够令人费解的话,请考虑在一个可以操作 过程, 函数, 的语言中,我们可以通过实现 0 和加 1 的运算来解决没有数字 (至少在涉及非负整数时) 的问题。这种表示法被称为 丘奇数 丘奇数,以其发明者逻辑学家 丘奇, 阿隆佐 阿隆佐·丘奇命名,他发明了演算 + + 直接定义 one 和 two(不是以 zero 和 + + add-1)。 + add_1)。 + + (提示:使用替换法来评估 + + (add-1 zero))。 + add_1(zero))。 + + + 直接给出加法的定义 + + 过程+ + 函数plus + + + (不是以 + add-1连续应用的)。 + add_1)。 + + + + + church_solution + 3 + +const one = f => x => f(x); +const two = f => x => f(f(x)); + +function plus(n, m) { + return f => x => n(f)(m(f)(x)); +} + +// testing + +const three = plus(one, two); + +function church_to_number(c) { + return c(n => n + 1)(0); +} +church_to_number(three); + + + + + + 【16:8†cn.txt】 + diff --git a/xml/cn/chapter2/section1/subsection4.xml b/xml/cn/chapter2/section1/subsection4.xml new file mode 100644 index 000000000..d33b33a15 --- /dev/null +++ b/xml/cn/chapter2/section1/subsection4.xml @@ -0,0 +1,573 @@ + + + 扩展练习: 区间算术 + + + + 区间算术 + 算术区间上 + + Alyssa P. Hacker 正在设计一个系统来帮助人们解决工程问题。她希望在其系统中提供一个功能,即可以操作具有已知精度的不精确数量(例如物理设备的测量参数),这样当对这些近似数量进行计算时,结果将是具有已知精度的数字。 + + + 电气工程师将使用 Alyssa 的系统来计算电气量。有时他们需要通过公式 + $R_{p}$ 计算两个电阻 $R_{1}$ 和 $R_{2}$ 的并联等效电阻并联电阻公式,公式如下: + + \[ + \begin{array}{lll} + R_{p} & = & \dfrac{1}{1/R_{1}+1/R_{2}} + \end{array} + \] + + 电阻值通常仅在由电阻制造商保证的某种 电阻电阻容差 容差范围内是已知的。例如,如果您购买一个标有 6.8 ohms with 10% tolerance 的电阻,您只能确定电阻在 $6.8-0.68=6.12$ 到 $6.8+0.68=7.48$ 欧姆之间。因此,如果您将 6.8 欧姆 10% 的电阻与 4.7 欧姆 5% 的电阻并联,其组合电阻可以从约 2.58 欧姆(如果两个电阻处于下限)到约 2.97 欧姆(如果两个电阻处于上限)。 + + + Alyssa 的想法是实现 “区间算术”,作为用于组合 “区间”(代表不精确数量可能值范围的对象)的一组算术运算。添加、减去、乘以或除以两个区间的结果本身就是一个区间,代表结果的范围。 + + + Alyssa 假设存在一个称为“区间”的抽象对象,该对象有两个端点:下限和上限。她还假设,给定一个区间的端点,她可以使用数据构造器 make_interval + + make-interval. + make_interval. + + + Alyssa 首先编写了一个 + + 过程 + 函数 + + 用于两个区间的加法。她推理出,和的最小值可能是两个下限之和,和的最大值可能是两个上限之和: + + add_interval + add_interval + make_interval + print_interval + [ 4, 7 ] + add_interval_example + +(define (add-interval x y) + (make-interval (+ (lower-bound x) (lower-bound y)) + (+ (upper-bound x) (upper-bound y)))) + + +function add_interval(x, y) { + return make_interval(lower_bound(x) + lower_bound(y), + upper_bound(x) + upper_bound(y)); +} + + + Alyssa 还通过找到边界积的最小值和最大值,并将它们用作结果区间的边界来求两个区间的积。 + math_minMath.min + math_min (原语函数) + + (Min + (The functions math_min + + 并且 + math_max (原语函数) + math_maxMath.max + + max + math_max + + 是用于查找任意数量参数的最小值或最大值的原语。 + + mul_interval + mul_interval + make_interval + print_interval + '[ 3 , 10 ]' + mul_interval_example + +(define (mul-interval x y) + (let ((p1 (* (lower-bound x) (lower-bound y))) + (p2 (* (lower-bound x) (upper-bound y))) + (p3 (* (upper-bound x) (lower-bound y))) + (p4 (* (upper-bound x) (upper-bound y)))) + (make-interval (min p1 p2 p3 p4) + (max p1 p2 p3 p4)))) + + +function mul_interval(x, y) { + const p1 = lower_bound(x) * lower_bound(y); + const p2 = lower_bound(x) * upper_bound(y); + const p3 = upper_bound(x) * lower_bound(y); + const p4 = upper_bound(x) * upper_bound(y); + return make_interval(math_min(p1, p2, p3, p4), + math_max(p1, p2, p3, p4)); +} + + + 为了将两个区间相除,Alyssa 将第一个区间乘以第二个区间的倒数。注意,倒数区间的边界是上限的倒数和下限的倒数,顺序不变。 + + div_interval + div_interval + mul_interval + print_interval + '[ 0.2 , 0.6666666666666666 ]' + div_interval_example + +(define (div-interval x y) + (mul-interval x + (make-interval (/ 1.0 (upper-bound y)) + (/ 1.0 (lower-bound y))))) + + +function div_interval(x, y) { + return mul_interval(x, make_interval(1 / upper_bound(y), + 1 / lower_bound(y))); +} + + + + + Alyssa 的程序是不完整的,因为她尚未指定区间抽象的实现。以下是区间构造器的定义: + + make_interval + +(define (make-interval a b) (cons a b)) + + +function make_interval(x, y) { + return pair(x, y); +} +function lower_bound(i) { + return head(i); +} +function upper_bound(i) { + return tail(i); +} + + + + print_interval + + + +// printing the interval in one line requires some string +// manipulation: stringify turns a number into a string +// and the operator + can be applied to strings for +// string concatenation +function print_interval(i) { + return "[ " + stringify(lower_bound(i)) + + " , " + stringify(upper_bound(i)) + " ]"; +} + + + + make_interval + make_interval_exercise + +(define (make-interval a b) (cons a b)) + + +function make_interval(x, y) { return pair(x, y); } + + + + add_interval_example + +(add-interval (make-interval 1 2) (make-interval 3 5)) + + +print_interval(add_interval(make_interval(1, 2), + make_interval(3, 5))); + + +add_interval(make_interval(1, 2), + make_interval(3, 5)); + + + + mul_interval_example + +(mul-interval (make-interval 1 2) (make-interval 3 5)) + + +print_interval(mul_interval(make_interval(1, 2), + make_interval(3, 5))); + + + + div_interval_example + +(div-interval (make-interval 1 2) (make-interval 3 5)) + + +print_interval(div_interval(make_interval(1, 2), + make_interval(3, 5))); + + + 定义选择器 + upper_bound + + upper-bound + upper_bound + + 和 + lower_bound + + lower-bound + lower_bound + + 来完成实现。 + + + + upper_bound + +function make_interval(x, y) { + return pair(x, y); +} +function lower_bound(x) { + return head(x); +} +function upper_bound(x) { + return tail(x); +} + + + + + + + + + + 使用类似于 Alyssa 的推理,描述如何计算两个区间的差。定义一个对应的减法 + + 过程, + 函数, + 叫做 + sub_interval + + sub-interval.sub_interval. + + + + make_interval + print_interval + '[ -1.5 , 0.4 ]' + sub_interval_example + +function sub_interval(x, y) { + return make_interval(lower_bound(x) - upper_bound(y), + upper_bound(x) - lower_bound(y)); +} + + + + sub_interval_example + +print_interval(sub_interval(make_interval(0, 1), + make_interval(0.6, 1.5))); + + + + + + + + 区间宽度 + 区间的宽度是其上限与下限之间差的一半。宽度是区间所指定数值的一个不确定性度量。对于某些算术运算,合并两个区间的结果的宽度仅仅是参数区间宽度的函数,而对于其他运算,组合的宽度不是参数区间宽度的函数。证明两个区间的和(或差)的宽度仅仅是被加(或减)的区间宽度的函数。举例说明这对于乘法或除法不成立。 + + 我们用 $W(i)$ 表示区间 $i$ 的宽度,其下限和上限分别用 $L(i)$ 和 $U(i)$ 表示。两个区间 $i_1$ 和 $i_2$ 的宽度根据定义分别为 $(U(i_1) - L(i_1))/2$ 和 $(U(i_2) - L(i_2))/2$。将两个区间相加得到区间 $[ L(i_1) + L(i_2), U(i_1) + U(i_2)]$,其宽度为 + \[(U(i_1) + U(i_2) - (L(i_1) + L(i_2)))/2\] + \[= (U(i_1) - L(i_1))/2 + (U(i_2) - L(i_2))/2\] + \[= W(i_1) + W(i_2)\] + 减法的推理类似。 +

+ 乘法结果的区间宽度没有这样好的性质。例如,将任何区间与宽度为零的区间 $[ 0, 0 ]$ 相乘得到的是宽度为零的区间,而将任何区间 $i$ 与宽度为零的区间 $[ 1, 1 ]$ 相乘得到的区间宽度为 $W(i)$。除法的推理类似。 +
+ +
+ + + Ben Bitdiddle, 一个专家系统程序员,看到 Alyssa 的程序后评论说,除以跨越零的区间含义不明确。修改 Alyssa 的程序以检查此情况,并在发生时发出错误信号。 + div_interval除以零 + + + + upper_bound + mul_interval + print_interval + '[ 0.2 , 0.6666666666666666 ]' + div_interval_example + +function div_interval(x, y) { + return lower_bound(y) <= 0 && upper_bound(y) >= 0 + ? error("division error (interval spans 0)") + : mul_interval(x, make_interval(1 / upper_bound(y), + 1 / lower_bound(y))); +} + + + + + + + + + 顺便提一下,Ben 还隐晦地评论道:通过测试区间端点的符号,可以将 + mul_interval更高效版本 + mul-interval + mul_interval + + 分为九种情况,其中只有一种情况需要超过两次乘法。根据 Ben 的建议重写这个 + + 过程 + 函数。 + + + + upper_bound + print_interval + mul_interval_example + '[ 3 , 10 ]' + +function p(n) { + return n >= 0; +} +function n(n) { + return ! p(n); +} +function the_trouble_maker(xl, xu, yl, yu) { + const p1 = xl * yl; + const p2 = xl * yu; + const p3 = xu * yl; + const p4 = xu * yu; + return make_interval(math_min(p1, p2, p3, p4), + math_max(p1, p2, p3, p4)); +} +function mul_interval(x, y) { + const xl = lower_bound(x); + const xu = upper_bound(x); + const yl = lower_bound(y); + const yu = upper_bound(y); + return p(xl) && p(xu) && p(yl) && p(yu) + ? make_interval(xl * yl, xu * yu) + : p(xl) && p(xu) && n(yl) && p(yu) + ? make_interval(xu * yl, xu * yu) + : p(xl) && p(xu) && n(yl) && n(yu) + ? make_interval(xu * yl, xl * yu) + : n(xl) && p(xu) && p(yl) && p(yu) + ? make_interval(xl * yu, xu * yu) + : n(xl) && p(xu) && n(yl) && n(yu) + ? make_interval(xu * yl, xl * yl) + : n(xl) && n(xu) && p(yl) && p(yu) + ? make_interval(xl * yu, xu * yl) + : n(xl) && n(xu) && n(yl) && p(yu) + ? make_interval(xl * yu, xl * yl) + : n(xl) && n(xu) && n(yl) && n(yu) + ? make_interval(xu * yu, xl * yl) + : n(xl) && p(xu) && n(yl) && p(yu) + ? the_trouble_maker(xl, xu, yl, yu) + : error("lower larger than upper"); +} + + + + + <练习后跟文本></练习后跟文本> + <简短页面 LINES="4"></简短页面> + + 修正程序后,Alyssa 向一个潜在用户展示了她的程序,该用户抱怨她的程序解决了错误的问题。他想要一个可以处理表示为中心值和加法容差的数字的程序;例如,他希望使用诸如 $3.5\pm 0.15$ 而不是 $[3.35, 3.65]$ 的区间。Alyssa 回到她的办公桌前,通过提供一个可选的构造器和可选的选择器解决了这个问题: + + make_center_width + center + width + make_center_width + make_interval + make_center_width_example + 0.5 + +(define (make-center-width c w) + (make-interval (- c w) (+ c w))) + +(define (center i) + (/ (+ (lower-bound i) (upper-bound i)) 2)) + +(define (width i) + (/ (- (upper-bound i) (lower-bound i)) 2)) + + +function make_center_width(c, w) { + return make_interval(c - w, c + w); +} +function center(i) { + return (lower_bound(i) + upper_bound(i)) / 2; +} +function width(i) { + return (upper_bound(i) - lower_bound(i)) / 2; +} + + + + make_center_width_example + make_interval + + + +const my_interval = make_center_width(1, 0.5); +width(my_interval); + + + + + 不幸的是,Alyssa 的大多数用户是工程师。实际的工程情况通常涉及测量仅有小的不确定性,以区间的宽度与中心的比率来衡量。不确定性测量为区间宽度与区间中点的比率。工程师通常对子系统的参数指定百分比容差,如前面给出的电阻规格。 + + + + 定义一个构造器 + make_center_percent + + make-center-percent + make_center_percent + + + 该构造器接受一个中心值和一个百分比容差,并生成所需的区间。您还必须定义一个选择器 + percent,用于生成给定区间的百分比容差。center 选择器与上面显示的相同。 + + + + make_center_percent + make_center_width + make_center_percent_example + 9.999999999999993 + +function make_center_percent(center, percent) { + const width = center * (percent / 100); + return make_center_width(center, width); +} +function percent(i) { + return (width(i) / center(i)) * 100; +} + + + + make_center_percent_example + +const my_interval = make_center_percent(6.0, 10); +percent(my_interval); + + + + + + + 在假设小百分比容差的前提下,证明在因数的容差方面存在一个用于近似的简单公式,表示两个区间乘积的百分比容差。您可以通过假设所有数字为正来简化问题。 + + + 设一个以 $i$ 为中心的区间的最大误差为 $\Delta i$,以 $j$ 为中心的区间的最大误差为 $\Delta j$,以及乘积结果以 $k$ 为中心的最大误差为 $\Delta k$。那么: + \[ k + \Delta k = (i+\Delta i) * (j+\Delta j) + = ij + j \Delta i + i\Delta j + \Delta i \Delta j \] + 由于 $k = i j$ + \[ \Delta k = j\Delta i + i \Delta j + \Delta i\Delta j \] + 由于我们假设 $\Delta i \ll i$ 且 $\Delta j \ll j$,因此可以忽略项 $\Delta i \Delta j$,并得到 + \[ \Delta k = j \Delta i + i \Delta j \] + 用容差表示,我们得到: + \[ \Delta k / k + = (j \Delta i + i \Delta j) / ij = \Delta i/i + \Delta j/j \] + 因此,区间乘积结果的容差(大致上)是其参数容差之和。 + + 【94:1†source】 + <练习后跟文本></练习后跟文本> + + 经过大量的工作,Alyssa P. Hacker 交付了她完成的系统。几年后,在她已经完全忘记这件事的时候,她接到了一个愤怒用户 Lem E. Tweakit 的狂热电话。似乎 Lem 发现 + 电阻并联电阻公式 + 的公式可以用两种代数上等效的方式书写: + + \[ + \dfrac{R_{1}R_{2}}{R_{1}+R_{2}} + \] + + 和 + + \[ + \dfrac{1}{1/R_{1}+1/R_{2}} + \] + + 他编写了以下两个程序,每个程序计算并联电阻公式的方式不同: + + par + add_interval + mul_interval + div_interval + print_interval + par_example + '[ 2 , 4.363636363636363 ][ 2.5454545454545454 , 3.428571428571429 ]' + +(define (par1 r1 r2) + (div-interval (mul-interval r1 r2) + (add-interval r1 r2))) + +(define (par2 r1 r2) + (let ((one (make-interval 1 1))) + (div-interval one + (add-interval (div-interval one r1) + (div-interval one r2))))) + + +function par1(r1, r2) { + return div_interval(mul_interval(r1, r2), + add_interval(r1, r2)); +} +function par2(r1, r2) { + const one = make_interval(1, 1); + return div_interval(one, + add_interval(div_interval(one, r1), + div_interval(one, r2))); +} + + + + par_example + +display(print_interval(par1(pair(4, 6), pair(7, 8)))); + +display(print_interval(par2(pair(4, 6), pair(7, 8)))); + + +print_interval(par1(pair(4, 6), pair(7, 8))) ++ +print_interval(par2(pair(4, 6), pair(7, 8))); + + + Lem 抱怨 Alyssa 的程序对这两种计算方式给出了不同的答案。这是一个严重的抱怨。 + + + 证明 Lem 是对的。研究系统在各种算术表达式上的表现。创建一些区间 $A$ 和 $B$,并用它们计算表达式 $A/A$ 和 $A/B$。使用宽度为中心值的小百分比的区间将会获得更多的洞察。检查以中心百分比形式计算的结果(参见练习 )。 + + + 表达式 $A/A$ 很有意思,因为如果区间是为了表示一个特定的(虽然精确度不够)值,结果应该是完全为 1(宽度为 0),然而区间除法将导致一个宽度为正的区间。上面的方法没有识别出相同项的多次出现,因此它们将会遇到该问题。 + + + + + Eva Lu Ator,另一位用户,也注意到了由不同但在代数上等效的表达式计算出的不同区间。她说,使用 Alyssa 的系统进行区间运算的公式,如果能够写成这样一种形式,即没有代表不确定数字的 + + + 变量 + + + 名称 + + + 是重复的,将生成更严格的误差界限。因此,她说,par2 比 par1 是一个“更好”的并联电阻程序。她对吗?为什么? + + 她是对的。区间算术中所谓的依赖问题出现在同一输入值(或中间项)出现在区间函数中时。第二种表述更好,因为每个输入只出现一次,因此天真的区间计算的结果是最优的。 + + + + + + 一般来说,解释一下为什么等效的代数表达式可能导致不同的答案。你能设计一个没有这个缺点的区间算术包吗,还是这个任务不可能完成?(警告:这个问题非常困难。) + + + 使用线性和多项式逼近来解决区间算术中的依赖问题,分别形成仿射算术和泰勒级数方法。 + + + + 区间算术 + 算术区间上 +
diff --git a/xml/cn/chapter2/section2/section2.xml b/xml/cn/chapter2/section2/section2.xml new file mode 100644 index 000000000..a9dc9b164 --- /dev/null +++ b/xml/cn/chapter2/section2/section2.xml @@ -0,0 +1,206 @@ +
+ Hierarchical Data and the Closure Property + + + + + \suppressfloats + + 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 + pair(s)box-and-pointer notation for + pairin this case, the pair formed by + + (cons 1 2). + pair(1, 2). + + + + In this representation, which is called + box-and-pointer notation for data + notation in this bookbox-and-pointer notation for data + box-and-pointer + notation, each object is shown as a + pointerin box-and-pointer notation + 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 + box-and-pointer + notation, each compound object is shown as a + pointerin box-and-pointer notation + 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. + + + + + +
+ + + Box-and-pointer representation of + (cons 1 2). + +
+ + +
+ + + Box-and-pointer representation of + pair(1, 2). + +
+
+
+ + 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. + +
+
+
+
+ + The ability to create pairs whose elements are pairs is the essence of + list structures importance as a representational tool. We refer to + this ability as the + closureclosure property of conspair + pair (primitive function)closure property of + 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.The use of the + word + closureabstractin abstract algebra + closure here comes from abstract algebra, where a set of + elements is said to be + closed under an operation if applying the operation + to elements in the set produces an element that is again an element of the + set. The + + Lisp + programming languages + + community also (unfortunately) uses the word closure to + describe a totally unrelated concept: A closure + is an implementation technique for representing + + procedures with free variables. + + functions with free names. + + + We do not use the word closure in this second sense in this + book. + Closure is the key to power in any means of combination because it permits + us to create + hierarchical data structures + datahierarchical + hierarchical structuresstructures made up of parts, which + themselves are made up of parts, and so on. + + + From the outset of chapter, weve 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.The notion that a means of + closurelack of in many languages + combination should satisfy closure is a straightforward idea. Unfortunately, + the data combiners provided in many popular programming languages do not + satisfy closure, or make closure cumbersome to exploit. In + Fortranrestrictions on compound data + Fortran or + Basicrestrictions on compound data + Basic, one typically combines data elements by assembling them into + arraysbut one cannot form arrays whose elements are themselves + arrays. + Pascalrestrictions on compound data + Pascal and + Crestrictions on compound data + C admit structures whose elements are structures. However, this requires + that the programmer manipulate pointers explicitly, and adhere to the + restriction that each field of a structure can contain only elements of a + prespecified form. Unlike Lisp with its pairs, these languages have no + built-in general-purpose glue that makes it easy to manipulate compound + data in a uniform way. This limitation lies behind Alan + Perlis, Alan J. + Perliss comment in his foreword to this book: In Pascal the + plethora of declarable data structures induces a specialization within + functions that inhibits and penalizes casual cooperation. It is better to + have 100 functions operate on one data structure than to have 10 functions + operate on 10 data structures. + + + + &subsection2.2.1; + + + &subsection2.2.2; + + + &subsection2.2.3; + + + &subsection2.2.4; + +
diff --git a/xml/cn/chapter2/section2/subsection1.xml b/xml/cn/chapter2/section2/subsection1.xml new file mode 100644 index 000000000..10a32fa63 --- /dev/null +++ b/xml/cn/chapter2/section2/subsection1.xml @@ -0,0 +1,1960 @@ + + + Representing Sequences + + + + + + One of the useful structures we can build with pairs is a + sequence(s) + sequence(s)represented by pairs + pair(s)used to represent sequence + sequencean ordered collection of data objects. There + are, of course, many ways to represent sequences in terms of pairs. One + particularly straightforward representation is illustrated in + + figure, + figure, + + + where the sequence 1, 2, 3, 4 is represented as a chain of pairs. The + + car + head + + of each pair is the + corresponding item in the chain, and the + + cdr + tail + + of the pair is the next pair in the chain. The + + cdr + tail + + of the final pair signals the end of the + + + sequence by pointing to a + distinguished value that is not a pair, + + + sequence, + + + represented in box-and-pointer + diagrams as a diagonal line + box-and-pointer notationend-of-list marker + and in programs as + keywordsnullnull + null (keyword)endas end-of-list marker + end-of-list marker + + the value of the variable nil. + + JavaScripts primitive value + null. + + + The entire sequence is constructed by nested + + cons + pair + + operations: + + cons_example + +(cons 1 + (cons 2 + (cons 3 + (cons 4 nil)))) + + +pair(1, + pair(2, + pair(3, + pair(4, null)))); + + + + + + +
+
+ The sequence 1, 2, 3, 4 represented as a chain of pairs. + + +
+ + +
+
+ The sequence 1, 2, 3, 4 represented as a chain of pairs. + + +
+
+
+ + + Such a sequence of pairs, formed by nested + + + conses, + + + pair applications, + + + is called a + list(s) + list, and + + Scheme + our JavaScript environment + + provides a primitive called + list (primitive function) + list (\textit{ns}) + list to help in constructing + lists.In this book, we use list to mean a chain of + pairs terminated by the end-of-list marker. In contrast, the term + list structurelist vs. + list(s)list structure vs. + list structure refers to any data structure made out of pairs, + not just to lists. + The above sequence could be produced by + list (primitive function) + list (\textit{ns}) + + + (list 1 2 3 4). + list(1, 2, 3, 4). + + + In general, + + +(list a$_{1}$ a$_{2}$ $\ldots$ a$_{n}$) + + +list(a$_{1}$, a$_{2}$, $\ldots$, a$_{n}$) + + + is equivalent to + + +(cons a$_{1}$ (cons a$_{2}$ (cons $\ldots$ (cons a$_{n}$ nil) $\ldots$))) + + +pair(a$_{1}$, pair(a$_{2}$, pair($\ldots$, pair(a$_{n}$, null)$\ldots$))) + + + + + Lisp systems conventionally print lists by printing the sequence of + elements, enclosed in parentheses. Thus, the data object in + figure + is printed as + (1 2 3 4): + + + Our interpreter prints pairs using a textual representation of + box-and-pointer diagrams that we call box notation. + list(s)printed representation of + [,] (box notation for pairs)0a21 + box notation for pairs + pair(s)box notation for + notation in this bookbox notation for data + The result of pair(1, 2) + is printed as [1, 2], and + the data object in figure + is printed as + [1, [2, [3, [4, null]]]]: + + + + one_four + one_four_example + [ 2, [ 3, [ 4, null ] ] ] + +(define one-through-four (list 1 2 3 4)) + + +const one_through_four = list(1, 2, 3, 4); + + + + one_four + one_four_example + +one-through-four + + +(1 2 3 4) + + +one_through_four; + + +tail(one_through_four); + + +[1, [2, [3, [4, null]]]] + + + + + + Be careful not to confuse the expression + (list 1 2 3 4) with the list + (1 2 3 4), which is the result obtained + when the expression is evaluated. Attempting to evaluate the + expression (1 2 3 4) will signal an error + when the interpreter tries to apply the procedure + 1 to arguments + 2, 3, + and 4. + + + + + We can think of + list(s)manipulation with carhead, cdrtail, and conspair + head (primitive function)listas list operation + + car + head + + as selecting the first item in the list, and of + tail (primitive function)listas list operation + + cdr + tail + + as selecting the sublist consisting of all but the first item. Nested + applications of + + car + head + + and + + cdr + tail + + can be used to extract the second, third, and subsequent items in the + list.Since nested applications of + car and cdr are + cumbersome to write, Lisp dialects provide abbreviations for + themfor instance, + nested applications of car and cdr + cadr + + +(cadr $\langle arg \rangle$) = (car (cdr $\langle arg \rangle$)) + + + The names of all such procedures start with c + and end with r. Each + a between them stands for a + car + cd r + car operation and each + d for a cdr + operation, to be applied in the same order in which they appear in the + name. The names car and + cdr persist because simple combinations like + cadr are + pronounceable. + The constructor + pair (primitive function)listas list operation + + cons + pair + + makes a list like the original one, but with an additional item at the + beginning. + + car_one_four + one_four + 1 + +(car one-through-four) + + +1 + + +head(one_through_four); + + +1 + + + + cdr_one_four + one_four + [ 2, [ 3, [ 4, null ] ] ] + +(cdr one-through-four) + + +(2 3 4) + + +tail(one_through_four); + + +[2, [3, [4, null]]] + + + + car_cdr_one_four + one_four + 2 + +(car (cdr one-through-four)) + + +2 + + +head(tail(one_through_four)); + + +2 + + + + cons_one_four + one_four + [ 2, [ 3, [ 4, null ] ] ] + +(cons 10 one-through-four) + + +(10 1 2 3 4) + + +pair(10, one_through_four); + + +tail(tail(pair(10, one_through_four))); + + +[10, [1, [2, [3, [4, null]]]]] + + + + cons5_one_four + one_four + [ 2, [ 3, [ 4, null ] ] ] + +(cons 5 one-through-four) + + +(5 1 2 3 4) + + +pair(5, one_through_four); + + +tail(tail(pair(5, one_through_four))); + + +[5, [1, [2, [3, [4, null]]]]] + + + + + The value of nil, used to terminate the + chain of pairs, can be thought of as a sequence of no elements, the + empty list + nil + empty list. The word nil is a contraction of the + Latin word nihil, which means + nothing.Its remarkable how much energy + in the standardization of Lisp dialects has been dissipated in + arguments that are literally over nothing: Should + nil be an ordinary name? Should the value + of nil be a symbol? Should it be a list? + Should it be a pair? + nilordinaryas ordinary variable in Scheme + In Scheme, nil is an ordinary name, which + we use in this section as a variable whose value is the end-of-list + marker (just as true is an ordinary + variable that has a true value). Other dialects of Lisp, including + Common Lisp, treat nil as a special + symbol. The + Common Lisptreatment of nil + authors of this book, who have endured too many language + standardization brawls, would like to avoid the entire issue. Once we + have introduced quotation in + section, we will denote the + empty list as '() and dispense with the + variable nil entirely. + + + The value null, used to terminate + the chain of pairs, can be thought of as a sequence of no elements, the + empty list + null (keyword)emptyas empty list + empty list.The value + null is used in JavaScript for + various purposes, but in this book we shall only use it to + represent the empty list. + + + + + + + Box notation is sometimes difficult to read. In this book, when we want to + indicate the list nature of a data structure, we will employ the + alternative + notation in this booklist notation for data + list notation for data + list notation: Whenever possible, list notation uses + applications + of list whose evaluation would result in the + desired structure. For example, instead of the box notation + + +[1, [[2, 3], [[4, [5, null]], [6, null]]]] + + + we write + + +list(1, [2, 3], list(4, 5), 6) + + + in list notation.Our JavaScript environment provides + a primitive function + display_list + that works like the primitive function + display, except that + it uses list notation instead of box notation. + + + + + + List operations + + + list(s)operations on + list(s)techniques for manipulating + + The use of pairs to represent sequences of elements as lists is accompanied + by conventional programming techniques for manipulating lists by + successively + + + cdr down a list + list(s)cdringcdring down + cdring down + the lists. + + + walking down a list with tail + list(s)walking down with tail + using tail to walk down the lists. + + + For example, the + + procedure + function + + list(s)nth$n$th element of + + list-ref + list_ref + + takes as arguments a list and a number $n$ and + returns the $n$th item of the list. It is + customary to number the elements of the list beginning with 0. The method + for computing + + list-ref + list_ref + + is the following: +
    +
  • + For $n=0$, + + list-ref + list_ref + + should return the + + car + head + + of the list. +
  • +
  • + Otherwise, + + list-ref + list_ref + + + should return the $(n-1)$st item of the + + cdr + tail + + of the list. +
  • +
+ + list_ref + list_ref + list_ref_example + +(define (list-ref items n) + (if (= n 0) + (car items) + (list-ref (cdr items) (- n 1)))) + + +function list_ref(items, n) { + return n === 0 + ? head(items) + : list_ref(tail(items), n - 1); +} + + + + list_ref_example + list_ref + 16 + +(define squares (list 1 4 9 16 25)) +(list-ref squares 3) + + +16 + + +const squares = list(1, 4, 9, 16, 25); + +list_ref(squares, 3); + + +16 + + + + manual_squares + +(define squares (list 1 4 9 16 25)) + + +const squares = list(1, 4, 9, 16, 25); + + + + manual_odds + +(define odds (list 1 3 5 7)) + + +const odds = list(1, 3, 5, 7); + + +
+ + Often we + + + cdr down the whole list. + + + walk down the whole list. + + + To aid in this, + + Scheme + our JavaScript environment + + includes a primitive + predicate + is_null (primitive function) + is_null (\textit{ns}) + empty listrecognizing with is_null + null (keyword)recognizing with is_null + + + null?, + is_null, + + which tests whether its argument is the empty list. The + + procedure + function + + length + list(s)length of + length, which returns the number of items in + a list, illustrates this typical pattern of use: + + lengthrecursive version + length + length_example + 4 + +(define (length items) + (if (null? items) + 0 + (+ 1 (length (cdr items))))) + + +function length(items) { + return is_null(items) + ? 0 + : 1 + length(tail(items)); +} + + + + length_example + +(define odds (list 1 3 5 7)) +(length odds) + + +const odds = list(1, 3, 5, 7); + +length(odds); + + +4 + + + The length + procedure + function + + implements a simple recursive plan. The reduction step is: +
    +
  • + The length of any list is 1 plus the + length of the + + cdr + tail + + of the list. +
  • +
+ This is applied successively until we reach the base case: +
    +
  • + The length of the empty list is 0. +
  • +
+ We could also compute length in an iterative + style: + + lengthiterative version + length_iter + length_example + 4 + +(define (length items) + (define (length-iter a count) + (if (null? a) + count + (length-iter (cdr a) (+ 1 count)))) + (length-iter items 0)) + + +function length(items) { + function length_iter(a, count) { + return is_null(a) + ? count + : length_iter(tail(a), count + 1); + } + return length_iter(items, 0); +} + + +
+ + Another conventional programming technique is to + + + cons up a list + list(s)consingconsing up + cons up + the heads and tails of an answer list while + cdring down a list, + + + constructing a list with pair + list(s)constructing with pair + adjoining to a list with pair + list(s)adjoining to with pair + construct an answer list by adjoining elements to + the front of the list with + pair + while walking down a list using + tail, + + + as in the + + procedure + function + + list(s)combining with append + append, which takes two lists as arguments and + combines their elements to make a new list: + + append_example + append + manual_squares + manual_odds + 9 + +(append squares odds) + + +(1 4 9 16 25 1 3 5 7) + + +append(squares, odds); + + +length(append(squares, odds)); + + +list(1, 4, 9, 16, 25, 1, 3, 5, 7) + + + + append_example2 + append + manual_squares + manual_odds + 9 + +(append odds squares) + + +(1 3 5 7 1 4 9 16 25) + + +append(odds, squares); + + +length(append(odds, squares)); + + +list(1, 3, 5, 7, 1, 4, 9, 16, 25) + + + + Append + The function append + + + is also implemented using a recursive plan. To + append lists + list1 and list2, + do the following: +
    +
  • + If list1 is the empty list, then the + result is just list2. +
  • +
  • + Otherwise, append the + + cdr + tail + + of list1 and + list2, and + + cons + adjoin + + the + + car + head + + of list1 + + + onto the result: + + + to the result: + + +
  • +
+ + append + append + append_example + 9 + +(define (append list1 list2) + (if (null? list1) + list2 + (cons (car list1) (append (cdr list1) list2)))) + + +function append(list1, list2) { + return is_null(list1) + ? list2 + : pair(head(list1), append(tail(list1), list2)); +} + + +
+ + + Define a + + procedure + function + + last_pair + list(s)last pair of + + last-pair + last_pair + + that returns the list that contains only the last element of a given + (nonempty) list: + + last_pair_by_student + +;; last-pair to be given by student + + +// last_pair to be given by student + + + + last_pair_example + last_pair_by_student + +(last-pair (list 23 72 149 34)) + + +(34) + + +last_pair(list(23, 72, 149, 34)); + + +list(34) + + + + + last_pair_definition + last_pair_example + [ 34, null ] + +function last_pair(items) { + return is_null(tail(items)) + ? items + : last_pair(tail(items)); +} + + + + + + + + + + Define a + + procedure + function + + reverse + list(s)reversing + reverse that takes a list as argument and + returns a list of the same elements in reverse order: + + reverse_example + +(reverse (list 1 4 9 16 25)) + + +(25 16 9 4 1) + + + reverse(list(1, 4, 9, 16, 25)); + + +list(25, 16, 9, 4, 1) + + + + Naive reverse (what is the run time?): + + naive_reverse_definition + reverse_example_2 + 25 + +function reverse(items) { + return is_null(items) + ? null + : append(reverse(tail(items)), + pair(head(items), null)); +} + + + A better version: + + reverse + reverse_example_2 + 25 + +function reverse(items) { + function reverse_iter(items, result) { + return is_null(items) + ? result + : reverse_iter(tail(items), + pair(head(items), result)); + } + return reverse_iter(items, null); +} + + + + + + + Consider the + counting change + change-counting program of + section. It would be nice to be + able to easily change the currency used by the program, so that we could + compute the number of ways to change a British pound, for example. As + the program is written, the knowledge of the currency is distributed + partly into the + + procedure + function + + + first-denomination + first_denomination + + + and partly into the + + procedure + function + + + count-change + count_change + + + (which knows + that there are five kinds of U.S. coins). + It would be nicer + to be able to supply a list of coins to be used for making change. + + We want to rewrite the + + procedure + function + + cc so that its second argument is a list of + the values of the coins to use rather than an integer specifying which + coins to use. We could then have lists that defined each kind of + currency: + + us_coins + +(define us-coins (list 50 25 10 5 1)) + +(define uk-coins (list 100 50 20 10 5 2 1)) + + +const us_coins = list(50, 25, 10, 5, 1); +const uk_coins = list(100, 50, 20, 10, 5, 2, 1); + + + We could then call cc as follows: + + cc_example + cc + us_coins + +(cc 100 us-coins) + + +292 + + +cc(100, us_coins); + + +292 + + + To do this will require changing the program + cc somewhat. It will still have the same + form, but it will access its second argument differently, as follows: + + cc_helpers + +;; first-denomination, except-first-denomination +;; and no-more? to be given by student + + +// first_denomination, except_first_denomination +// and no_more to be given by student + + + + cc + cc_helpers + cc_example + +(define (cc amount coin-values) + (cond ((= amount 0) 1) + ((or (< amount 0) (no-more? coin-values)) 0) + (else + (+ (cc amount + (except-first-denomination coin-values)) + (cc (- amount + (first-denomination coin-values)) + coin-values))))) + + +function cc(amount, coin_values) { + return amount === 0 + ? 1 + : amount < 0 || no_more(coin_values) + ? 0 + : cc(amount, except_first_denomination(coin_values)) + + cc(amount - first_denomination(coin_values), coin_values); +} + + + Define the + + procedures + functions + + + first-denomination, + first_denomination, + + + + except-first-denomination, + + except_first_denomination, + + + and + + no-more? + no_more + + + in terms of primitive operations on list structures. Does the order of + the list + + coin-values + coin_values + + + affect the answer produced by cc? + Why or why not? + + + + exercise_2_19_solution + cc + cc_example + 292 + +function first_denomination(coin_values) { + return head(coin_values); +} +function except_first_denomination(coin_values) { + return tail(coin_values); +} +function no_more(coin_values) { + return is_null(coin_values); +} + + + + + The order of the list coin_values + does not affect the answer given by any correct solution of the problem, + because the given list represents an unordered collection of + denominations. + + + + + + + + + dotted-tail notationprocedurefor procedure parameters + definedottedwith dotted-tail notation + argument(s)arbitrary number of + procedurearbitrary number of arguments + The procedures + +, *, and + list take arbitrary numbers of arguments. + One way to define such procedures is to use + define with dotted-tail notation. + In a procedure definition, a parameter list that has a dot before the + last parameter name indicates that, when the procedure is called, the + initial parameters (if any) will have as values the initial arguments, + as usual, but the final parameters value will be a list + of any remaining arguments. For instance, given the definition + + + (define (f x y . z) $\langle \textit{body} \rangle$) + + + the procedure f can be called with two or + more arguments. If we evaluate + + + (f 1 2 3 4 5 6) + + + then in the body of f, + x will be 1, + y will be 2, and + z will be the list + (3 4 5 6). Given the definition + + +(define (g . w) $\langle \textit{body} \rangle$) + + + the procedure g can be called with zero or + more arguments. If we evaluate + + + (g 1 2 3 4 5 6) + + + then in the body of g, + w will be the list + (1 2 3 4 5 6).To define + f and g using + lambdadottedwith dotted-tail notation + lambda we would write + + +(define f (lambda (x y . z) body)) +(define g (lambda w body)) + + + Use this notation to write a procedure + same-parity that takes one or more integers + and returns a list of all the arguments that have the same even-odd + parity as the first argument. For example, + + same_parity + + ;; same-parity to be given by student + + + + same_parity_example + same_parity + +(same-parity 1 2 3 4 5 6 7) + + +(1 3 5 7) + + + + same_parity_example2 + same_parity + +(same-parity 2 3 4 5 6 7) + + +(2 4 6) + + + + list(s)operations on + list(s)techniques for manipulating + + + + In the presence of higher-order functions, it is not strictly necessary + for functions to have multiple parameters; one would + suffice. If we have a function such as + plus that naturally requires two + arguments, we could write a variant of the function to which we pass + the arguments one at a time. An application of the variant to the + first argument could return a function that we can then apply to the + second argument, and so on. This practicecalled + currying + currying and named after the American mathematician and + logician + Curry, Haskell Brooks + Haskell Brooks Curryis quite common in programming + languages such as + Haskell + Haskell and + Ocaml + OCaml. In JavaScript, a curried + version of plus looks as follows. + + currying_plus + curry_example + 7 + +function plus_curried(x) { + return y => x + y; +} + + + + curry_example + +plus_curried(3)(4); + + + Write a function brooks that + takes a curried function as first argument and as second argument a list + of arguments to which the curried function is then applied, one by one, + in the given order. For example, the following application of + brooks should have the + same effect as + plus_curried(3)(4): + + currying + +// brooks to be written by the student + + + + currying_example + currying_plus + currying + +brooks(plus_curried, list(3, 4)); + + +7 + + + While we are at it, we might as well curry the function + brooks! Write a function + brooks_curried that can be applied + as follows: + + currying_currying + + // brooks_curried to be written by the student + + + + currying_currying_example + currying_plus + currying_currying + +brooks_curried(list(plus_curried, 3, 4)); + + +7 + + + With this function brooks_curried, + what are the results of evaluating the following two statements? + + currying_currying_example_2 + currying_plus + currying_currying + +brooks_curried(list(brooks_curried, + list(plus_curried, 3, 4))); + + + + currying_currying_example_3 + currying_plus + currying_currying + +brooks_curried(list(brooks_curried, + list(brooks_curried, + list(plus_curried, 3, 4)))); + + + +
    +
  1. + + brooks_solution + currying_plus + currying_example + 7 + +function brooks(f, items) { + return is_null(items) + ? f + : brooks(f(head(items)), tail(items)); +} + + +
  2. +
  3. + + brooks_curried_solution + brooks_solution + currying_currying_example + 7 + +function brooks_curried(items) { + return brooks(head(items), tail(items)); +} + + +
  4. +
  5. + The statement + + currying_currying_example_4 + currying_plus + brooks_curried_solution + 7 + +brooks_curried(list(brooks_curried, + list(plus_curried, 3, 4))); + + + of course evaluates to 7, as does +
  6. +
  7. + + currying_currying_example_5 + currying_plus + brooks_curried_solution + 7 + +brooks_curried(list(brooks_curried, + list(brooks_curried, + list(plus_curried, 3, 4)))); + + +
  8. +
+
+ +
+ list(s)operations on + list(s)techniques for manipulating +
+
+ + + Mapping over lists + + + list(s)mapping over + mappinglistover lists + + One extremely useful operation is to apply some transformation to each + element in a list and generate the list of results. For instance, the + following + + procedure + function + + scales each number in a list by a given factor: + + scale_list + scale_list + scale_list_example + [ 30, [ 40, [ 50, null ] ] ] + +(define (scale-list items factor) + (if (null? items) + nil + (cons (* (car items) factor) + (scale-list (cdr items) factor)))) + + +function scale_list(items, factor) { + return is_null(items) + ? null + : pair(head(items) * factor, + scale_list(tail(items), factor)); +} + + + + scale_list_example + +(scale-list (list 1 2 3 4 5) 10) + + +(10 20 30 40 50) + + +scale_list(list(1, 2, 3, 4, 5), 10); + + +[10, [20, [30, [40, [50, null]]]]] + + +tail(tail(scale_list(list(1, 2, 3, 4, 5), 10))); + + + + + We can abstract this general idea and capture it as a common pattern + expressed as a higher-order + + procedure, + function, + just as in section. The + higher-order + + procedure + function + + here is called map. + + Map + The function map + + + takes as arguments a + + procedure + function + + of one argument and a list, and returns a list of the results produced by + applying the + + procedure + function + + to each element in the list: + + Scheme standardly provides a + mapwith multiple arguments + map + procedure that is more general than the one described here. This more + general map takes a procedure of + $n$ arguments, together with + $n$ lists, and applies the procedure to all the + first elements of the lists, all the second elements of the lists, and so + on, returning a list of the results. For example: + + general_map_example + general_map + plus + +(map + (list 1 2 3) (list 40 50 60) (list 700 800 900)) + + +(741 852 963) + + + + general_map_example2 + general_map + +(map (lambda (x y) (+ x (* 2 y))) + (list 1 2 3) + (list 4 5 6)) + + + + + map + map + map_example + [ 2.5, [ 11.6, [ 17, null ] ] ] + +(define (map proc items) + (if (null? items) + nil + (cons (proc (car items)) + (map proc (cdr items))))) + + +function map(fun, items) { + return is_null(items) + ? null + : pair(fun(head(items)), + map(fun, tail(items))); +} + + + + map_example + abs_definition + map + [ 2.5, [ 11.6, [ 17, null ] ] ] + +(map abs (list -10 2.5 -11.6 17)) + + +(10 2.5 11.6 17) + + +map(abs, list(-10, 2.5, -11.6, 17)); + + +tail(map(abs, list(-10, 2.5, -11.6, 17))); + + +[10, [2.5, [11.6, [17, null]]]] + + + + map_example2 + map + [ 4, [ 9, [ 16, null ] ] ] + +(map (lambda (x) (* x x)) + (list 1 2 3 4)) + + +(1 4 9 16) + + +map(x => x * x, list(1, 2, 3, 4)); + + +tail(map(x => x * x, list(1, 2, 3, 4))); + + +[1, [4, [9, [16, null]]]] + + + Now we can give a new definition of + + scale-list + scale_list + + in terms of map: + + scale_list + scale_list2 + scale_list_example + [ 30, [ 40, [ 50, null ] ] ] + +(define (scale-list items factor) + (map (lambda (x) (* x factor)) + items)) + + +function scale_list(items, factor) { + return map(x => x * factor, items); +} + + + + + + + Map + The function map + + + is an important construct, not only because it captures a common pattern, + but because it establishes a higher level of abstraction in dealing with + lists. In the original definition of + + scale-list, + scale_list, + + the recursive structure of the program draws attention to the + element-by-element processing of the list. Defining + + scale-list + scale_list + + in terms of map suppresses that level of + detail and emphasizes that scaling transforms a list of elements to a list + of results. The difference between the two definitions is not that the + computer is performing a different process (it isnt) but that we + think about the process differently. In effect, + map helps establish an abstraction barrier + that isolates the implementation of + + procedures + functions + + that transform lists from the details of how the elements of the list are + extracted and combined. Like the barriers shown in + + + figure, + + + figure, + + + this abstraction gives us the flexibility to change the low-level details + of how sequences are implemented, while preserving the conceptual framework + of operations that transform sequences to sequences. + Section expands + on this use of sequences as a framework for organizing programs. + + + + + The + + procedure + function + + + square-list + square_list + + takes a list of numbers as argument and returns a list of the squares of + those numbers. + + square_list_example + +;; square-list to be given by student +(square-list (list 1 2 3 4)) + + +(1 4 9 16) + + +square_list(list(1, 2, 3, 4)); + + +tail(square_list(list(1, 2, 3, 4))); + + +[1, [4, [9, [16, null]]]] + + + Here are two different definitions of + + square-list. + square_list. + + Complete both of them by filling in the missing expressions: + + square_list_example + +(define (square-list items) + (if (null? items) + nil + (cons ?? ??))) + + +function square_list(items) { + return is_null(items) + ? null + : pair(??, ??); +} + + + + square_list_example + +(define (square-list items) + (map ?? ??)) + + +function square_list(items) { + return map(??, ??); +} + + + + + square_list_pedestrian + square_definition + square_list_example + [ 4, [ 9, [ 16, null ] ] ] + +function square_list(items) { + return is_null(items) + ? null + : pair(square(head(items)), + square_list(tail(items))); +} + + + + square_list_with_map + square_definition + [ 4, [ 9, [ 16, null ] ] ] + square_list_example + +function square_list(items) { + return map(square, items); +} + + + + + + + Louis Reasoner tries to rewrite the first + square-list + square_list + + + procedure + function + + of exercise so that it evolves an + iterative process: + + square_list_warning + +// THIS IS NOT A CORRECT SOLUTION + + + + square_list2 + square_definition + square_list_warning + square_list_example + [ 9, [ 4, [ 1, null ] ] ] + +(define (square-list items) + (define (iter things answer) + (if (null? things) + answer + (iter (cdr things) + (cons (square (car things)) + answer)))) + (iter items nil)) + + +function square_list(items) { + function iter(things, answer) { + return is_null(things) + ? answer + : iter(tail(things), + pair(square(head(things)), + answer)); + } + return iter(items, null); +} + + + Unfortunately, defining + + square-list + square_list + + this way produces the answer list in the reverse order of the one desired. + Why? + + Louis then tries to fix his bug by interchanging the arguments to + + cons: + pair: + + + square_list3 + square_definition + square_list_warning + square_list_example + 16 + +(define (square-list items) + (define (iter things answer) + (if (null? things) + answer + (iter (cdr things) + (cons answer + (square (car things)))))) + (iter items nil)) + + +function square_list(items) { + function iter(things, answer) { + return is_null(things) + ? answer + : iter(tail(things), + pair(answer, + square(head(things)))); + } + return iter(items, null); +} + + + This doesnt work either. Explain. + + + +
    +
  1. + The result list is reversed in the first program because the argument + list is traversed in the given order, from first to last, but squares + are added successively to the front of the answer list via + cons. + pair. + + The last element of the list is the last one to be added to the answer + and thus ends up as the first element of the result list. +
  2. +
  3. + The second program makes things worse! The result is not even a list + any longer, because the elements occupy the tail position of the + result list and not the head position. +
  4. +
+
+
+ + + + + The + + procedure + function + + for_each + + for-each + for_each + + is similar to map. It takes as arguments a + + procedure + function + + and a list of elements. However, rather than forming a list of the + results, + + for-each + for_each + + just applies the + + procedure + function + + to each of the elements in turn, from left to right. The values returned by + applying the + + procedure + function + + to the elements are not used + + at allfor-each + at allfor_each + + + is used with + + procedures + functions + + that perform an action, such as printing. For example, + + for_each_by_student + +;; for_each to be given by student + + +// for_each to be given by student + + + + for_each_by_student + for_each_example + +(for-each + (lambda (x) (newline) (display x)) + (list 57 321 88)) + + +57 +321 +88 + + +for_each(x => display(x), list(57, 321, 88)); + + +57 +321 +88 + + +for_each(x => x, + list(57, 321, 88)); + + + The value returned by the call to + + for-each + for_each + + (not illustrated above) can be something arbitrary, such as true. Give an + implementation of + + for-each. + for_each. + + + + for_each + for_each_example + undefined + +function for_each(fun, items) { + if (is_null(items)){ + return undefined; + } else { + fun(head(items)); + for_each(fun, tail(items)); + } +} + + + + + + + list(s)mapping over + mappinglistover lists +
diff --git a/xml/cn/chapter2/section2/subsection2.xml b/xml/cn/chapter2/section2/subsection2.xml new file mode 100644 index 000000000..114012d98 --- /dev/null +++ b/xml/cn/chapter2/section2/subsection2.xml @@ -0,0 +1,1504 @@ + + + Hierarchical Structures + + + + datahierarchical + hierarchical data structures + treerepresented as pairs + pair(s)used to represent tree + + 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)). + +
+
+
+
+ + + 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. + +
+
+
+
+ + + Recursion + recursionworkingin working with trees + is a natural tool for dealing with tree structures, since we can + often reduce operations on trees to operations on their branches, which + reduce in turn to operations on the branches of the branches, and so on, + until we reach the leaves of the tree. As an example, compare the + + length + length + + + procedure + function + + of section with the + count_leaves + treecounting leaves of + + 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 + + + + + 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 + + cars + heads + + 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 + is_pair (primitive function) + is_pair (\textit{ns}) + + pair?, + is_pair, + + which tests whether its argument is a pair. Here is the complete + procedure:The order of the first two clauses + in the cond matters, since the empty list + satisfies null? and also is not a + pair.function:The order of the + two predicates matters, since null + satisfies is_null and also is not a + pair. + + count_leaves + 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))); + + +
+ + + Suppose we evaluate the expression + + + (list 1 (list 2 (list 3 4))). + + + list(1, list(2, list(3, 4))). + + + Give the result printed by the interpreter, the corresponding + box-and-pointer structure, and the interpretation of this as a tree (as in + + figure). + figure). + + + +
    +
  1. + + count_leaves_example_result + + + +[1, [[2, [[3, [4, null]], null]], null]] + + +
  2. +
  3. +
    + +
    +
  4. +
  5. +
    + +
    +
  6. +
+
+ +
+ + + + Give combinations of + cars + heads + + and + cdrs + tails + + that will pick 7 from each of the following + lists: + lists, given in list notation: + + + extreme_list + +(1 3 (5 7) 9) + +((7)) + +(1 (2 (3 (4 (5 (6 7)))))) + + +list(1, 3, list(5, 7), 9) + +list(list(7)) + +list(1, list(2, list(3, list(4, list(5, list(6, 7)))))) + + + +
    +
  • + + +head(tail(head(tail(tail(the_first_list))))); + + +
  • +
  • + + + head(head(the_second_list)); + + +
  • +
  • + + +head(tail(head(tail(head(tail(head(tail(head( + tail(head(tail(the_third_list)))))))))))); + + +
  • +
+
+ +
+ + + Suppose we define x and + y to be two lists: + + xy + +(define x (list 1 2 3)) + +(define y (list 4 5 6)) + + +const x = list(1, 2, 3); + +const y = list(4, 5, 6); + + + + + What result is printed by the interpreter in response to evaluating + each of the following + expressions: + + + What is the result of evaluating each of the + following expressions, in box notation and list notation? + + + + appendxy + xy + +(append x y) + + +append(x, y) + + + + pairxy + xy + +(cons x y) + + +pair(x, y) + + + + listxy + xy + +(list x y) + + +list(x, y) + + +head(tail(list(x, y))); + + + +
    +
  1. + + appendxy_sol + appendxy + + + +[1, [2, [3, [4, [5, [6, null]]]]]] + + +// result: [1, [2, [3, [4, [5, [6, null]]]]]] + + +
  2. +
  3. + + pairxy_sol + pairxy + + + +[[1, [2, [3, null]]], [4, [5, [6, null]]]] + + +// result: [[1, [2, [3, null]]], [4, [5, [6, null]]]] + + +
  4. +
  5. + + listxy_sol + listxy + [ 4, [ 5, [ 6, null ] ] ] + + + +[[1, [2, [3, null]]], [[4, [5, [6, null]]], null]] + + +// result: [[1, [2, [3, null]]], [[4, [5, [6, null]]], null]] + + +
  6. +
+
+ +
+ + + Modify your + + reverse + reverse + + + procedure + function + + of exercise to produce a + deep_reverse + treereversing at all levels + + deep-reverse + deep_reverse + + + procedure + function + + that takes a list as argument and returns as its value the list with its + elements reversed and with all sublists deep-reversed as well. For example, + + x_list_list + +(define x (list (list 1 2) (list 3 4))) + + +const x = list(list(1, 2), list(3, 4)); + + + + x_x_list_list + x_list_list + +x + + +((1 2) (3 4)) + + +x; + + +list(list(1, 2), list(3, 4)) + + + + reverse_x + x_list_list + reverse + +(reverse x) + + +((3 4) (1 2)) + + +reverse(x); + + +list(list(3, 4), list(1, 2)) + + + + deep_reverse + +;; deep_reverse to be written by student + + +// deep_reverse to be written by student + + + + deep_reverse_x + x_list_list + deep_reverse + +(deep-reverse x) + + +((4 3) (2 1)) + + +deep_reverse(x); + + +head(deep_reverse(x)); + + +list(list(4, 3), list(2, 1)) + + + + + deep_reverse_solution + x_list_list + deep_reverse_x + [ 4, [ 3, null ] ] + +function deep_reverse(items){ + return is_null(items) + ? null + : is_pair(items) + ? append(deep_reverse(tail(items)), + pair(deep_reverse(head(items)), + null)) + : items; +} + + + + + + + + + + + Write a + + procedure + function + + fringe + treefringe of + + fringe + fringe + + that takes as argument a tree (represented as a list) and returns a list + whose elements are all the leaves of the tree arranged in left-to-right + order. For example, + + fringe + +;; fringe to be written by student + + +// fringe to be written by student + + + + x_fringe_example + +(define x (list (list 1 2) (list 3 4))) + + +const x = list(list(1, 2), list(3, 4)); + + + + x_fringe_example_2 + x_fringe_example + fringe + +(fringe x) + + +(1 2 3 4) + + +fringe(x); + + +list(1, 2, 3, 4) + + + + x_fringe_example_3 + x_fringe_example + fringe + +(fringe (list x x)) + + +(1 2 3 4 1 2 3 4) + + +fringe(list(x, x)); + + +length(fringe(list(x, x))); + + +list(1, 2, 3, 4, 1, 2, 3, 4) + + + + + x_fringe_solution + x_fringe_example + x_fringe_example_3 + 8 + +function fringe(x) { + return is_null(x) + ? null + : is_pair(x) + ? append(fringe(head(x)), fringe(tail(x))) + : list(x); +} + + + + + + + + + A binary + mobile + mobile consists of two branches, a left branch and a right + branch. Each branch is a rod of a certain length, from which hangs + either a weight or another binary mobile. We can represent a binary + mobile using compound data by constructing it from two branches (for + example, using list): + + make_mobile + +(define (make-mobile left right) + (list left right)) + + +function make_mobile(left, right) { + return list(left, right); +} + + + A branch is constructed from a length (which + must be a number) together with a structure, + which may be either a number (representing a simple weight) or another + mobile: + + make_branch + +(define (make-branch length structure) + (list length structure)) + + +function make_branch(length, structure) { + return list(length, structure); +} + + +
    +
  1. + Write the corresponding selectors + + left-branch + left_branch + + + and + + right-branch, + right_branch, + + + which return the branches of a mobile, and + + branch-length + branch_length + + + and + + branch-structure, + branch_structure, + + which return the components of a branch. +
  2. +
  3. + Using your selectors, define a + + procedure + function + + + + total-weight + total_weight + + + that returns the total weight of a mobile. +
  4. +
  5. + A mobile is said to be + balanced mobile + balanced if the torque applied by its top-left branch is equal + to that applied by its top-right branch (that is, if the length of the + left rod multiplied by the weight hanging from that rod is equal to the + corresponding product for the right side) and if each of the submobiles + hanging off its branches is balanced. Design a predicate that tests + whether a binary mobile is balanced. +
  6. +
  7. + Suppose we change the representation of mobiles so that the + constructors are + + make_mobile_branch_alternative + +(define (make-mobile left right) + (cons left right)) + +(define (make-branch length structure) + (cons length structure)) + + +function make_mobile(left, right) { + return pair(left, right); +} +function make_branch(length, structure) { + return pair(length, structure); +} + + + How much do you need to change your programs to convert to the new + representation? +
  8. +
+ + +
    +
  1. + + branch_construction + make_mobile + make_branch + +function left_branch(m) { + return head(m); +} +function right_branch(m) { + return head(tail(m)); +} +function branch_length(b) { + return head(b); +} +function branch_structure(b) { + return head(tail(b)); +} + + +
  2. +
  3. + + total_weight + branch_construction + example_2.29 + 30 + +function is_weight(x){ + return is_number(x); +} +function total_weight(x) { + return is_weight(x) + ? x + : total_weight(branch_structure( + left_branch(x))) + + total_weight(branch_structure( + right_branch(x))); +} + + + + example_2.29 + +const m = make_mobile( + make_branch(10, + make_mobile(make_branch(10, 2), + make_branch(4, 5))), + make_branch(10, 23)); +total_weight(m); + + +
  4. +
  5. + + is_balanced_solution + total_weight + example_2.29_2 + true + +function is_balanced(x) { + return is_weight(x) || + ( is_balanced(branch_structure( + left_branch(x))) && + is_balanced(branch_structure( + right_branch(x))) && + total_weight(branch_structure( + left_branch(x))) + * branch_length(left_branch(x)) + === + total_weight(branch_structure( + right_branch(x))) + * branch_length(right_branch(x)) + ); +} + + + + example_2.29_2 + +const m = make_mobile( + make_branch(20, + make_mobile(make_branch(10, 2), + make_branch(4, 5))), + make_branch(28, 5)); +is_balanced(m); + + +
  6. +
  7. + With this alternative representation, the selector functions for + mobile and branch need to change as follows: + + branch_construction_2 + make_mobile + make_branch + +function left_branch(m) { + return head(m); +} +function right_branch(m) { + return tail(m); +} +function branch_length(b) { + return head(b); +} +function branch_structure(b) { + return tail(b); +} + + +
  8. +
+
+
+ + datahierarchical + hierarchical data structures + treerepresented as pairs + pair(s)used to represent tree + + + Mapping over trees + + + treemapping over + mappingtreeover trees + + + 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 + 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)) + + + + + 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 + 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. + + + + + + Define a procedure + Declare a function + + + square-tree + square_tree + + analogous to the + + square-list + square_list + + + procedure + function + + of exercise. That is, + + square-tree + square_tree + + should behave as follows: + + square_tree + + + +// square_tree to be written by student + + + + square_tree_example + square_tree + +(square-tree + (list 1 + (list 2 (list 3 4) 5) + (list 6 7))) + + +(1 (4 (9 16) 25) (36 49)) + + +square_tree(list(1, + list(2, list(3, 4), 5), + list(6, 7))); + + +tail(tail(square_tree(list(1, + list(2, list(3, 4), 5), + list(6, 7))))); + + +list(1, list(4, list(9, 16), 25), list(36, 49))) + + + + Define square-tree + Declare square_tree + + + both directly (i.e., without using any higher-order + + procedures) + functions) + and also by using + map and recursion. + + Directly: + + square_tree_pedestrian + square_definition + square_tree_example + [ [ 36, [ 49, null ] ], null ] + +function square_tree(tree) { + return is_null(tree) + ? null + : ! is_pair(tree) + ? square(tree) + : pair(square_tree(head(tree)), + square_tree(tail(tree))); +} + + +[1, [[4, [[9, [16, null]], [25, null]]], [[36, [49, null]], null]]] + + + The version using map: + + square_tree_with_map + square_definition + square_tree_example + [ [ 36, [ 49, null ] ], null ] + +function square_tree(tree) { + return map(sub_tree => ! is_pair(sub_tree) + ? square(sub_tree) + : square_tree(sub_tree), + tree); +} + + +[1, [[4, [[9, [16, null]], [25, null]]], [[36, [49, null]], null]]] + + + + + + + + Abstract your answer to exercise to + produce a + + procedure + function + + tree_map + + tree-map + tree_map + + with the property that + + + square-tree + could be defined as + + + square_tree + could be declared as + + + + tree_map + square_tree_example + +;; tree_map to be written by student + + +// tree_map to be written by student + + + + square_tree_using_tree_map + tree_map + +(define (square-tree tree) (tree-map square tree)) + + +function square_tree(tree) { return tree_map(square, tree); } + + + + + tree_map_with_map + square_definition + square_tree_using_tree_map + square_tree_example + [ [ 36, [ 49, null ] ], null ] + +function tree_map(f, tree) { + return map(sub_tree => is_null(sub_tree) + ? null + : is_pair(sub_tree) + ? tree_map(f, sub_tree) + : f(sub_tree), + tree); +} + + +[1, [[4, [[9, [16, null]], [25, null]]], [[36, [49, null]], null]]] + + + + + + + + We can represent a + setsubsets of + set as a list of distinct elements, and we can + represent the set of all subsets of the set as a list of lists. For + example, if the set is + + (1 2 3), + list(1, 2, 3), + + + then the set of all subsets is + + + (() (3) (2) (2 3) (1) (1 3) (1 2) (1 2 3)). + + + + +list(null, list(3), list(2), list(2, 3), + list(1), list(1, 3), list(1, 2), + list(1, 2, 3)) + + + + + Complete the + following + + definition of a procedure + declaration of a function + + that generates the set of subsets of a set and give a clear explanation of + why it works: + + subsets of a set + +(define (subsets s) + (if (null? s) + (list nil) + (let ((rest (subsets (cdr s)))) + (append rest (map ?? rest))))) + + +function subsets(s) { + if (is_null(s)) { + return list(null); + } else { + const rest = subsets(tail(s)); + return append(rest, map(??, rest)); + } +} + + + + + sublists + example_2.33 + 4 + +function subsets(s) { + if (is_null(s)) { + return list(null); + } else { + const rest = subsets(tail(s)); + return append(rest, map(x => pair(head(s), x), rest)); + } +} + + +[ null, + [ [3, null], + [ [2, null], + [ [2, [3, null]], + [ [1, null], + [ [1, [3, null]], + [ [1, [2, null]], + [[1, [2, [3, null]]], null] + ] + ] + ] + ] + ] + ] +] + + + + + + example_2.33 + +subsets(list(1, 2, 3)); + + +length(subsets(list(1, 2))); + + + The argument starts in a similar way as the argument for the function + cc + in section: A subset either + contains the first element $e$ of the given + set, or it doesn't. If it doesn't, the problem becomes strictly smaller: + Compute all subsets of the tail of the list that represents the given + set. If it does, it must result from adding $e$ + to a subset that doesn't contain $e$. In the + end, we need to append both lists of subsets to obtain the list of all + subsets. + + + + treemapping over + mappingtreeover trees +
diff --git a/xml/cn/chapter2/section2/subsection3.xml b/xml/cn/chapter2/section2/subsection3.xml new file mode 100644 index 000000000..c82e208a2 --- /dev/null +++ b/xml/cn/chapter2/section2/subsection3.xml @@ -0,0 +1,2599 @@ + + + Sequences as Conventional Interfaces + + + + sequence(s)as conventional interface + conventional interfacesequence as + + + In working with compound data, weve stressed how data abstraction + permits us to design programs without becoming enmeshed in the details + of data representations, and how abstraction preserves for us the + flexibility to experiment with alternative representations. In this + section, we introduce another powerful design principle for working + with data structuresthe use of conventional interfaces. + + + + In section we saw how + program abstractions, implemented as higher-order + + procedures, + functions, + can capture common patterns in programs that deal with numerical data. Our + ability to formulate analogous operations for working with compound data + depends crucially on the style in which we manipulate our data structures. + Consider, for example, the following + + procedure, + function, + + analogous to the + + count-leaves + count_leaves + + + procedure + function + + of section, which takes a tree as argument + and computes the sum of the squares of the leaves that are odd: + + odd_definition + +(define (odd? n) + (= (remainder n 2) 1)) + + +function is_odd(n) { + return n % 2 === 1; +} + + + + sum_odd_squares + square_definition + odd_definition + sum_odd_squares_example + 34 + +(define (sum-odd-squares tree) + (cond ((null? tree) 0) + ((not (pair? tree)) + (if (odd? tree) (square tree) 0)) + (else (+ (sum-odd-squares (car tree)) + (sum-odd-squares (cdr tree)))))) + + +function sum_odd_squares(tree) { + return is_null(tree) + ? 0 + : ! is_pair(tree) + ? is_odd(tree) ? square(tree) : 0 + : sum_odd_squares(head(tree)) + + sum_odd_squares(tail(tree)); +} + + + + sum_odd_squares_example + +(sum-odd-squares (list (list 2 3) (list 4 5))) + + +sum_odd_squares(list(list(2, 3), list(4, 5))); + + + On the surface, this + + procedure + function + + is very different from the following one, which constructs a list of all + the even Fibonacci numbers + ${\textrm{Fib}}(k)$, where + $k$ is less than or equal to a given integer + $n$: + + even_fibs + even_definition + fib_definition + even_fibs_example + [ 2, [ 8, [ 34, null ] ] ] + +(define (even-fibs n) + (define (next k) + (if (> k n) + nil + (let ((f (fib k))) + (if (even? f) + (cons f (next (+ k 1))) + (next (+ k 1)))))) + (next 0)) + + +function even_fibs(n) { + function next(k) { + if (k > n) { + return null; + } else { + const f = fib(k); + return is_even(f) + ? pair(f, next(k + 1)) + : next(k + 1); + } + } + return next(0); +} + + + + even_fibs_example + +(even-fibs 9) + + +even_fibs(9); + + +tail(even_fibs(9)); + + + + + Despite the fact that these two + + procedures + functions + + are structurally very different, a more abstract description of the two + computations reveals a great deal of similarity. The first program +
    +
  • + enumerates the leaves of a tree; +
  • +
  • + filters them, selecting the odd ones; +
  • +
  • + squares each of the selected ones; and +
  • +
  • + accumulates the results using + + +, + +, + + starting with 0. +
  • +
+ The second program +
    +
  • + enumerates the integers from 0 to $n$; +
  • +
  • + computes the Fibonacci number for each integer; +
  • +
  • + filters them, selecting the even ones; and +
  • +
  • + accumulates the results using + + cons, + pair, + + starting with the empty list. +
  • +
+
+ + + + + +
+ + The signal-flow plans for the procedures + sum-odd-squares (top) and + even-fibs + (bottom) reveal the commonality between the two programs. + +
+ + +
+ + The signal-flow plans for the + functions + sum_odd_squares (top) and + even_fibs (bottom) reveal the + commonality between the two programs. + +
+
+
+
+ + + A signal-processing engineer would find it natural to conceptualize these + processes in terms of + signal-processing view of computation + signal-flow diagram + signals flowing through a cascade of stages, each of + which implements part of the program plan, as shown in + + figure. + figure. + + + In + + sum-odd-squares, + sum_odd_squares, + + + we begin with an + enumerator + enumerator, which generates a signal consisting of + the leaves of a given tree. This signal is passed through a + filter + filter, which eliminates all but the odd elements. The resulting + signal is in turn passed through a + mappingtransas a transducer + map, which is a transducer that applies the + square + + procedure + function + + to each element. The output of the map is then fed to an + accumulator + accumulator, which combines the elements using + + +, + +, + + starting from an initial 0. The plan for + + even-fibs + even_fibs + + is analogous. + + + + +
+ + The signal-flow plans for the procedures + sum-odd-squares (top) and + even-fibs + (bottom) reveal the commonality between the two programs. + +
+ + +
+ + The signal-flow plans for the + functions + sum_odd_squares (top) and + even_fibs (bottom) reveal the + commonality between the two programs. + +
+
+
+
+
+ + Unfortunately, the two + + procedure definitions + function declarations + + above fail to exhibit this signal-flow structure. For instance, if we + examine the + + sum-odd-squares + sum_odd_squares + + + + procedure, + function, + + we find that the enumeration is implemented partly by the + + null? + is_null + + and + + pair? + is_pair + + tests and partly by the tree-recursive structure of the + + procedure. + function. + + Similarly, the accumulation is found partly in the tests and partly in the + addition used in the recursion. In general, there are no distinct parts of + either + + procedure + function + + that correspond to the elements in the signal-flow description. Our two + + procedures + functions + + decompose the computations in a different way, spreading the enumeration + over the program and mingling it with the map, the filter, and the + accumulation. If we could organize our programs to make the signal-flow + structure manifest in the + + procedures + functions + + we write, this would increase the conceptual clarity of the resulting + + code. + program. + + + + + Sequence Operations + + + + sequence(s)operations on + + + + The key to organizing programs so as to more clearly reflect the + signal-flow structure is to concentrate on the signals that + flow from one stage in the process to the next. If we represent these + signals as lists, then we can use list operations to implement the + processing at each of the stages. For instance, we can implement the + mapping stages of the signal-flow diagrams using the + map + + procedure + function + + from section: + + square_definition + map + 25 + +(map square (list 1 2 3 4 5)) + + + (1 4 9 16 25) + + +map(square, list(1, 2, 3, 4, 5)); + + +list_ref(map(square, list(1, 2, 3, 4, 5)), 4); + + +list(1, 4, 9, 16, 25) + + + + + + Filtering a sequence to select only those elements that satisfy a given + predicate is accomplished by + + filter + filter + filter_odd + [ 1, [ 3, [ 5, null ] ] ] + +(define (filter predicate sequence) + (cond ((null? sequence) nil) + ((predicate (car sequence)) + (cons (car sequence) + (filter predicate (cdr sequence)))) + (else (filter predicate (cdr sequence))))) + + +function filter(predicate, sequence) { + return is_null(sequence) + ? null + : predicate(head(sequence)) + ? pair(head(sequence), + filter(predicate, tail(sequence))) + : filter(predicate, tail(sequence)); +} + + + + filter_odd + [ 1, [ 3, [ 5, null ] ] ] + +(define (odd? n) + (= (remainder n 2) 1)) + +(filter odd? (list 1 2 3 4 5)) + + +function is_odd(n) { + return n % 2 === 1; +} +filter(is_odd, list(1, 2, 3, 4, 5)); + + + For example, + + filter + odd_definition + +(filter odd? (list 1 2 3 4 5)) + + + (1 3 5) + + +filter(is_odd, list(1, 2, 3, 4, 5)); + + +list(1, 3, 5) + + + + + Accumulations can be implemented by + + accumulate + accumulate + +(define (accumulate op initial sequence) + (if (null? sequence) + initial + (op (car sequence) + (accumulate op initial (cdr sequence))))) + + +function accumulate(op, initial, sequence) { + return is_null(sequence) + ? initial + : op(head(sequence), + accumulate(op, initial, tail(sequence))); +} + + + + simple_plus + +;; in Scheme, the operator symbol + is procedure name + + +function plus(x, y) { + return x + y; +} + + + + accumulate + simple_plus + 15 + +(accumulate + 0 (list 1 2 3 4 5)) + + +15 + + +accumulate(plus, 0, list(1, 2, 3, 4, 5)); + + +15 + + + + simple_times + +;; in Scheme, the operator symbol * is procedure name + + +function times(x, y) { + return x * y; +} + + + + accumulate + simple_times + 120 + +(accumulate * 1 (list 1 2 3 4 5)) + + + 120 + + +accumulate(times, 1, list(1, 2, 3, 4, 5)); + + +120 + + + + accumulate + [ 3, [ 4, [ 5, null ] ] ] + +(accumulate cons nil (list 1 2 3 4 5)) + + +(1 2 3 4 5) + + +accumulate(pair, null, list(1, 2, 3, 4, 5)); + + +tail(tail(accumulate(pair, null, list(1, 2, 3, 4, 5)))); + + +list(1, 2, 3, 4, 5) + + + + + All that remains to implement signal-flow diagrams is to enumerate the + sequence of elements to be processed. For + + even-fibs, + even_fibs, + + we need to generate the sequence of integers in a given range, which we + can do as follows: + + enumerate_interval + enumerate_interval + enumerate_interval_example + [ 5, [ 6, [ 7, null ] ] ] + +(define (enumerate-interval low high) + (if (> low high) + nil + (cons low (enumerate-interval (+ low 1) high)))) + + +function enumerate_interval(low, high) { + return low > high + ? null + : pair(low, + enumerate_interval(low + 1, high)); +} + + + + enumerate_interval_example + enumerate_interval + +(enumerate-interval 2 7) + + +(2 3 4 5 6 7) + + +enumerate_interval(2, 7); + + +tail(tail(tail(enumerate_interval(2, 7)))); + + +list(2, 3, 4, 5, 6, 7) + + + To enumerate the leaves of a tree, we can useThis is, in fact, + precisely the + fringeas a tree enumeration + + fringe + fringe + + procedure + function + + from exercise. Here weve renamed it + to emphasize that it is part of a family of general sequence-manipulation + + procedures. + functions. + + + treeenumerating leaves of + enumerate_tree + enumerate_tree + enumerate_tree_example + [ 3, [ 4, [ 5, null ] ] ] + +(define (enumerate-tree tree) + (cond ((null? tree) nil) + ((not (pair? tree)) (list tree)) + (else (append (enumerate-tree (car tree)) + (enumerate-tree (cdr tree)))))) + + +function enumerate_tree(tree) { + return is_null(tree) + ? null + : ! is_pair(tree) + ? list(tree) + : append(enumerate_tree(head(tree)), + enumerate_tree(tail(tree))); +} + + + + enumerate_tree_example + enumerate_tree + +(enumerate-tree (list 1 (list 2 (list 3 4)) 5)) + + +(1 2 3 4 5) + + +enumerate_tree(list(1, list(2, list(3, 4)), 5)); + + +tail(tail(enumerate_tree(list(1, list(2, list(3, 4)), 5)))); + + +list(1, 2, 3, 4, 5) + + + + + Now we can reformulate + + sum-odd-squares + sum_odd_squares + + + and + + even-fibs + even_fibs + + as in the signal-flow diagrams. For + + sum-odd-squares, + sum_odd_squares, + + we enumerate the sequence of leaves of the tree, filter this to keep only + the odd numbers in the sequence, square each element, and sum the results: + + sum_odd_squares + square_definition + simple_plus + odd_definition + enumerate_tree + sum_odd_squares_example + 34 + +(define (sum-odd-squares tree) + (accumulate + + 0 + (map square + (filter odd? + (enumerate-tree tree))))) + + +function sum_odd_squares(tree) { + return accumulate(plus, + 0, + map(square, + filter(is_odd, + enumerate_tree(tree)))); +} + + + For + + even-fibs, + even_fibs, + + we enumerate the integers from 0 to $n$, generate + the Fibonacci number for each of these integers, filter the resulting + sequence to keep only the even elements, and accumulate the results + into a list: + + even_fibs + even_definition + fib_definition + enumerate_interval + even_fibs_example + [ 2, [ 8, [ 34, null ] ] ] + +(define (even-fibs n) + (accumulate cons + nil + (filter even? + (map fib + (enumerate-interval 0 n))))) + + +function even_fibs(n) { + return accumulate(pair, + null, + filter(is_even, + map(fib, + enumerate_interval(0, n)))); +} + + + + + + The value of expressing programs as sequence operations is that this + helps us make program designs that are modular, that is, designs that + are constructed by combining relatively independent pieces. We can + encourage modular design by providing a library of standard components + together with a conventional interface for connecting the components + in flexible ways. + + + + Modular construction + modularity + sequence(s)as source of modularity + is a powerful strategy for controlling complexity in + engineering design. In real signal-processing applications, for example, + designers regularly build systems by cascading elements selected from + standardized families of filters and transducers. Similarly, sequence + operations provide a library of standard program elements that we can mix + and match. For instance, we can reuse pieces from the + + sum-odd-squares + sum_odd_squares + + + and + + + even-fibs + + + even_fibs + + + + procedures + functions + + in a program that constructs a list of the squares of the first + $n+1$ Fibonacci numbers: + + list_fib_squares + square_definition + fib_definition + enumerate_interval + list_fib_squares_example + 11 + +(define (list-fib-squares n) + (accumulate cons + nil + (map square + (map fib + (enumerate-interval 0 n))))) + + +function list_fib_squares(n) { + return accumulate(pair, + null, + map(square, + map(fib, + enumerate_interval(0, n)))); +} + + + + list_fib_squares_example + list_fib_squares + +(list-fib-squares 10) + + +(0 1 1 4 9 25 64 169 441 1156 3025) + + +list_fib_squares(10); + + +length(list_fib_squares(10)); + + +list(0, 1, 1, 4, 9, 25, 64, 169, 441, 1156, 3025) + + + We can rearrange the pieces and use them in computing the product of the + squares of the odd integers in a sequence: + + product_of_squares_of_odd_elements + square_definition + odd_definition + simple_times + product_of_squares_of_odd_elements_example + 225 + +(define (product-of-squares-of-odd-elements sequence) + (accumulate * + 1 + (map square + (filter odd? sequence)))) + + +function product_of_squares_of_odd_elements(sequence) { + return accumulate(times, + 1, + map(square, + filter(is_odd, sequence))); +} + + + + product_of_squares_of_odd_elements_example + product_of_squares_of_odd_elements + +(product-of-squares-of-odd-elements (list 1 2 3 4 5)) + + +225 + + +product_of_squares_of_odd_elements(list(1, 2, 3, 4, 5)); + + +225 + + + + + We can also formulate conventional data-processing applications in terms of + sequence operations. Suppose we have a sequence of personnel records and + we want to find the salary of the highest-paid programmer. Assume that we + have a selector salary that returns the salary + of a record, and a predicate + + programmer? + is_programmer + + + that tests if a record is for a programmer. Then we can write + + linus + + + +const my_records = list(list("Linus", "programmer", 30000), + list("Richard", "programmer", 25000), + list("Bill", "manager", 2500000)); +function is_programmer(record) { + return head(tail(record)) === "programmer"; +} +function salary(record) { + return head(tail(tail(record))); +} +salary_of_highest_paid_programmer(my_records); + + + + linus + 30000 + +(define (salary-of-highest-paid-programmer records) + (accumulate max + 0 + (map salary + (filter programmer? records)))) + + +function salary_of_highest_paid_programmer(records) { + return accumulate(math_max, + 0, + map(salary, + filter(is_programmer, records))); +} + + + These examples give just a hint of the vast range of operations that + can be expressed as sequence operations. + Waters, Richard C. + Richard Waters (1979) developed a program that automatically analyzes + traditional + Fortran + Fortran programs, viewing them in terms of maps, filters, and accumulations. + He found that fully 90 percent of the code in the Fortran Scientific + Subroutine Package fits neatly into this paradigm. One of the reasons + for the success of Lisp as a programming language is that lists provide a + standard medium for expressing ordered collections so that they can be + manipulated using higher-order operations. Many modern languages, such as + Python, have learned this lesson. + + + Sequences, implemented here as lists, serve as a conventional interface + that permits us to combine processing modules. Additionally, when we + uniformly represent structures as sequences, we have localized the + data-structure dependencies in our programs to a small number of sequence + operations. By changing these, we can experiment with alternative + representations of sequences, while leaving the overall design of our + programs intact. We will exploit this capability in + section, when we generalize the + sequence-processing paradigm to admit infinite sequences. + + + + Fill in the missing expressions to complete the following definitions of + some basic list-manipulation operations as accumulations: + + map_append_length_example + + + +map(math_sqrt, list(1, 2, 3, 4)); +// append(list(1, 2, 3), list(4, 5, 6)); +// length(list(1, 2, 3, 4)); + + +tail(map(math_sqrt, list(1, 2, 3, 4))); + + + + lengthaccumulationas accumulation + mapaccumulationas accumulation + appendaccumulationas accumulation + map_append_length_example + +(define (map p sequence) + (accumulate (lambda (x y) ??) nil sequence)) + +(define (append seq1 seq2) + (accumulate cons ?? ??)) + +(define (length sequence) + (accumulate ?? 0 sequence)) + + +function map(f, sequence) { + return accumulate((x, y) => ??, + null, sequence); +} +function append(seq1, seq2) { + return accumulate(pair, ??, ??); +} +function length(sequence) { + return accumulate(??, 0, sequence); +} + + + + + map_append_length_example + [ 1.4142135623730951, [ 1.7320508075688772, [ 2, null ] ] ] + +function map(f, sequence) { + return accumulate((x, y) => pair(f(x), y), + null, + sequence); +} +function append(seq1, seq2) { + return accumulate(pair, seq2, seq1); +} +function length(sequence) { + return accumulate((x, y) => y + 1, + 0, + sequence); +} + + + + + + + + + + + Evaluating a + polynomial in $x$ at a given value + of $x$ can be formulated as an accumulation. + We evaluate the polynomial + + \[ a_{n} x^n +a_{n-1}x^{n-1}+\cdots + a_{1} x+a_{0} \] + + using a well-known algorithm called + polynomial(s)evaluating with Horners rule + Horners rule + Horners rule, which structures the computation as + + \[ \left(\cdots (a_{n} x+a_{n-1})x+\cdots +a_{1}\right) x+a_{0} \] + + In other words, we start with $a_{n}$, multiply + by $x$, add $a_{n-1}$, + multiply by $x$, and so on, until we reach + $a_{0}$.According to + Knuth, Donald E. + Knuth (1997b), this rule was formulated by + HornerHorner, W. G. + W. G. Horner early in the nineteenth century, but the method was actually + used by Newton over a hundred years earlier. Horners rule evaluates + the polynomial using fewer additions and multiplications than does the + straightforward method of first computing + $a_{n} x^n$, then adding + $a_{n-1}x^{n-1}$, and so on. In fact, it is + possible to prove that any algorithm for evaluating arbitrary polynomials + must use at least as many additions and multiplications as does + Horners rule, and thus Horners rule is an + algorithmoptimal + optimalityof Horners rule + optimal algorithm for polynomial evaluation. This was proved (for the + number of additions) by + Ostrowski, A. M. + A. M. Ostrowski in a 1954 paper that essentially founded the modern study + of optimal algorithms. The analogous statement for multiplications was + proved by + Pan, V. Y. + V. Y. Pan in 1966. The book by + Borodin, Alan + Munro, Ian + + Borodin and Munro (1975) + + provides an overview of these and other results about optimal + algorithms. + Fill in the following template to produce a + + procedure + function + + that evaluates a polynomial using Horners rule. Assume that the + coefficients of the polynomial are arranged in a sequence, from + $a_{0}$ through + $a_{n}$. + + horner + horner_eval_example + +(define (horner-eval x coefficient-sequence) + (accumulate (lambda (this-coeff higher-terms) ??) + 0 + coefficient-sequence)) + + +function horner_eval(x, coefficient_sequence) { + return accumulate((this_coeff, higher_terms) => ??, + 0, + coefficient_sequence); +} + + + For example, to compute $1+3x+5x^3+x^5$ at + $x=2$ you would evaluate + + horner_eval_example + horner + +(horner-eval 2 (list 1 3 0 5 0 1)) + + +horner_eval(2, list(1, 3, 0, 5, 0, 1)); + + + + + horner_solution + horner_eval_example_2 + 79 + +function horner_eval(x, coefficient_sequence) { + return accumulate((this_coeff, higher_terms) => + x * higher_terms + this_coeff, + 0, + coefficient_sequence); +} + + + + + + horner_eval_example_2 + horner_solution + horner_eval_example + +(horner-eval 2 (list 1 3 0 5 0 1)) + + +horner_eval(2, list(1, 3, 0, 5, 0, 1)); + + + + + + + + Redefine + + count-leaves + count_leaves + + + from section as an accumulation: + + count_leavesaccumulationas accumulation + count_leaves_example + +(define (count-leaves t) + (accumulate ?? ?? (map ?? ??))) + + +function count_leaves(t) { + return accumulate(??, ??, map(??, ??)); +} + + + + + count_leaves_example + 4 + +function count_leaves(t) { + return accumulate((leaves, total) => leaves + total, + 0, + map(sub_tree => is_pair(sub_tree) + ? count_leaves(sub_tree) + : 1, + t)); +} + + + + + + + + + The + + procedure + function + + + accumulate-n + accumulate_n + + + is similar to + + + accumulate + accumulate + + except that it takes as its third argument a sequence of sequences, which + are all assumed to have the same number of elements. It applies the + designated accumulation + + procedure + function + + to combine all the first elements of the sequences, all the second elements + of the sequences, and so on, and returns a sequence of the results. For + instance, if s is a sequence containing four + sequences + + +((1 2 3) (4 5 6) (7 8 9) (10 11 12)) + + +list(list(1, 2, 3), list(4, 5, 6), list(7, 8, 9), list(10, 11, 12)) + + + then the value of + + + (accumulate-n + 0 s) + + + accumulate_n(plus, 0, s) + + + should be the sequence + + (22 26 30). + list(22, 26, 30). + + + Fill in the missing expressions in the following definition of + + accumulate-n: + accumulate_n: + + + + accumulate_n + example_2.36 + +(define (accumulate-n op init seqs) + (if (null? (car seqs)) + nil + (cons (accumulate op init ??) + (accumulate-n op init ??)))) + + +function accumulate_n(op, init, seqs) { + return is_null(head(seqs)) + ? null + : pair(accumulate(op, init, ??), + accumulate_n(op, init, ??)); +} + + + + + accumulate_n + example_2.36 + [ 22, [ 26, [ 30, null ] ] ] + +function accumulate_n(op, init, seqs) { + return is_null(head(seqs)) + ? null + : pair(accumulate(op, init, map(x => head(x), seqs)), + accumulate_n(op, init, map(x => tail(x), seqs))); +} + + + + + + example_2.36 + simple_plus + +const seq_seq = list(list(1, 2, 3), list(4, 5, 6), + list(7, 8, 9), list(10, 11, 12)); +accumulate_n(plus, 0, seq_seq); + + + + + + + Suppose we represent vectors $v=(v_{i})$ as + matrix, represented as sequence + vector (mathematical)represented as sequence + vector (mathematical)operations on + sequences of numbers, and matrices $m=(m_{ij})$ + as sequences of vectors (the rows of the matrix). For example, the matrix + + \[ \left[ + \begin{array}{llll} + 1 & 2 & 3 & 4\\ + 4 & 5 & 6 & 6\\ + 6 & 7 & 8 & 9\\ + \end{array} + \right] \] + + + + is represented as the sequence + ((1 2 3 4) (4 5 6 6) (6 7 8 9)). + + + is represented as the following sequence: + + +list(list(1, 2, 3, 4), + list(4, 5, 6, 6), + list(6, 7, 8, 9)) + + + + + With this representation, we can use sequence operations to concisely + express the basic matrix and vector operations. These operations + (which are described in any book on matrix algebra) are the following: + + + + + + + + + + + + + + + + + +
+ + + (dot-product + $v$ $w$) + + + dot_product($v$, + $w$) + + + returns the sum $\sum_{i}v_{i} w_{i}$; +
+ + + (matrix-*-vector + $m$ $v$) + matrix_times_vector($m$, + $v$) + + + returns the vector $t$, where + $t_{i} =\sum_{j}m_{ij}v_{j}$; +
+ + + (matrix-*-matrix + $m\ n$) + + + matrix_times_matrix($m$, + $n$) + + + returns the matrix $p$, where + $p_{ij}=\sum_{k} m_{ik}n_{kj}$; +
+ + + (transpose + $m$)transpose($m$) + + + returns the matrix $n$, where + $n_{ij}=m_{ji}$. +
+ We can define the dot product asThis definition uses + + + the extended version of map + described in footnote. + + + the function accumulate_n + from exercise. + + + + dot_product + dot_product + dot_product_example + 11 + simple_plus + simple_times + accumulate_n + +(define (dot-product v w) + (accumulate + 0 (map * v w))) + + +function dot_product(v, w) { + return accumulate(plus, 0, accumulate_n(times, 1, list(v, w))); +} + + + + dot_product_example + +(dot-product (list 1 2) (list 3 4)) + + +dot_product(list(1, 2), list(3, 4)); + + + Fill in the missing expressions in the following + + procedures + functions + + for computing the other matrix operations. (The + + procedure + function + + + + accumulate-n + is defined in + + + accumulate_n + is declared in + + + exercise.) + + matrix_times_vector + matrix_times_matrix + transpose a matrix + +(define (matrix-*-vector m v) + (map ?? m)) + +(define (transpose mat) + (accumulate-n ?? ?? mat)) + +(define (matrix-*-matrix m n) + (let ((cols (transpose n))) + (map ?? m))) + + +function matrix_times_vector(m, v) { + return map(??, m); +} +function transpose(mat) { + return accumulate_n(??, ??, mat); +} +function matrix_times_matrix(m, n) { + const cols = transpose(n); + return map(??, m); +} + + + + + exercise_2_37_solution + accumulate_n + dot_product + example_2.37 + [ 140, [ 160, [ 60, null ] ] ] + +function matrix_times_vector(m, v) { + return map(row => dot_product(row, v), m); +} +function transpose(mat) { + return accumulate_n(pair, null, mat); +} +function matrix_times_matrix(n, m) { + const cols = transpose(m); + return map(x => map(y => dot_product(x, y), cols), n); +} + + + + + + + example_2.37 + +const v = list(10, 20, 30); +const m1 = list(list(1, 2, 3), list(3, 5, 1), list(1, 1, 1)); +const m2 = list(list(1, 2, 3), list(4, 5, 6), list(7, 8, 9)); + +matrix_times_vector(m1, v); +// transpose(m1); +// matrix_times_matrix(m1, m2); + + + + +
+ + + + The + accumulatesame as fold rightsame as fold_right + fold_right + + accumulate + accumulate + + + procedure + function + + is also known as + + + fold-right, + fold_right, + + because it combines the first element of the sequence with the result + of combining all the elements to the right. There is also a + + fold-left, + fold_left, + + which is similar to + + fold-right, + fold_right, + + except that it combines elements working in the opposite direction: + + fold_left + fold_left + fold_left_example + [ [ [ null, [ 1, null ] ], [ 2, null ] ], [ 3, null ] ] + +(define (fold-left op initial sequence) + (define (iter result rest) + (if (null? rest) + result + (iter (op result (car rest)) + (cdr rest)))) + (iter initial sequence)) + + +function fold_left(op, initial, sequence) { + function iter(result, rest) { + return is_null(rest) + ? result + : iter(op(result, head(rest)), + tail(rest)); + } + return iter(initial, sequence); +} + + + + fold_right + fold_right_example + +;; same as accumulate +(define fold_right accumulate) + + +// same as accumulate +const fold_right = accumulate; + + + What are the values of + + simple_divide + +;; in Scheme, the operator symbol * is procedure name + + +function divide(x, y) { + return x / y; +} + + + + fold_right_example_1 + fold_right + simple_divide + +(fold-right / 1 (list 1 2 3)) + + +fold_right(divide, 1, list(1, 2, 3)); + + + + fold_left_example_1 + fold_left + simple_divide + +(fold-left / 1 (list 1 2 3)) + + +fold_left(divide, 1, list(1, 2, 3)); + + + + fold_right_example + fold_right + +(fold-right list nil (list 1 2 3)) + + +fold_right(list, null, list(1, 2, 3)); + + +length(fold_right(list, null, list(1, 2, 3))); + + + + fold_left_example + fold_left + +(fold-left list nil (list 1 2 3)) + + +fold_left(list, null, list(1, 2, 3)); + + + Give a property that + + op + op + + should satisfy to guarantee that + + fold-right + fold_right + + and + + fold-left + fold_left + + will produce the same values for any sequence. + +
    +
  1. + + fold_right_example_1_solution + fold_right_example_1 + 1.5 + + + +1.5 + + +// result: 1.5 + + +
  2. +
  3. + + fold_left_example_1_solution + fold_left_example_1 + 0.16666666666666666 + + + +0.16666666666666666 + + +// result: 0.16666666666666666 + + +
  4. +
  5. + + fold_right_example_solution + fold_right_example + 2 + + + +[1, [[2, [[3, [null, null]], null]], null]] + + +// result: [1, [[2, [[3, [null, null]], null]], null]] + + +
  6. +
  7. + + fold_left_example_solution + fold_left_example + [ [ [ null, [ 1, null ] ], [ 2, null ] ], [ 3, null ] ] + + + +[[[null, [1, null]], [2, null]], [3, null]] + + +// result: [[[null, [1, null]], [2, null]], [3, null]] + + +
  8. +
+ We can guarantee that fold_right + and fold_left produce + the same values for any sequence, if we require that + op is commutative and associative. + They also produce the same values, if + op is just associative and the + argument initial is a left and right + neutral element with respect to op. + + simple_plus + fold_right + 6 + +fold_right(plus, 0, list(1, 2, 3)); + + + + simple_plus + fold_left + 6 + +fold_left(plus, 0, list(1, 2, 3)); + + +
+
+ + + Complete the following definitions of reverse + reversefoldingas folding + (exercise) in terms of + + fold-right + fold_right + + and + + fold-left + fold_left + + from exercise: + + reverse_example + +(define (reverse sequence) + (fold-right (lambda (x y) ??) nil sequence)) + + +function reverse(sequence) { + return fold_right((x, y) => ??, null, sequence); +} + + + + reverse_example + +(define (reverse sequence) + (fold-left (lambda (x y) ??) nil sequence)) + + +function reverse(sequence) { + return fold_left((x, y) => ??, null, sequence); +} + + + + + reverse_example_2 + +reverse(list(1, 4, 5, 9, 16, 25)); + + +head(reverse(list(1, 4, 5, 9, 16, 25))); + + + + fold_right + reverse_example_2 + 25 + +function reverse(sequence) { + return fold_right((x, y) => append(y, list(x)), + null, sequence); +} + + + + fold_left + reverse_example_2 + 25 + +function reverse(sequence) { + return fold_left((x, y) => pair(y, x), null, sequence); +} + + + + + + + sequence(s)operations on + + + Nested Mappings + + + + mappingnested + + + We can extend the sequence paradigm to include many computations that are + commonly expressed using nested loops.This approach to nested + mappings was shown to us by + Turner, David + David Turner, whose languages + KRC + KRC and + Miranda + Miranda provide elegant formalisms for dealing with these constructs. The + examples in this section (see also + exercise) are adapted from Turner 1981. + In section, well see + how this approach generalizes to infinite sequences. + Consider this problem: Given a positive integer + $n$, find all ordered pairs of distinct positive + integers $i$ and $j$, + where $1\leq j < i\leq n$, such that + $i +j$ is prime. For example, if + $n$ is 6, then the pairs are the following: + + \[ + \begin{array}{c|ccccccc} + i & 2 & 3 & 4 & 4 & 5 & 6 & 6 \\ + j & 1 & 2 & 1 & 3 & 2 & 1 & 5 \\ + \hline + i+j & 3 & 5 & 5 & 7 & 7 & 7 & 11 + \end{array} + \] + + A natural way to organize this computation is to generate the sequence + of all ordered pairs of positive integers less than or equal to + $n$, filter to select those pairs whose sum is + prime, and then, for each pair $(i, j)$ that + passes through the filter, produce the triple + $(i, j, i+j)$. + + + + Here is a way to generate the sequence of pairs: For each integer + $i\leq n$, enumerate the integers + $j < i$, and for each such + $i$ and $j$ + generate the pair $(i, j)$. In terms of + sequence operations, we map along the sequence + + (enumerate-interval 1 n). + enumerate_interval(1, n). + + + For each $i$ in this sequence, we map along the + sequence + + (enumerate-interval 1 (- i 1)). + + + enumerate_interval(1, i - 1). + + + For each $j$ in this latter sequence, we + generate the pair + + (list i j). + list(i, j). + + This gives us a sequence of pairs for each $i$. + Combining all the sequences for all the $i$ (by + accumulating with append) produces the + required sequence of pairs:Were representing a pair here + as a list of two elements rather than as + + a Lisp pair. + an ordinary pair. + Thus, the pair $(i, j)$ is + represented as + + (list i j), + list(i, j), + + not + + (cons i j). + pair(i, j). + + + enumerate_interval_n + + + +const n = 6; + + + + enumerate_interval + enumerate_interval_n + 15 + +;; replace n below by the desired number +(accumulate append + nil + (map (lambda (i) + (map (lambda (j) (list i j)) + (enumerate-interval 1 (- i 1)))) + (enumerate-interval 1 n))) + + +accumulate(append, + null, + map(i => map(j => list(i, j), + enumerate_interval(1, i - 1)), + enumerate_interval(1, n))); + + +length(accumulate(append, + null, + map(i => map(j => list(i, j), + enumerate_interval(1, i - 1)), + enumerate_interval(1, n)))); + + + The combination of mapping and accumulating with + append is so common in this sort of program + that we will isolate it as a separate + + procedure: + function: + + + flatmap + flatmap + flatmap_example + 8 + +(define (flatmap proc seq) + (accumulate append nil (map proc seq))) + + +function flatmap(f, seq) { + return accumulate(append, null, map(f, seq)); +} + + + + flatmap_example + + + +flatmap(x => list(x, x), list(1, 2, 3, 4)); + + +length(flatmap(x => list(x, x), list(1, 2, 3, 4))); + + + Now filter this sequence of pairs to find those whose sum is prime. The + filter predicate is called for each element of the sequence; its argument + is a pair and it must extract the integers from the pair. Thus, the + predicate to apply to each element in the sequence is + + prime_sum + prime_definition + prime_sum_example + true + +(define (prime-sum? pair) + (prime? (+ (car pair) (cadr pair)))) + + +function is_prime_sum(pair) { + return is_prime(head(pair) + head(tail(pair))); +} + + + + prime_sum_example + +(prime-sum? (list 8 9)) + + +is_prime_sum(list(8, 9)); + + + Finally, generate the sequence of results by mapping over the filtered + pairs using the following + + procedure, + function, + + which constructs a triple consisting of the two elements of the pair along + with their sum: + + make_pair_sum + make_pair_sum_example + [ 8, [ 9, [ 17, null ] ] ] + +(define (make-pair-sum pair) + (list (car pair) (cadr pair) (+ (car pair) (cadr pair)))) + + +function make_pair_sum(pair) { + return list(head(pair), head(tail(pair)), + head(pair) + head(tail(pair))); +} + + + + make_pair_sum_example + +(make-pair-sum (list 8 9)) + + +make_pair_sum(list(8, 9)); + + + Combining all these steps yields the complete + + procedure: + function: + + + prime_sum_pairs + prime_sum_pairs + make_pair_sum + prime_sum + flatmap + enumerate_interval + prime_sum_pairs_example + 7 + +(define (prime-sum-pairs n) + (map make-pair-sum + (filter prime-sum? + (flatmap + (lambda (i) + (map (lambda (j) (list i j)) + (enumerate-interval 1 (- i 1)))) + (enumerate-interval 1 n))))) + + +function prime_sum_pairs(n) { + return map(make_pair_sum, + filter(is_prime_sum, + flatmap(i => map(j => list(i, j), + enumerate_interval(1, i - 1)), + enumerate_interval(1, n)))); +} + + + + prime_sum_pairs_example + +(prime-sum-pairs 15) + + +prime_sum_pairs(6); + + +length(prime_sum_pairs(6)); + + + + + + Nested mappings are also useful for sequences other than those that + enumerate intervals. Suppose we wish to generate all the + setpermutations of + permutations of a set + permutations + of a set $S$; that is, all the ways of ordering + the items in the set. For instance, the permutations of + $\{1, 2, 3\}$ are + $\{1, 2, 3\}$, + $\{ 1, 3, 2\}$, + $\{2, 1, 3\}$, + $\{ 2, 3, 1\}$, + $\{ 3, 1, 2\}$, and + $\{ 3, 2, 1\}$. Here is a plan for generating + the permutations of$S$: For each item + $x$ in $S$, + recursively generate the sequence of permutations of + $S-x$,The set + $S-x$ is the set of all elements of + $S$, excluding + $x$. and adjoin + $x$ to the front of each one. This yields, for + each $x$ in $S$, the + sequence of permutations of $S$ that begin + with$x$. Combining these sequences for + all $x$ gives all the permutations + of$S$: + 0a5// (for comments in programs) + comments in programs + programcomments in + slash (double slash // for comments in programs) + + Semicolons in Scheme code are + The character sequence // + in JavaScript programs is + + used to introduce comments. Everything from + + the semicolon + // + + to the end of the line is ignored by the interpreter. In this book we + dont use many comments; we try to make our programs self-documenting + by using descriptive names. + permutations of a setpermutations + + flatmap + permutations_example + 6 + +(define (permutations s) + (if (null? s) ; empty set? + (list nil) ; sequence containing empty set + (flatmap (lambda (x) + (map (lambda (p) (cons x p)) + (permutations (remove x s)))) + s))) + + +function permutations(s) { + return is_null(s) // empty set? + ? list(null) // sequence containing empty set + : flatmap(x => map(p => pair(x, p), + permutations(remove(x, s))), + s); +} + + + + permutations_example + +(permutations (list 1 2 3)) + + +permutations(list(1, 2, 3)); + + +length(permutations(list(1, 2, 3))); + + + Notice how this strategy reduces the problem of generating permutations of + $S$ to the problem of generating the + permutations of sets with fewer elements than + $S$. In the terminal case, we work our way down + to the empty list, which represents a set of no elements. For this, we + generate + + (list nil), + list(null), + + which is a sequence with one item, namely the set with no elements. The + + remove + remove + + + procedure + function + + used in permutations returns all the items in + a given sequence except for a given item. This can be expressed as a + simple filter: + + remove + remove + remove_example + 4 + +(define (remove item sequence) + (filter (lambda (x) (not (= x item))) + sequence)) + + +function remove(item, sequence) { + return filter(x => ! (x === item), + sequence); +} + + + + remove_example + + (remove 3 (list 1 2 3 4 5)) + + +length(remove(3, list(1, 2, 3, 4, 5))); + + +length(remove(3, list(1, 2, 3, 4, 5))); + + + + + + Write a + + procedure + function + + unique_pairs + + unique-pairs + unique_pairs + + + that, given an integer $n$, generates the + sequence of pairs $(i, j)$ with + $1\leq j < i\leq n$. Use + + unique-pairs + unique_pairs + + + to simplify the definition of + + prime-sum-pairs + prime_sum_pairs + + + given above. + + + solution_2.40 + enumerate_interval + make_pair_sum + prime_sum + flatmap + example_2.40 + 7 + +function unique_pairs(n) { + return flatmap(i => map(j => list(i, j), + enumerate_interval(1, i - 1)), + enumerate_interval(1, n)); +} +function prime_sum_pairs(n) { + return map(make_pair_sum, + filter(is_prime_sum, + unique_pairs(n))); +} + + + + + + example_2.40 + +prime_sum_pairs(6); + + +length(prime_sum_pairs(6)); + + + + + + + + + + Write a + + procedure + function + + to find all ordered triples of distinct positive integers + $i$, $j$, + and$k$ less than or equal to a given + integer $n$ that sum to a given integer + $s$. + + + enumerate_interval + flatmap + example_2.41 + 3 + +function unique_triples(n) { + return flatmap(i => flatmap(j => map(k => list(i, j, k), + enumerate_interval(1, j - 1)), + enumerate_interval(1, i - 1)), + enumerate_interval(1, n)); +} +function plus(x, y) { + return x + y; +} +function triples_that_sum_to(s, n) { + return filter(items => accumulate(plus, 0, items) === s, + unique_triples(n)); +} + + + + + + example_2.41 + +triples_that_sum_to(10, 6); + + +length(triples_that_sum_to(10, 6)); + + + + + + + +
+ + A solution to the eight-queens puzzle. + +
+ + + The + eight-queens puzzle + chess, eight-queens puzzle + puzzleseight-queens puzzle + eight-queens puzzle asks how to place eight queens on a + chessboard so that no queen is in check from any other (i.e., no two + queens are in the same row, column, or diagonal). One possible solution + is shown in figure. One way to solve the + puzzle is to work across the board, placing a queen in each column. + Once we have placed $k-1$ queens, we must place + the $k$th queen in a position where it does not + check any of the queens already on the board. We can formulate this + approach recursively: Assume that we have already generated the sequence + of all possible ways to place $k-1$ queens in + the first $k-1$ columns of the board. For + each of these ways, generate an extended set of positions by placing a + queen in each row of the $k$th column. Now + filter these, keeping only the positions for which the queen in the + $k$th column is safe with respect to the other + queens. This produces the sequence of all ways to place + $k$ queens in the first + $k$ columns. By continuing this process, we + will produce not only one solution, but all solutions to the puzzle. + + We implement this solution as a + + procedure + function + + queens, + queens, + + which returns a sequence of all solutions to the problem of placing + $n$ queens on an + $n\times n$ chessboard. + + Queens + The function queens + + + has an internal + + procedure + function + + + queen-cols + queens_cols + + + that returns the sequence of all ways to place queens in the first + $k$ columns of the board. + + example_queens + + + +queens(8); + + +length(queens(8)); + + + + queens + queens + flatmap + enumerate_interval + example_queens + +(define (queens board-size) + (define (queen-cols k) + (if (= k 0) + (list empty-board) + (filter + (lambda (positions) (safe? k positions)) + (flatmap + (lambda (rest-of-queens) + (map (lambda (new-row) + (adjoin-position new-row k rest-of-queens)) + (enumerate-interval 1 board-size))) + (queen-cols (- k 1)))))) + (queen-cols board-size)) + + +function queens(board_size) { + function queen_cols(k) { + return k === 0 + ? list(empty_board) + : filter(positions => is_safe(k, positions), + flatmap(rest_of_queens => + map(new_row => + adjoin_position(new_row, k, + rest_of_queens), + enumerate_interval(1, board_size)), + queen_cols(k - 1))); + } + return queen_cols(board_size); +} + + + In this + + procedure + function + + + rest-of-queens + rest_of_queens + + + is a way to place $k-1$ queens in the first + $k-1$ columns, and + + new-row + new_row + + is a proposed row in which to place the queen for the + $k$th column. Complete the program by + implementing the representation for sets of board positions, including the + + procedure + function + + + adjoin-position, + adjoin_position, + + + which adjoins a new row-column position to a set of positions, and + + empty-board, + empty_board, + + + which represents an empty set of positions. You must also write the + + procedure + function + + + safe?, + is_safe, + + which determines for a set of positions whether the queen in the + $k$th column is safe with respect to the others. + (Note that we need only check whether the new queen is safethe + other queens are already guaranteed safe with respect to each other.) + + + + adjoin_position + + + +function adjoin_position(row, col, rest) { + return pair(pair(row, col), rest); +} + + + + empty_board + + + +const empty_board = null; + + + + is_safe + + + +function is_safe(k, positions) { + const first_row = head(head(positions)); + const first_col = tail(head(positions)); + return accumulate((pos, so_far) => { + const row = head(pos); + const col = tail(pos); + return so_far && + first_row - first_col !== + row - col && + first_row + first_col !== + row + col && + first_row !== row; + }, + true, + tail(positions)); +} + + + Putting it all together: + + queens_solution + adjoin_position + empty_board + is_safe + queens + example_queens + 92 + + + +// click here to see all solutions + + + + + + + + + Louis Reasoner is having a terrible time doing + exercise. His + + queens + queens + + + procedure + function + + seems to work, but it runs extremely slowly. (Louis never does manage to + wait long enough for it to solve even the + $6\times 6$ case.) When Louis asks Eva Lu Ator + for help, she points out that he has interchanged the order of the nested + mappings in the + + flatmap, + flatmap, + + writing it as + + +(flatmap + (lambda (new-row) + (map (lambda (rest-of-queens) + (adjoin-position new-row k rest-of-queens)) + (queen-cols (- k 1)))) + (enumerate-interval 1 board-size)) + + +flatmap(new_row => + map(rest_of_queens => + adjoin_position(new_row, k, rest_of_queens), + queen_cols(k - 1)), + enumerate_interval(1, board_size)); + + + Explain why this interchange makes the program run slowly. Estimate + how long it will take Louiss program to solve the eight-queens + puzzle, assuming that the program in + exercise solves the puzzle in time + $T$. + + Louis's program re-evaluates the application + queen_cols(k - 1)) in each iteration + of flatmap, which happens + $n$ times for each + $k$. That means overall Louis's program will + solve the puzzle in a time of about $n^n T$ + if the program in exercise solves the + puzzle in time $T$. + + + + sequence(s)as conventional interface + conventional interfacesequence as + mappingnested +
diff --git a/xml/cn/chapter2/section2/subsection4.xml b/xml/cn/chapter2/section2/subsection4.xml new file mode 100644 index 000000000..4aa7dd919 --- /dev/null +++ b/xml/cn/chapter2/section2/subsection4.xml @@ -0,0 +1,2541 @@ + + + Example: A Picture Language + + + + picture language + + + This section presents a simple language for drawing pictures that + illustrates the power of data abstraction and closure, and also exploits + higher-order + + procedures + functions + + in an essential way. The language is designed to make it easy to + experiment with patterns such as the ones in + figure, which are composed of + repeated elements that are shifted and scaled.The picture + language is based on the language + Henderson, Peter + Peter Henderson created to construct images like + Escher, Maurits Cornelis + M.C. Eschers Square Limit woodcut (see + Henderson 1982). The woodcut incorporates a repeated + scaled pattern, similar to the arrangements drawn using the + + square-limit + square_limit + + + procedure + function + + in this section. In this language, the data objects being + combined are represented as + + procedures + functions + + rather than as list structure. Just as + + cons, + pair, + + which satisfies the + closureclosurepictclosure property of picture-language operations + closure property, allowed us to easily build arbitrarily complicated list + structure, the operations in this language, which also satisfy the closure + property, allow us to easily build arbitrarily complicated patterns. +
+ + Designs generated with the picture language. + +
+ + + +
+ + + The picture language + + + + + When we began our study of programming in + section, we emphasized the + importance of describing a language by focusing on the languages + primitives, its means of combination, and its means of abstraction. + Well follow that framework here. + + + + Part of the elegance of this picture language is that there is only one + kind of element, called a + painter(s) + painter. A painter draws an image that is shifted and scaled to + fit within a designated + frame (picture language) + parallelogram-shaped frame. For example, theres a primitive painter + well call wave + that makes a crude line drawing, + as shown in figure. +
+ + Images produced by the wave + painter, with respect to four different frames. The frames, shown + with dashed lines, are not part of the images. + +
+ + + The actual shape of the drawing depends on the frameall four + images in figure are produced by the same + wave painter, but with respect to four + different frames. Painters can be more elaborate than this: The primitive + painter called rogers paints a picture of + MITs founder, William Barton Rogers, as shown in + figure. + MITearly history of + Rogers, William Barton + William Barton Rogers (18041882) was the founder and first + president of MIT. A geologist and talented teacher, he taught at + William and Mary College and at the University of Virginia. In 1859 + he moved to Boston, where he had more time for research, worked on a + plan for establishing a polytechnic institute, and + served as Massachusettss first State Inspector of Gas Meters. +

+ When MIT was established in 1861, Rogers was elected its first + president. Rogers espoused an ideal of useful learning + that was different from the university education of the time, with its + overemphasis on the classics, which, as he wrote, stand in the + way of the broader, higher and more practical instruction and + discipline of the natural and social sciences. This + education was likewise to be different from narrow trade-school + education. In Rogerss words: +
+ The world-enforced distinction between the practical and the + scientific worker is utterly futile, and the whole experience of + modern times has demonstrated its utter worthlessness. +
+ Rogers served as president of MIT until 1870, when he resigned due to + ill health. In 1878 the second president of MIT, + Runkle, John Daniel + John Runkle, resigned under the pressure of a financial crisis + brought on by the Panic of 1873 and strain of fighting off attempts + by Harvard to take over MIT. Rogers returned to hold the office of + president until 1881. +

+ Rogers collapsed and died while addressing MITs graduating + class at the commencement exercises of 1882. Runkle quoted + Rogerss last words in a memorial address delivered that same + year: +
+ As I stand here today and see what the Institute is, I call + to mind the beginnings of science. I remember one hundred and fifty + years ago Stephen Hales published a pamphlet on the subject of + illuminating gas, in which he stated that his researches had + demonstrated that 128 grains of bituminous coal +

+ coal, bituminous + Bituminous coal, these were his last words on + earth. Here he bent forward, as if consulting some notes on the + table before him, then slowly regaining an erect position, threw + up his hands, and was translated from the scene of his earthly + labors and triumphs to the tomorrow of death, + where the mysteries of life are solved, and the disembodied + spirit finds unending satisfaction in contemplating the new and + still unfathomable mysteries of the infinite future. +
+ In the words of Francis A. Walker + Walker, Francis Amasa + (MITs third president): +
+ All his life he had borne himself most faithfully and heroically, + and he died as so good a knight would surely have wished, in + harness, at his post, and in the very part and act of public duty. +
+
+ The four images in figure + are drawn with respect to the same four frames + as the wave images in + figure. +
+ + + Images of William Barton Rogers, founder and first + president of MIT, painted with respect to the same four frames as in + figure (original image courtesy MIT Museum). + +
+
+ + + To combine images, we use various + painter(s)operations + operations that construct new painters + from given painters. For example, the + beside + beside operation takes two painters and + produces a new, compound painter that draws the first painters image + in the left half of the frame and the second painters image in the + right half of the frame. Similarly, + below + below takes two painters and produces a + compound painter that draws the first painters image below the + second painters image. Some operations transform a single painter + to produce a new painter. For example, + flip_vert + + flip-vert + flip_vert + + takes a painter and produces a painter that draws its image upside-down, and + flip_horiz + + flip-horiz + flip_horiz + + produces a painter that draws the original painters image + left-to-right reversed. + + + + Figure shows the drawing of a + painter called wave4 + that is built up in two stages starting from + wave: + + wave2 + wave2_example + +(define wave2 + (beside wave (flip-vert wave))) +(define wave4 + (below wave2 wave2)) + + +const wave2 = beside(wave, flip_vert(wave)); +const wave4 = below(wave2, wave2); + + +const heart2 = beside(heart, flip_vert(heart)); +// const heart4 = stack(heart2, heart2); + + + + wave2_example + +wave2; + + +show(heart2); +// show(heart4); + + + In building up a complex image in this manner we are exploiting the fact + that painters are + closureclosurepictclosure property of picture-language operations + closed under the languages means of combination. + The beside or + below of two painters is itself a painter; + therefore, we can use it as an element in making more complex painters. + As with building up list structure using + + cons, + pair, + + the closure of our data under the means of combination is crucial to the + ability to create complex structures while using only a few operations. + + +
+ + + wave4_web + wave2_example + +(define wave2 + (beside wave (flip-vert wave))) +(define wave4 + (below wave2 wave2)) + + +$\ $ +const wave2 = const wave4 = + beside(wave, flip_vert(wave)); below(wave2, wave2); + + + +const heart2 = beside(heart, flip_vert(heart)); +// const heart4 = stack(heart2, heart2); + + + Creating a complex figure, + starting from the wave painter of + figure. + +
+ + + + Once we can combine painters, we would like to be able to abstract typical + patterns of combining painters. We will implement the painter operations as + + Scheme procedures. + JavaScript functions. + + This means that we dont need a special abstraction mechanism in the + picture language: Since the means of combination are ordinary + Scheme procedures, + JavaScript functions, + we automatically have the capability to do anything with painter operations + that we can do with + + procedures. + functions. + + For example, we can abstract the pattern in + wave4 as + + flipped_pairs + flipped_pairs + wave4_2 + +(define (flipped-pairs painter) + (let ((painter2 (beside painter (flip-vert painter)))) + (below painter2 painter2))) + + +function flipped_pairs(painter) { + const painter2 = beside(painter, flip_vert(painter)); + return below(painter2, painter2); +} + + +function flipped_pairs(painter) { + const painter2 = beside(painter, flip_vert(painter)); + return stack(painter2, painter2); +} + + + and + + define + declare + + wave4 as an instance of this + pattern: + + wave4_2 + flipped_pairs + +(define wave4 (flipped-pairs wave)) + + +const wave4 = flipped_pairs(wave); + + +const heart4 = flipped_pairs(heart); +show(heart4); + + + + + + + +
+ + Recursive plans for right-split and + corner-split. +
+ + +
+ + Recursive plans for + right_split and + corner_split. +
+
+
+ We can also define recursive operations. Heres one that makes + painters split and branch towards the right as shown in + + + figures + + + figures + + + and + + + : + + + : + + + + right_split + right_split + right_split_example_1 + +(define (right-split painter n) + (if (= n 0) + painter + (let ((smaller (right-split painter (- n 1)))) + (beside painter (below smaller smaller))))) + + +function right_split(painter, n) { + if (n === 0) { + return painter; + } else { + const smaller = right_split(painter, n - 1); + return beside(painter, below(smaller, smaller)); + } +} + + +function right_split(painter, n) { + if (n === 0) { + return painter; + } else { + const smaller = right_split(painter, n - 1); + return beside(painter, stack(smaller, smaller)); + } +} + + + We can produce balanced patterns by branching upwards as well as towards + the right (see exercise and + figures + and): + + corner_split + corner_split + right_split + up_split + corner_split_example_1 + +(define (corner-split painter n) + (if (= n 0) + painter + (let ((up (up-split painter (- n 1))) + (right (right-split painter (- n 1)))) + (let ((top-left (beside up up)) + (bottom-right (below right right)) + (corner (corner-split painter (- n 1)))) + (beside (below painter top-left) + (below bottom-right corner)))))) + + +function corner_split(painter, n) { + if (n === 0) { + return painter; + } else { + const up = up_split(painter, n - 1); + const right = right_split(painter, n - 1); + const top_left = beside(up, up); + const bottom_right = below(right, right); + const corner = corner_split(painter, n - 1); + return beside(below(painter, top_left), + below(bottom_right, corner)); + } +} + + +function corner_split(painter, n) { + if (n === 0) { + return painter; + } else { + const up = up_split(painter, n - 1); + const right = right_split(painter, n - 1); + const top_left = beside(up, up); + const bottom_right = stack(right, right); + const corner = corner_split(painter, n - 1); + return stack(beside(top_left, corner), + beside(painter, bottom_right)); + } +} + + + + corner_split_example_1 + corner_split_example_1 + + + +show(corner_split(heart, 4)); + + + + + right_split_example_1 + right_split + + (right-split wave 4) + (right-split rogers 4) + (corner-split wave 4) + (corner-split rogers 4) + + + right_split(wave, 4); // (a) + right_split(rogers, 4); // (b) + corner_split(wave, 4); // (c) + corner_split(rogers, 4); // (d) + + + show(right_split(heart, 4)); + + + + +
+ + The recursive operation + right-split applied to the painters + wave and + rogers. + Combining four + corner-split figures produces + symmetric + square-limit + as shown in figure. + +
+ + +
+ + The recursive operation + right_split applied to the + painters wave and + rogers. + Combining four + corner_split figures + produces symmetric + square_limit + as shown in figure. + +
+
+
+ By placing four copies of a + corner-split + corner_split + + appropriately, we obtain a pattern called + + square-limit, + square_limit, + + + whose application to wave and + rogers is shown in + figure: + + square_limit + square_limit + corner_split + square_limit_example + +(define (square-limit painter n) + (let ((quarter (corner-split painter n))) + (let ((half (beside (flip-horiz quarter) quarter))) + (below (flip-vert half) half)))) + + +function square_limit(painter, n) { + const quarter = corner_split(painter, n); + const half = beside(flip_horiz(quarter), quarter); + return below(flip_vert(half), half); +} + + +function square_limit(painter, n) { + const quarter = corner_split(painter, n); + const upper_half = beside(flip_horiz(quarter), quarter); + const lower_half = beside(turn_upside_down(quarter), + flip_vert(quarter)); + return stack(upper_half, lower_half); +} + + + + square_limit_example + +square_limit(rogers, 4); + + +show(square_limit(heart, 4)); + + +
+ + + + Define the procedure + Declare the function + + up_split + + up-split + up_split + + used by + + corner-split. + corner_split. + + + It is similar to + + right-split, + right_split, + + + except that it switches the roles of below + and beside. + + + + up_split + up_split_example + + + +function up_split(painter, n) { + if (n === 0) { + return painter; + } else { + const smaller = up_split(painter, n - 1); + return stack(beside(smaller, smaller), painter); + } +} + + + + up_split_example + +up_split(rogers, 4)); + + +show(up_split(heart, 4)); + + + + + + + + Higher-order operations + + + painter(s)higher-order operations + + + In addition to abstracting patterns of combining painters, we can work at a + higher level, abstracting patterns of combining painter operations. That + is, we can view the painter operations as elements to manipulate and can + write means of combination for these + + elementsprocedures + elementsfunctions + that take painter operations as arguments and create new painter operations. + + + + + For example, + + flipped-pairs + flipped_pairs + + + and + + square-limit + square_limit + + + each arrange four copies of a painters image in a square pattern; + they differ only in how they orient the copies. One way to abstract this + pattern of painter combination is with the following + + procedure, + function, + + which takes four one-argument painter operations and produces a painter + operation that transforms a given painter with those four operations and + arranges the results in a square. + In square_of_four, + we use an extension of the syntax of lambda expressions that was introduced + in section: The body of a lambda + expression can be a block, not just a return expression. + sequence of statementslambdaas body of lambda expression + lambda expressionblock as body of + Such a lambda expression has the shape + (parameters)=>{statements} or + parameter=>{statements}. + + + + Tl, + The functions tl, + + + tr, bl, and + br are the transformations to apply to the + top left copy, the top right copy, the bottom left copy, and the bottom + right copy, respectively. + + square_of_four + square_of_four + square_of_four_example + +(define (square-of-four tl tr bl br) + (lambda (painter) + (let ((top (beside (tl painter) (tr painter))) + (bottom (beside (bl painter) (br painter)))) + (below bottom top)))) + + +function square_of_four(tl, tr, bl, br) { + return painter => { + const top = beside(tl(painter), tr(painter)); + const bottom = beside(bl(painter), br(painter)); + return below(bottom, top); + }; +} + + +function square_of_four(tl, tr, bl, br) { + return painter => stack(beside(tl(painter), tr(painter)), + beside(bl(painter), br(painter))); +} + + + + square_of_four_example + identity + +square_of_four(flip_vert, identity, + quarter_turn_right, quarter_turn_left)(rogers); + + +show(square_of_four(turn_upside_down, identity, + quarter_turn_right, quarter_turn_left) + (heart) + ); + + + Then + + flipped-pairs + can be defined in terms of + + flipped_pairs + can be defined in terms of + + + + square-of-four + square_of_four + + as follows:Equivalently, we could + write + + flipped_pairs + flipped_pairs_call + square_of_four + identity + flipped_pairs_example + +(define flipped-pairs + (square-of-four identity flip-vert identity flip-vert)) + + +const flipped_pairs = square_of_four(identity, flip_vert, + identity, flip_vert); + + +const flipped_pairs = + square_of_four(turn_upside_down, flip_vert, + flip_horiz, identity); + + + + flipped_pairs + flipped_pairs_2 + square_of_four + identity + flipped_pairs_example + +(define (flipped-pairs painter) + (let ((combine4 (square-of-four identity flip-vert + identity flip-vert))) + (combine4 painter))) + + +function flipped_pairs(painter) { + const combine4 = square_of_four(identity, flip_vert, + identity, flip_vert); + return combine4(painter); +} + + +function flipped_pairs(painter) { + const combine4 = square_of_four(turn_upside_down, flip_vert, + flip_horiz, identity); + return combine4(painter); +} + + + + flipped_pairs_example + +((flipped-pairs rogers) full-frame) + + +flipped_pairs(rogers); + + +show(flipped_pairs(heart)); + + + and + + square-limit + square_limit + + + can be expressed as + + Rotate180 + + The function + rotate180 + + rotates a painter by 180 degrees. Instead of + rotate180 + we could say + + (compose flip-vert flip-horiz), + + + compose(flip_vert, flip_horiz), + + using the + + compose + compose + + + procedure + function + + from exercise. + + square_limit + square_limit_2 + square_of_four + identity + corner_split + square_limit_example_2 + +(define (square-limit painter n) + (let ((combine4 (square-of-four flip-horiz identity + rotate180 flip-vert))) + (combine4 (corner-split painter n)))) + + +function square_limit(painter, n) { + const combine4 = square_of_four(flip_horiz, identity, + rotate180, flip_vert); + return combine4(corner_split(painter, n)); +} + + +function square_limit(painter, n) { + const combine4 = square_of_four(flip_horiz, identity, + turn_upside_down, flip_vert); + return combine4(corner_split(painter, n)); +} + + + + square_limit_example_2 + + ((square-limit rogers 4) full-frame) + + +square_limit(rogers, 4)(full_frame); + + +show(square_limit(heart, 4)); + + + + + + + Right-split + + The functions + right_split + + + and + + up-split + up_split + + can be expressed as instances of a general splitting operation. + + Define a procedure + Declare a function + + split + split with the property that evaluating + + split + +// function split to be written by student + + + + right_split_up_split + split + +(define right-split (split beside below)) +(define up-split (split below beside)) + + +const right_split = split(beside, below); +const up_split = split(below, beside); + + + produces + + procedures + functions + + + right-split + right_split + + and + + up-split + with the same behaviors as the ones already defined. + + up_split + with the same behaviors as the ones already declared. + + + + + + split_definition + split_example + +function split(bigger_op, smaller_op) { + function rec_split(painter, n) { + if (n === 0) { + return painter; + } else { + const smaller = rec_split(painter, n - 1); + return bigger_op(painter, + smaller_op(smaller, smaller)); + } + } + return rec_split; +} + + + + + + split_example + split_definition + +const right_split = split(beside, below); + +right_split(rogers, 4); + + +const right_split = split(beside, stack); + +show(right_split(heart, 4)); + + + + + + + Frames + + + frame (picture language) + + Before we can show how to implement painters and their means of + combination, we must first consider + vector (mathematical)picturein picture-language frame + frames. A frame can be described by three vectorsan origin vector + and two edge vectors. The origin vector specifies the offset of the + frames origin from some absolute origin in the plane, and the edge + vectors specify the offsets of the frames corners from its origin. + If the edges are perpendicular, the frame will be rectangular. + Otherwise the frame will be a more general parallelogram. + + + + Figure shows a frame and its associated + vectors. In accordance with data abstraction, we need not be specific yet + about how frames are represented, other than to say that there is a + constructor + make_frame + + make-frame, + make_frame, + + which takes three vectors and produces a frame, and three corresponding + selectors + origin_frame + + origin-frame, + origin_frame, + + + edge1_frame + + edge1-frame, + edge1_frame, + + + and + edge2_frame + + + edge2-frame + edge2_frame + + + (see exercise). + +
+
+ A frame is described by three vectorsan origin and two edges. + + +
+
+ + + We will use coordinates in the + unit square + unit square + ($0\leq x, y\leq 1$) to specify images. With + each frame, we associate a + frame (picture language)coordinate map + frame coordinate map, which will be used to shift and scale images + to fit the frame. The map transforms the unit square into the frame by + mapping the vector $\mathbf{v}=(x, y)$ to the + vector sum + + \[ + \text{Origin(Frame)} + x\cdot \text{ Edge}_1\text{ (Frame)} + + y\cdot \text{ Edge}_2\text{ (Frame)} + \] + + For example, $(0, 0)$ is mapped to the origin of + the frame, $(1, 1)$ to the vertex diagonally + opposite the origin, and $(0.5, 0.5)$ to the + center of the frame. We can create a frames coordinate map with + the following + procedurefunction: + + Frame-coord-map + + The function + frame_coord_map + + + uses the vector operations described in + exercise below, which we assume have been + implemented using some representation for vectors. Because of data + abstraction, it doesnt matter what this vector representation is, + so long as the vector operations behave correctly. + + frame_coord_map + frame_coord_map + frame_coord_map_example + [ 14, 18 ] + frame_functions + vector_functions + +(define (frame-coord-map frame) + (lambda (v) + (add-vect + (origin-frame frame) + (add-vect (scale-vect (xcor-vect v) + (edge1-frame frame)) + (scale-vect (ycor-vect v) + (edge2-frame frame)))))) + + +function frame_coord_map(frame) { + return v => add_vect(origin_frame(frame), + add_vect(scale_vect(xcor_vect(v), + edge1_frame(frame)), + scale_vect(ycor_vect(v), + edge2_frame(frame)))); +} + + + + frame_coord_map_example + +const my_origin = make_vect(1, 2); +const my_edge_1 = make_vect(3, 4); +const my_edge_2 = make_vect(5, 6); +const my_frame = make_frame(my_origin, my_edge_1, my_edge_2); +const my_coord_map = frame_coord_map(my_frame); +const my_vector = make_vect(1, 2); +const my_mapped_vector = my_coord_map(my_vector); +my_mapped_vector; + + + Observe that applying + + frame-coord-map + frame_coord_map + + + to a frame returns a + + procedure + function + + that, given a vector, returns a vector. If the argument vector is in the + unit square, the result vector will be in the frame. For example, + + vector_example + frame_functions + vector_functions + frame_coord_map + [ 1, 2 ] + +((frame-coord-map a-frame) (make-vect 0 0)) + + +frame_coord_map(a_frame)(make_vect(0, 0)); + + +const a_frame = make_frame(make_vect(1, 2), + make_vect(3, 4), + make_vect(5, 5)); +frame_coord_map(a_frame)(make_vect(0, 0)); + + + returns the same vector as + + vector_example_2 + frame_functions + [ 1, 2 ] + +(origin-frame a-frame) + + +origin_frame(a_frame); + + +const a_frame = make_frame(make_vect(1, 2), + make_vect(3, 4), + make_vect(5, 5)); +origin_frame(a_frame); + + + + + + + + A two-dimensional + vector (mathematical)represented as pair + vector (mathematical)operations on + vector $v$ running from the + origin to a point can be represented as a pair consisting of an + $x$-coordinate and a + $y$-coordinate. Implement a data abstraction + for vectors by giving a constructor + make_vect + + make-vect + make_vect + + and corresponding selectors + xcor_vect + + xcor-vect + xcor_vect + + and + ycor_vect + + ycor-vect. + ycor_vect. + + In terms of your selectors and constructor, implement + + procedures + functions + + add_vect + + add-vect, + add_vect, + + sub_vect + + sub-vect, + sub_vect, + + and + scale_vect + + scale-vect + scale_vect + + that perform the operations vector addition, vector subtraction, and + multiplying a vector by a scalar: + + \[\begin{array}{lll} + (x_1, y_1)+(x_2, y_2) &=& (x_1+x_2, y_1+y_2)\\ + (x_1, y_1)-(x_2, y_2) &=& (x_1-x_2, y_1-y_2)\\ + s\cdot(x, y)&= &(sx, sy) + \end{array}\] + + + + vector_functions + vector_functions_example + [ 4, 6 ] + +function make_vect(x, y) { + return pair(x, y); +} +function xcor_vect(vector) { + return head(vector); +} +function ycor_vect(vector) { + return tail(vector); +} +function scale_vect(factor, vector) { + return make_vect(factor * xcor_vect(vector), + factor * ycor_vect(vector)); +} +function add_vect(vector1, vector2) { + return make_vect(xcor_vect(vector1) + + xcor_vect(vector2), + ycor_vect(vector1) + + ycor_vect(vector2)); +} +function sub_vect(vector1, vector2) { + return make_vect(xcor_vect(vector1) + - xcor_vect(vector2), + ycor_vect(vector1) + - ycor_vect(vector2)); +} + + + + vector_functions_example + +const my_vector_1 = make_vect(1, 2); +const my_vector_2 = make_vect(3, 4); +add_vect(my_vector_1, my_vector_2); + + + + + + + Here are two possible constructors for frames: + + make_frame + +(define (make-frame origin edge1 edge2) + (list origin edge1 edge2)) + +(define (make-frame origin edge1 edge2) + (cons origin (cons edge1 edge2))) + + +function make_frame(origin, edge1, edge2) { + return list(origin, edge1, edge2); +} + +function make_frame(origin, edge1, edge2) { + return pair(origin, pair(edge1, edge2)); +} + + + For each constructor supply the appropriate selectors to produce an + implementation for frames. + +
    +
  1. + + frame_functions + vector_functions + frame_functions_example + [ 5, 6 ] + +function make_frame(origin, edge1, edge2) { + return list(origin, edge1, edge2); +} +function origin_frame(frame) { + return list_ref(frame, 0); +} +function edge1_frame(frame) { + return list_ref(frame, 1); +} +function edge2_frame(frame) { + return list_ref(frame, 2); +} + + + + + frame_functions_example + +const my_origin = make_vect(1, 2); +const my_edge_1 = make_vect(3, 4); +const my_edge_2 = make_vect(5, 6); +const my_frame = make_frame(my_origin, my_edge_1, my_edge_2); +edge2_frame(my_frame); + + +
  2. + +
  3. + + frame_functions_2 + vector_functions + frame_functions_example + [ 5, 6 ] + +function make_frame(origin, edge1, edge2) { + return pair(origin, pair(edge1, edge2)); +} +function origin_frame(frame) { + return head(frame); +} +function edge1_frame(frame) { + return head(tail(frame)); +} +function edge2_frame(frame) { + return tail(tail(frame)); +} + + +
  4. +
+
+ +
+ + + Painters + + + + A painter is represented as a + painter(s)represented as proceduresfunctions + + procedure + function + + that, given a frame as argument, draws a particular image shifted and + scaled to fit the frame. That is to say, if + p is a painter and + f is a frame, then we produce + ps image in + f by calling p + with f as argument. + + + + + The details of how primitive painters are implemented depend on the + particular characteristics of the graphics system and the type of image to + be drawn. For instance, suppose we have a + + procedure + function + + draw_line + + draw-line + draw_line + + that draws a line on the screen between two specified points. Then we can + create painters for line drawings, such as the + + wave + wave + + painter in figure, from lists of line + segments as follows: + + Segments->painter + + The function + segments_to_painter + + + uses the representation for line segments described in + exercise below. It also uses the + + for-each + for_each + + + procedure + function + described in exercise. + + draw_line + +// "drawing a line" here simulated +// by printing the coordinates of +// the start and end of the line +function draw_line(v_start, v_end) { + display("line starting at"); + display(v_start); + display("line ending at"); + display(v_end); +} + + + + segments_to_painter + segments_to_painter + frame_coord_map + segment_functions + draw_line + segments_to_painter_example + +(define (segments->painter segment-list) + (lambda (frame) + (for-each + (lambda (segment) + (draw-line + ((frame-coord-map frame) (start-segment segment)) + ((frame-coord-map frame) (end-segment segment)))) + segment-list))) + + +function segments_to_painter(segment_list) { + return frame => + for_each(segment => + draw_line( + frame_coord_map(frame) + (start_segment(segment)), + frame_coord_map(frame) + (end_segment(segment))), + segment_list); +} + + + + segments_to_painter_example + +const my_origin = make_vect(1, 2); +const my_edge_1 = make_vect(3, 4); +const my_edge_2 = make_vect(5, 6); +const my_frame = make_frame(my_origin, my_edge_1, my_edge_2); + +const my_start_1 = make_vect(0, 1); +const my_end_1 = make_vect(1, 1); +const my_segment_1 = make_segment(my_start_1, my_end_1); + +const my_start_2 = make_vect(0, 2); +const my_end_2 = make_vect(2, 2); +const my_segment_2 = make_segment(my_start_2, my_end_2); + +const my_painter = segments_to_painter( + list(my_segment_1, my_segment_2)); + +my_painter(my_frame); + + + The segments are given using coordinates with respect to the unit square. + For each segment in the list, the painter transforms the segment endpoints + with the frame coordinate map and draws a line between the transformed + points. + + + + Representing painters as + + procedures + functions + + erects a powerful abstraction barrier in the picture language. We can + create and intermix all sorts of primitive painters, based on a variety of + graphics capabilities. The details of their implementation do not matter. + Any + + procedure + function + + can serve as a painter, provided that it takes a frame as argument and + draws something scaled to fit the frame. + For example, the rogers painter of + figure was constructed from a gray-level + image. For each point in a given frame, the + rogers painter determines the point in + the image that is mapped to it under the frame coordinate map, and + shades it accordingly. + + By allowing different types of painters, we are capitalizing on the + abstract data idea discussed in section, + where we argued that a rational-number representation could be anything at + all that satisfies an appropriate condition. Here were using the + fact that a painter can be implemented in any way at all, so long as it + draws something in the designated frame. + + Section also showed how pairs could be + implemented as + + procedures. + functions. + + Painters are our second example of a + + + procedural + + + functional + + + representation for data. + + + + A directed line segment in the plane can be represented as a pair of + line segmentrepresented as pair of vectors + vectorsthe vector running from the origin to the start-point of + the segment, and the vector running from the origin to the end-point of + the segment. Use your vector representation from + exercise to define a representation for + segments with a constructor + make_segment + + make-segment + make_segment + + + and selectors + start_segment + + start-segment + start_segment + + + and + end_segment + + end-segment. + end_segment. + + + + + + segment_functions + segment_functions_example + [ [ 0, 1 ], [ 1, 1 ] ] + +function make_segment(v_start, v_end) { + return pair(v_start, v_end); +} +function start_segment(v) { + return head(v); +} +function end_segment(v) { + return tail(v); +} + + + + + + + segment_functions_example + vector_functions + +const my_start = make_vect(0, 1); +const my_end = make_vect(1, 1); +const my_segment = make_segment(my_start, my_end); + +my_segment; + + + + + + + Use + + segments->painter + segments_to_painter + + + to define the following primitive painters: +
    +
  1. + The painter that draws the outline of the designated frame. +
  2. +
  3. + The painter that draws an X by connecting opposite corners of + the frame. +
  4. +
  5. + The painter that draws a diamond shape by connecting the midpoints of + the sides of the frame. +
  6. +
  7. + The wave painter. +
  8. +
+ + +
    +
  1. + The painter that draws the outline of the designated frame. + + unit_frame + frame_functions + +const unit_origin = make_vect(0, 0); +const unit_edge_1 = make_vect(1, 0); +const unit_edge_2 = make_vect(0, 1); +const unit_frame = make_frame(unit_origin, + unit_edge_1, + unit_edge_2); + + + + outline_painter + vector_functions + segment_functions + segments_to_painter + outline_painter_example + +const outline_start_1 = make_vect(0, 0); +const outline_end_1 = make_vect(1, 0); +const outline_segment_1 = make_segment(outline_start_1, + outline_end_1); +const outline_start_2 = make_vect(1, 0); +const outline_end_2 = make_vect(1, 1); +const outline_segment_2 = make_segment(outline_start_2, + outline_end_2); +const outline_start_3 = make_vect(1, 1); +const outline_end_3 = make_vect(0, 1); +const outline_segment_3 = make_segment(outline_start_3, + outline_end_3); +const outline_start_4 = make_vect(0, 1); +const outline_end_4 = make_vect(0, 0); +const outline_segment_4 = make_segment(outline_start_4, + outline_end_4); +const outline_painter = segments_to_painter( + list(outline_segment_1, + outline_segment_2, + outline_segment_3, + outline_segment_4)); + + + + outline_painter_example + unit_frame + +outline_painter(unit_frame); + + +
  2. +
  3. + The painter that draws an X by connecting opposite + corners of the frame. + + x_painter + vector_functions + segment_functions + segments_to_painter + x_painter_example + +const x_start_1 = make_vect(0, 0); +const x_end_1 = make_vect(1, 1); +const x_segment_1 = make_segment(x_start_1, + x_end_1); +const x_start_2 = make_vect(1, 0); +const x_end_2 = make_vect(0, 1); +const x_segment_2 = make_segment(x_start_2, + x_end_2); +const x_painter = segments_to_painter( + list(x_segment_1, + x_segment_2)); + + + + x_painter_example + unit_frame + x_painter + +x_painter(unit_frame); + + +
  4. +
  5. + The painter that draws a diamond shape by connecting the midpoints of + the sides of the frame. + + diamond_painter + vector_functions + segment_functions + segments_to_painter + diamond_painter_example + +const diamond_start_1 = make_vect(0.5, 0); +const diamond_end_1 = make_vect(1, 0.5); +const diamond_segment_1 = make_segment(diamond_start_1, + diamond_end_1); +const diamond_start_2 = make_vect(1, 0.5); +const diamond_end_2 = make_vect(0.5, 1); +const diamond_segment_2 = make_segment(diamond_start_2, + diamond_end_2); +const diamond_start_3 = make_vect(0.5, 1); +const diamond_end_3 = make_vect(0, 0.5); +const diamond_segment_3 = make_segment(diamond_start_3, + diamond_end_3); +const diamond_start_4 = make_vect(0, 0.5); +const diamond_end_4 = make_vect(0.5, 0); +const diamond_segment_4 = make_segment(diamond_start_4, + diamond_end_4); +const diamond_painter = segments_to_painter( + list(diamond_segment_1, + diamond_segment_2, + diamond_segment_3, + diamond_segment_4)); + + + + diamond_painter_example + unit_frame + +diamond_painter(unit_frame); + + +
  6. +
+
+
+ + + Transforming and combining painters + + + painter(s)transforming and combining + + + An operation on painters (such as + + flip-vert + flip_vert + + or beside) + works by creating a painter that invokes the original painters with respect + to frames derived from the argument frame. Thus, for example, + + flip-vert + flip_vert + + doesnt have to know how a painter works in order to flip + itit just has to know how to turn a frame upside down: The flipped + painter just uses the original painter, but in the inverted frame. + + + + Painter operations are based on the + + procedure + function + + + transform-painter, + transform_painter, + + + which takes as arguments a painter and information on how to transform a + frame and produces a new painter. The transformed painter, when called on + a frame, transforms the frame and calls the original painter on the + transformed frame. The arguments to + + transform-painter + transform_painter + + + are points (represented as vectors) that specify the corners of the new + frame: When mapped into the frame, the first point specifies the new + frames origin and the other two specify the ends of its edge vectors. + Thus, arguments within the unit square specify a frame contained within the + original frame. + + transform_painter + transform_painter + frame_functions + vector_functions + frame_coord_map + flip_vert_example + +(define (transform-painter painter origin corner1 corner2) + (lambda (frame) + (let ((m (frame-coord-map frame))) + (let ((new-origin (m origin))) + (painter + (make-frame new-origin + (sub-vect (m corner1) new-origin) + (sub-vect (m corner2) new-origin))))))) + + +function transform_painter(painter, origin, corner1, corner2) { + return frame => { + const m = frame_coord_map(frame); + const new_origin = m(origin); + return painter(make_frame( + new_origin, + sub_vect(m(corner1), new_origin), + sub_vect(m(corner2), new_origin))); + }; +} + + + + + Heres how to flip painter images vertically: + + flip_vert + flip_vert + transform_painter + flip_vert_example + +(define (flip-vert painter) + (transform-painter painter + (make-vect 0.0 1.0) ; new origin + (make-vect 1.0 1.0) ; new end of edge1 + (make-vect 0.0 0.0))) ; new end of edge2 + + +function flip_vert(painter) { + return transform_painter(painter, + make_vect(0, 1), // new origin + make_vect(1, 1), // new end of edge1 + make_vect(0, 0)); // new end of edge2 +} + + + + flip_vert_example + outline_painter + unit_frame + flip_vert + +const flipped_outline_painter = + flip_vert(outline_painter); + +flipped_outline_painter(unit_frame); + + + Using + + transform-painter, + we can easily define new transformations. + For example, we can define a + painter that shrinks its image to the upper-right quarter of the frame it + is given: + + transform_painter, + we can easily define new transformations. + For example, we can declare a + painter that shrinks its image to the upper-right quarter of the frame + it is given: + + + + shrink_to_upper_right + shrink_to_upper_right + transform_painter + shrink_to_upper_right_example + +(define (shrink-to-upper-right painter) + (transform-painter painter + (make-vect 0.5 0.5) + (make-vect 1.0 0.5) + (make-vect 0.5 1.0))) + + +function shrink_to_upper_right(painter) { + return transform_painter(painter, + make_vect(0.5, 0.5), + make_vect(1, 0.5), + make_vect(0.5, 1)); +} + + + + shrink_to_upper_right_example + outline_painter + unit_frame + +const shrunk_outline_painter = + shrink_to_upper_right(outline_painter); + +shrunk_outline_painter(unit_frame); + + + Other transformations rotate images counterclockwise by 90 + degrees + + Rotate90 + The function rotate90 + + + is a pure rotation only for square frames, because it also stretches and + shrinks the image to fit into the rotated frame. + + rotate90 + rotate90 + transform_painter + rotate90_example + +(define (rotate90 painter) + (transform-painter painter + (make-vect 1.0 0.0) + (make-vect 1.0 1.0) + (make-vect 0.0 0.0))) + + +function rotate90(painter) { + return transform_painter(painter, + make_vect(1, 0), + make_vect(1, 1), + make_vect(0, 0)); +} + + + + rotate90_example + outline_painter + unit_frame + +const rotated_outline_painter = + rotate90(outline_painter); + +rotated_outline_painter(unit_frame); + + + or squash images towards the center of the frame: + The diamond-shaped images in + figures + and were created with + + squash-inwards + squash_inwards + + + applied to wave and + rogers. + + + squash_inwards + squash_inwards + transform_painter + squash_inwards_example + +(define (squash-inwards painter) + (transform-painter painter + (make-vect 0.0 0.0) + (make-vect 0.65 0.35) + (make-vect 0.35 0.65))) + + +function squash_inwards(painter) { + return transform_painter(painter, + make_vect(0, 0), + make_vect(0.65, 0.35), + make_vect(0.35, 0.65)); +} + + + + squash_inwards_example + outline_painter + unit_frame + +const squashed_outline_painter = + squash_inwards(outline_painter); + +squashed_outline_painter(unit_frame); + + + + + + Frame transformation is also the key to + defining means of combining two or more painters. + The beside + + procedure, + function, + + for example, takes two painters, transforms them to paint in the left and + right halves of an argument frame respectively, and produces a new, + compound painter. When the compound painter is given a frame, it calls the + first transformed painter to paint in the left half of the frame and calls + the second transformed painter to paint in the right half of the frame: + + beside + beside + transform_painter + beside_example + +(define (beside painter1 painter2) + (let ((split-point (make-vect 0.5 0.0))) + (let ((paint-left + (transform-painter painter1 + (make-vect 0.0 0.0) + split-point + (make-vect 0.0 1.0))) + (paint-right + (transform-painter painter2 + split-point + (make-vect 1.0 0.0) + (make-vect 0.5 1.0)))) + (lambda (frame) + (paint-left frame) + (paint-right frame))))) + + +function beside(painter1, painter2) { + const split_point = make_vect(0.5, 0); + const paint_left = transform_painter(painter1, + make_vect(0, 0), + split_point, + make_vect(0, 1)); + const paint_right = transform_painter(painter2, + split_point, + make_vect(1, 0), + make_vect(0.5, 1)); + return frame => { + paint_left(frame); + paint_right(frame); + }; +} + + + + + beside_example + x_painter_example + +beside(x_painter, x_painter)(unit_frame); + + + + + + Observe how the painter data abstraction, and in particular the + representation of painters as + + procedures, + functions, + + makes + beside easy to implement. The + beside + + procedure + function + + need not know anything about the details of the component painters other + than that each painter will draw something in its designated frame. + + + + + Define + Declare + + the transformation + flip_horiz + + flip-horiz, + flip_horiz, + + which flips painters horizontally, and transformations that rotate painters + counterclockwise by 180 degrees and 270 degrees. + + +
    +
  1. + The transformation + + flip-horiz: + flip_horiz: + + + + flip_horiz + transform_painter + flip_horiz_example + +(define (flip-horiz painter) + (transform-painter painter + (make-vect 1.0 0.0) ; new origin + (make-vect 0.0 0.0) ; new end of edge1 + (make-vect 1.0 1.0))) ; new end of edge2 + + +function flip_horiz(painter) { + return transform_painter(painter, + make_vect(1, 0), // new origin + make_vect(0, 0), // new end of edge1 + make_vect(1, 1)); // new end of edge2 +} + + + + flip_horiz_example + x_painter_example + +flip_horiz(x_painter)(unit_frame); + + +
  2. +
  3. + The transformation + + rotate180: + rotate180: + + + + rotate180 + transform_painter + rotate180_example + +(define (rotate180 painter) + (transform-painter painter + (make-vect 1.0 1.0) ; new origin + (make-vect 0.0 1.0) ; new end of edge1 + (make-vect 1.0 0.0))) ; new end of edge2 + + +function rotate180(painter) { + return transform_painter( + painter, + make_vect(1, 1), // new origin + make_vect(0, 1), // new end of edge1 + make_vect(1, 0)); // new end of edge2 +} + + + + rotate180_example + x_painter_example + +rotate180(x_painter)(unit_frame); + + +
  4. +
  5. + The transformation + + rotate270: + rotate270: + + + + rotate270 + transform_painter + rotate270_example + +(define (rotate270 painter) + (transform-painter painter + (make-vect 0.0 1.0) ; new origin + (make-vect 0.0 0.0) ; new end of edge1 + (make-vect 1.0 0.0))) ; new end of edge2 + + +function rotate270(painter) { + return transform_painter( + painter, + make_vect(0, 1), // new origin + make_vect(0, 0), // new end of edge1 + make_vect(1, 0)); // new end of edge2 +} + + + + rotate270_example + x_painter_example + +rotate270(x_painter)(unit_frame); + + +
  6. +
+
+
+ + + + Define + Declare + + the + below + below operation for painters. + + Below + The function below + + + takes two painters as arguments. The resulting painter, given a frame, + draws with the first painter in the bottom of the frame and with the + second painter in the top. + Define below in two different + waysfirst by writing a + + procedure + function + + that is analogous to the + beside + + procedure + function + + given above, and again in terms of beside and + suitable rotation operations (from exercise). + + +
    +
  1. + First the direct method: + + below_definition + transform_painter + below_example + +(define (below painter1 painter2) + (let ((split-point (make-vect 0.0 0.5))) + (let ((paint-upper + (transform-painter painter1 + split-point + (make-vect 1.0 0.0) + (make-vect 0.0 0.5))) + (paint-lower + (transform-painter painter2 + (make-vect 0.0 0.0) + (make-vect 1.0 0.0) + split-point))) + (lambda (frame) + (paint-upper frame) + (paint-lower frame))))) + + +function below(painter1, painter2) { + const split_point = make_vect(0, 0.5); + const paint_upper = + transform_painter(painter1, + split_point, + make_vect(1, 0.5), + make_vect(0, 1)); + const paint_lower = + transform_painter(painter2, + make_vect(0, 0), + make_vect(1, 0), + split_point); + return frame => { + paint_upper(frame); + paint_lower(frame); + }; +} + + + + below_example + x_painter_example + +below(x_painter, x_painter)(unit_frame); + + +
  2. +
  3. + Now the version with rotation and + beside: + + beside + rotate90 + rotate270 + below_example + +(define (below painter1 painter2) + (rotate270 (beside (rotate90 painter1) + (rotate90 painter2)))) + + +function below(painter1, painter2) { + return rotate270(beside(rotate90(painter1), + rotate90(painter2))); +} + + +
  4. +
+
+
+ + + Levels of language for robust design + + + + + The picture language exploits some of the critical ideas weve + introduced about abstraction with + + procedures + functions + + and data. The fundamental data abstractions, painters, are implemented + using + + + procedural + + + functional + + + representations, which enables the language to handle different basic + drawing capabilities in a uniform way. The means of combination satisfy + the closure property, which permits us to easily build up complex designs. + Finally, all the tools for abstracting + + procedures + functions + + are available to us for abstracting means of combination for painters. + + + + We have also obtained a glimpse of another crucial idea about languages and + program design. This is the approach of + stratified design + design, stratified + stratified design, the notion that a complex system should be + structured as a sequence of levels that are described using a sequence of + languages. Each level is constructed by combining parts that are regarded + as primitive at that level, and the parts constructed at each level are + used as primitives at the next level. The language used at each level + of a stratified design has primitives, means of combination, and means + of abstraction appropriate to that level of detail. + + + + Stratified design pervades the engineering of complex systems. For + example, in computer engineering, resistors and transistors are + combined (and described using a language of analog circuits) to + produce parts such as and-gates and or-gates, which form the + primitives of a language for digital-circuit design. + Section describes one such + language. These parts are combined to build + processors, bus structures, and memory systems, which are in turn + combined to form computers, using languages appropriate to computer + architecture. Computers are combined to form distributed systems, + using languages appropriate for describing network interconnections, + and so on. + + + + As a tiny example of stratification, our picture language uses primitive + elements (primitive painters) that specify points and lines to provide the + shapes of a painter like rogers. The bulk of + our description of the picture language focused on combining these + primitives, using geometric combiners such as + beside and below. + We also worked at a higher level, regarding + beside and below + as primitives to be manipulated in a language whose operations, such as + + square-of-four, + square_of_four, + + + capture common patterns of combining geometric combiners. + + + + Stratified design helps make programs + robustness + robust, that is, it makes + it likely that small changes in a specification will require + correspondingly small changes in the program. For instance, suppose we + wanted to change the image based on wave + shown in figure. We could work + at the lowest level to change the detailed appearance of the + wave element; we could work at the middle + level to change the way + + corner-split + corner_split + + replicates the wave; we could work at the + highest level to change how + + square-limit + square_limit + + + arranges the four copies of the corner. In general, each level of a + stratified design provides a different vocabulary for expressing the + characteristics of the system, and a different kind of ability to change it. + + + + Make changes to the square limit of wave + shown in figure by working at + each of the levels described above. In particular: +
    +
  1. + Add some segments to the primitive + wave painter + of exercise (to add a smile, for + example). +
  2. +
  3. + Change the pattern constructed by + + corner-split + corner_split + + + (for example, by using only one copy of the + + up-split + up_split + + + and + + right-split + right_split + + + images instead of two). +
  4. +
  5. + Modify the version of + square_limit + + square-limit + square_limit + + + that uses + square_of_four + + square-of-four + square_of_four + + + so as to assemble the corners in a different pattern. + (For example, you might make the big Mr. Rogers look outward + from each corner of the square.) +
  6. +
+ + + combine4_click_here + square_of_four + identity + corner_split + square_limit_2 + square_limit_example_2 + +(define (square-limit painter n) + (let ((combine4 (square-of-four flip-horiz identity + rotate180 flip-vert))) + (combine4 (corner-split painter n)))) + + +// Click here to play with any abstraction +// used for square_limit + + + + +
+ picture language +
diff --git a/xml/cn/chapter2/section3/section3.xml b/xml/cn/chapter2/section3/section3.xml new file mode 100644 index 000000000..ad4621aff --- /dev/null +++ b/xml/cn/chapter2/section3/section3.xml @@ -0,0 +1,31 @@ +
+ 符号数据 + + + + + 数据符号 + + All the compound data objects we have used so far were constructed + ultimately from numbers. In this section we extend the representational + capability of our language by introducing the ability to work with + 字符串 + + 任意符号 + 字符串 + + as data. + + + &subsection2.3.1; + + + &subsection2.3.2; + + + &subsection2.3.3; + + + &subsection2.3.4; + +
diff --git a/xml/cn/chapter2/section3/subsection1.xml b/xml/cn/chapter2/section3/subsection1.xml new file mode 100644 index 000000000..1c2a6d885 --- /dev/null +++ b/xml/cn/chapter2/section3/subsection1.xml @@ -0,0 +1,746 @@ + + + + 引号 + 字符串 + + + + + + + + + + + + + + + 引号 + + + 字符串 + + + + + + + If we can form compound data using symbols, we can have lists such as + Sussman, Julie Esther Mazel, nieces of + + +(a b c d) +(23 45 17) +((Norah 12) (Molly 9) (Anna 7) (Lauren 6) (Charlotte 4)) + + + Lists containing symbols can look just like the expressions of our + language: + + +(* (+ 23 45) (+ x 9)) + +(define (fact n) (if (= n 1) 1 (* n (fact (- n 1))))) + + + + + In order to manipulate symbols we need a new element in our language: + the ability to quote a data object. Suppose we want to + construct the list (a b). We cant + accomplish this with (list a b), because + this expression constructs + a list of the values of a and + b rather than the symbols themselves. + This issue is well known in the context of + quotationnaturalin natural language + natural languagequotation in + natural languages, where words and sentences may be regarded either as + semantic entities or as character strings (syntactic entities). The + common practice in natural languages is to use quotation marks to + indicate that a word or a sentence is to be treated literally as a + string of characters. For instance, the first letter of + John is clearly J. If we tell somebody + say your name aloud, we expect to hear that + persons name. However, if we tell somebody + say your name aloud, we expect to hear + the words your name. Note that we are forced to nest + quotation marks to describe what somebody else might + say.Allowing quotation in a language wreaks havoc + with the ability to reason about the language in simple terms, because + it destroys the notion that equals can be substituted for equals. For + example, three is one plus two, but the word three is + not the phrase one plus two. Quotation is powerful + because it gives us a way to build expressions that manipulate other + expressions (as we will see when we write an interpreter in + chapter). But allowing statements in a language that talk + about other statements in that language makes it very difficult to + maintain any coherent principle of what equals can be + substituted for equals should mean. For example, if we know + that + Venus + the evening star is the morning star, then from the statement the + evening star is Venus we can deduce the morning star is + Venus. However, given that John knows that the evening + star is Venus we cannot infer that John knows that the + morning star is Venus. + + + quotationLispof Lisp data objects + We can follow this same practice to identify lists and symbols that are + to be treated as data objects rather than as expressions to be + evaluated. However, our format for quoting differs from that of + natural languages in that we place a quotation mark (traditionally, + the single + ' (single quote) + quote symbol') only at the + beginning of the object to be quoted. We can get away with this in + Scheme syntax because we rely on blanks and parentheses to delimit + objects. Thus, the meaning of the single quote character is to quote + the next object.The single quote is different + quotation mark, single vs.double + quotationcharacterof character strings + character stringsquotation of + "" (double quote) + from the double quote we have been using to enclose character strings + to be printed. Whereas the single quote can be used to denote lists + or symbols, the double quote is used only with character strings. + In this book, the only use for character strings is as items to be + printed. + + + string(s)quotation of + Now we can distinguish between symbols and their values: + + ab + +(define a 1) + +(define b 2) + + + + + ab + +(list a b) + + +(1 2) + + + + + +(list 'a 'b) + + +(a b) + + + + ab + +(list 'a b) + + +(a 2) + + + + + list(s)quotation of + Quotation also allows us to type in compound objects, using the + conventional printed representation for lists:Strictly, our + use of the quotation mark violates the general rule that all compound + expressions in our language should be delimited by parentheses + and look like lists. We + quote + special forms (those marked ns are not in the IEEE Scheme standard)quotequote + can recover this consistency by introducing a special form + quote, which serves the same purpose as + the quotation mark. Thus, we would type + (quote a) instead of + 'a, and we would type + (quote (a b c)) instead of + '(a b c). This is precisely how the + interpreter works. The quotation mark is just a single-character + abbreviation for wrapping the next complete expression with + quote to form + (quote expression). This is important + because it maintains the principle that any expression seen by the + interpreter can be manipulated as a data object. For instance, we + could construct the expression + (car '(a b c)), which is the same as + (car (quote (a b c))), by evaluating + (list 'car (list 'quote '(a b c))). + + +(car '(a b c)) + + +a + + + + + +(cdr '(a b c)) + + +(b c) + + + + empty listdenoted as '() + nildispensing with + In keeping with this, we can obtain the empty list by evaluating '(), + and thus dispense with the variable nil. + + + One additional primitive used in manipulating symbols is + equalitysymbolsof symbols + symbol(s)equality of + *eq? (primitive function) + primitive procedures (those marked ns are not in the IEEE Scheme standard)eq? + eq?, + which takes two symbols as arguments and tests whether they are the + same.We can consider two symbols to be + the same if they consist of the same characters in the + same order. Such a definition skirts a deep issue that we are not yet + ready to address: the meaning of sameness in a + programming language. We will return to this in + chapter + (section). + Using eq?, we can implement a useful + procedure called memq. This takes two + arguments, a symbol and a list. If the symbol is not contained in the + list (i.e., is not eq? to any item in the + list), then memq returns false. + Otherwise, it returns the sublist of the list beginning with the + first occurrence of the symbol: + memq + + memq + memq_example + +(define (memq item x) + (cond ((null? x) false) + ((eq? item (car x)) x) + (else (memq item (cdr x))))) + + + For example, the value of + + memq_example + memq + +(memq 'apple '(pear banana prune)) + + + is false, whereas the value of + + memq + +(memq 'apple '(x (apple sauce) y apple pear)) + + + is (apple pear). + + + + What would the interpreter print in response to evaluating each of the + following expressions? + + +(list 'a 'b 'c) + + + + +(list (list 'george)) + + + + +(cdr '((x1 x2) (y1 y2))) + + + + +(cadr '((x1 x2) (y1 y2))) + + + + +(pair? (car '(a short list))) + + + + memq + +(memq 'red '((red shoes) (blue socks))) + + + + memq + +(memq 'red '(red shoes blue socks)) + + + + + + Two lists are said to be + equal? + equalitylistsof lists + list(s)equality of + equal? if they contain equal elements + arranged in the same order. For example, + + equal_definition + +;; procedure equal? to be written by student + + + + equal_definition + +(equal? '(this is a list) '(this is a list)) + + + is true, but + + equal_definition + +(equal? '(this is a list) '(this (is a) list)) + + + is false. To be more precise, we can define + equal? recursively in terms of the basic + eq? equality of symbols by saying that + a and b are + equal? if they are both symbols and the + symbols are eq?, or if they are both + lists such that (car a) is + equal? to + (car b) and + (cdr a) is + equal? to + (cdr b). + Using this idea, implement equal? as a + procedure.In practice, programmers use + equal? to compare lists that contain + numbers as well as symbols. Numbers are not considered to be symbols. + The question + number(s)equality of + equalitynumbersof numbers + of whether two numerically equal numbers (as tested by + =) are also + eq? is highly implementation-dependent. + A better definition of equal? (such as + the one that comes as a primitive in Scheme) would also stipulate that + if a and b are + both numbers, then a and + b are equal? + if they are numerically equal. + + + + Eva Lu Ator types to the interpreter the expression + + +(car ''abracadabra) + + + To her surprise, the interpreter prints back + quote. Explain. + + + + + + + 到目前为止,我们使用字符串来显示消息, + 使用函数display 和 + error (例如在 + 练习中)。 + 我们可以使用字符串形成复合数据,并具有诸如这样的列表 + Wrigstad, Tobias 的女儿 + Henz, Martin 的孩子 + + +list("a", "b", "c", "d") +list(23, 45, 17) +list(list("Jakob", 27), list("Lova", 9), list("Luisa", 24)) + + + 为了将字符串与名称区分开来,我们将它们括在 + 引号双重 + ""(双引号) + 中。 例如,JavaScript 表达式 + z 表示 + 名称z的值,而 JavaScript + 表达式 "z" 表示一个由单个字符组成的字符串,即英文字母表中最后一个字母的小写形式。 + + + 通过引号,我们可以区分字符串和名称: + + ab + +const a = 1; +const b = 2; + + + + list_ab + ab + [ 1, [ 2, null ] ] + +list(a, b); + + +[1, [2, null]] + + + + list_quote_a_quote_b + [ 'a', [ 'b', null ] ] + +list("a", "b"); + + +["a", ["b", null]] + + + + list_quote_a_b + ab + [ 'a', [ 2, null ] ] + +list("a", b); + + +["a", [2, null]] + + + + + 在节 ,我们介绍了 + === 和 + !== + 作为数字上的原语谓词。 + 相等性字符串字符串的 + ===字符串作为字符串比较运算符 + ===(用于字符串比较) + "!==;4作为字符串比较运算符数值 + "!==(用于字符串比较);4 + 从现在开始, + 我们将允许两个字符串作为操作数 + === 和 + !== 。谓词 + === + 当且仅当两个字符串相同时返回 true,并且 + !== + 当且仅当两个字符串不同时返回 true。 + + 我们可以认为两个字符串相同,如果它们由相同顺序的相同字符组成。这样的定义暂时避开了一个我们尚未准备处理的深刻问题:在编程语言中相同的意义。我们将在第章中回到这个问题(第节)。 + 使用 === ,我们可以实现一个称为 member 。 + 其接受两个参数:一个字符串和一个字符串列表或一个数字和一个数字列表。 + 如果第一个参数不在列表中(即,不是 + === 列表中的任何项), + 则 + member 返回 + null。 否则,它返回 + 列表中以该字符串或数字第一次出现的子列表开始: + + member + memq + memq_example + null + +function member(item, x) { + return is_null(x) + ? null + : item === head(x) + ? x + : member(item, tail(x)); +} + + + 例如, + + memq_example + memq + +member("apple", list("pear", "banana", "prune")) + + +member("apple", list("pear", "banana", "prune")); + + + 是null,而 + + memq + [ 'apple', [ 'pear', null ] ] + +member("apple", list("x", "y", "apple", "pear")) + + +member("apple", list("x", "y", "apple", "pear")); + + + 是list("apple", "pear")。 + + + + 计算以下每个表达式的结果,使用方框表示法和列表表示法? + + +list("a", "b", "c") + + + + +list(list("george")) + + + + +tail(list(list("x1", "x2"), list("y1", "y2"))) + + + + +tail(head(list(list("x1", "x2"), list("y1", "y2")))) + + + + memq + +member("red", list("blue", "shoes", "yellow", "socks")) + + + + memq + +member("red", list("red", "shoes", "blue", "socks")) + + + + + list_abc + [ 'a', [ 'b', [ 'c', null ] ] ] + +list("a", "b", "c"); +// ["a", ["b", ["c", null]]] + + +["a", ["b", ["c", null]]] + + + + list_list_george + [ [ 'george', null ], null ] + +list(list("george")); +// [["george", null], null] + + +[["george", null], null] + + + + tail_list_list_x1 + [ [ 'y1', [ 'y2', null ] ], null ] + +tail(list(list("x1", "x2"), list("y1", "y2"))); +// [["y1", ["y2", null]], null] + + +[["y1", ["y2", null]], null] + + + + tail_head_list_list + [ 'x2', null ] + +tail(head(list(list("x1", "x2"), list("y1", "y2")))); +// ["x2", null] + + +["x2", null] + + + + member_red_list_list + null + memq + +member("red", list(list("red", "shoes"), list("blue", "socks"))); +// null + + +null + + + + member_red_list_red + [ 'shoes', [ 'blue', [ 'socks', null ] ] ] + memq + +member("shoes", list("red", "shoes", "blue", "socks")); +// ["shoes", ["blue", ["socks", null]]] + + +["red", ["shoes", ["blue", ["socks", null]]]] + + + + + + + + + 两个列表被认为是相等 + 相等性列表的列表 + 相等性数值的数值 + 相等性字符串的字符串 + 列表的相等性 + equal + 如果它们包含相同顺序的相等元素。例如, + + equal_definition + +// function equal to be written by student + + + + equal_example_1 + equal_definition + true + +equal(list("this", "is", "a", "list"), list("this", "is", "a", "list")) + + +equal(list("this", "is", "a", "list"), list("this", "is", "a", "list")); + + + 为真,但是 + + equal_definition + false + +equal(list("this", "is", "a", "list"), list("this", list("is", "a"), "list")) + + +equal(list("this", "is", "a", "list"), list("this", list("is", "a"), "list")); + + + + 的相等性 + 字符串的相等性 + 为假。 更精确地说,我们可以通过 + ===表示的数值和字符串的基本相等性来递归定义 + equal, + 即如果它们都是字符串或者 + 它们都是数字并且它们是 + ===,或者如果它们都是成对的,并且 + head(a)与 + head(b) + equal并且 + tail(a)与 + tail(b)相等。 使用这个概念, + 实现equal作为一个 + 函数。 + + 以下函数为第 1 和第 2 章实现了一般结构相等性,假定 + === 只能应用于两个数字或两个字符串,否则会报告错误。 + 下面的equal函数只会在两个参数都是函数时遇到错误,因为在第 1 和第 2 章中,函数的相等性意义尚不明确。 + + equal + true + equal_example_1 + +function equal(xs, ys) { + return is_pair(xs) + ? (is_pair(ys) && + equal(head(xs), head(ys)) && + equal(tail(xs), tail(ys))) + : is_null(xs) + ? is_null(ys) + : is_number(xs) + ? (is_number(ys) && xs === ys) + : is_boolean(xs) + ? (is_boolean(ys) && ((xs && ys) || (!xs && !ys))) + : is_string(xs) + ? (is_string(ys) && xs === ys) + : is_undefined(xs) + ? is_undefined(ys) + : // we know now that xs is a function + (is_function(ys) && xs === ys); +} + + + + + 在第 3 章,我们扩展了===,使其可以应用于任何值并给出有意义的结果(指针相等性)。 + 上述equal函数在这种扩展含义下仍然有效,但可以简化如下: + + equal_2 + true + equal_example_1 + +function equal(xs, ys) { + return is_pair(xs) + ? (is_pair(ys) && + equal(head(xs), head(ys)) && + equal(tail(xs), tail(ys))) + : xs === ys; +} + + + + + + + + + JavaScript 解释器读取双引号(双引号) + "后的字符,直到找到另一个双引号。两个引号之间的所有字符都是字符串的一部分,但不包括引号本身。但如果我们希望字符串包含双引号呢?为此,JavaScript 还允许使用引号 + '(单引号) + 单引号来分隔字符串,例如在 + 'say your name aloud'中。 + 在单引号字符串内,我们可以使用双引号反之亦然,因此 + 'say "your name" aloud' 和 + "say 'your name' aloud" 是有效的字符串,它们在位置 4 和 14 处有不同字符,假设从 0 开始计数。根据使用的字体,两个单引号可能很难与双引号区分开。你能分辨哪个是哪个并计算出以下表达式的值吗? + + which_is_which + false + +' "' === "" + + +' "' === ""; + + + + + 给定的表达式检查一个只包含一个双引号字符的字符串是否等于空字符串。结果值为 false。 + + + + + + + + + 引号 + + + 字符串 + + + diff --git a/xml/cn/chapter2/section3/subsection2.xml b/xml/cn/chapter2/section3/subsection2.xml new file mode 100644 index 000000000..8667008f8 --- /dev/null +++ b/xml/cn/chapter2/section3/subsection2.xml @@ -0,0 +1,1217 @@ + + +示例: 符号微分 + + + + 微分符号 + 符号微分 + 代数表达式求导 + + +作为符号操作的一个示例,以及进一步说明数据抽象,考虑一个设计 过程 函数 的情况,它执行代数表达式的符号微分。我们希望 过程 函数 以代数表达式和变量作为参数,并返回表达式关于该变量的导数。例如,如果 过程 函数 的参数是 $ax^2 + bx +c$ 和$x$,则 过程 函数 应返回 $2ax+b$。符号微分在 Lisp. 编程语言 Lisp.本书的原始版本使用编程语言 Scheme,Lisp 的一种方言。 中具有特殊的历史意义。它是开发符号操作计算机语言的推动示例之一。此外,它标志着研究线的开始,这种研究导致了用于符号数学工作的强大系统的发展,这些系统 目前正被越来越多的应用数学家和物理学家使用。 今天被应用数学家和物理学家常规使用。 + +在开发符号微分程序时,我们将遵循与开发部分的有理数系统时相同的数据抽象策略。也就是说,我们将首先定义一个微分算法,该算法作用于诸如和,积,变量等抽象对象,而不必担心这些对象应如何表示。只有在此之后,我们才会解决表示问题。 + + + + 抽象数据的微分程序 + + + + + +为了 + + +为了 + + +简单起见,我们将考虑一个非常简单的符号微分程序,该程序处理仅使用两个参数的加法和乘法运算构建的表达式。任何此类表达式的微分都可以通过应用以下求导规则规则化简规则来进行: + +\[ +\begin{array}{rll} +\dfrac{dc}{dx} & = & 0\quad \text{当 $c$ 为常数或与 $x$ 不同的变量时} \\[12pt] +\dfrac{dx}{dx} & = & 1 \\[12pt] +\dfrac{d(u+v)}{dx} & = & \dfrac{du}{dx}+\dfrac{dv}{dx} \\[12pt] +\dfrac{d(uv)}{dx} & = & u\left( \dfrac{dv}{dx}\right)+v\left( \dfrac{du}{dx}\right) +\end{array} +\] + + + +注意,后两个规则本质上是递归的。也就是说,为了获得和的导数,我们首先找到项的导数并将它们相加。每一项可能又是一个需要分解的表达式。逐渐分解为越来越小的部分,最终将产生一些要么是常数,要么是变量的部分,其导数将是$0$或$1$。 + + +为了在 过程 函数 中体现这些规则,我们像设计有理数实现时那样进行一些wishful thinking一厢情愿的想法。如果我们有一种表示代数表达式的方法,我们应该能够判断一个表达式是和、积、常量还是变量。我们应该能够提取表达式的各个部分。例如,对于和,我们希望能够提取加数(第一项)和被加数(第二项)。我们还应该能够从各个部分构造表达式。让我们假设我们已经有 过程 函数 来实现以下选择器、构造器和谓词: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +(variable? e) +is_variable(e) + + + +e是一个变量吗? +
+ +(same-variable? v1 v2) + +is_same_variable(v1, v2) + + + +v1和v2是相同的变量吗? +
+ +(sum? e) +is_sum(e) + + + +e是一个和吗? +
+ +(addend e) +addend(e) + + + +和的加数e。 +
+ +(augend e) +augend(e) + + + +和的被加数e。 +
+ +(make-sum a1 a2) +make_sum(a1, a2) + + + +构造a1和a2的和。 +
+ +(product? e) +is_product(e) + + + +e是一个积吗? +
+ +(multiplier e) +multiplier(e) + + + +乘积e的乘数。 +
+ +(multiplicand e) +multiplicand(e) + + + +乘积e的被乘数。 +
+ +(make-product m1 m2) + +make_product(m1, m2) + + + +构造m1和m2的乘积。 +
+使用这些,以及原语谓词 + +使用这些,以及原语函数is_number +is_number (\textit{ns}) + +number?, +is_number, + +它识别数字,我们可以将微分规则表达为以下过程:函数: + + deriv (symbolic) + deriv + is_variable + is_same_variable + is_sum + make_sum + addend + augend + is_product + multiplier + multiplicand + xyx3 + [ '*', [ 'x', [ 'y', null ] ] ] + +(define (deriv exp var) + (cond ((number? exp) 0) + ((variable? exp) + (if (same-variable? exp var) 1 0)) + ((sum? exp) + (make-sum (deriv (addend exp) var) + (deriv (augend exp) var))) + ((product? exp) + (make-sum + (make-product (multiplier exp) + (deriv (multiplicand exp) var)) + (make-product (deriv (multiplier exp) var) + (multiplicand exp)))) + (else + (error "unknown expression type -- DERIV" exp)))) + + +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? is_same_variable(exp, variable) ? 1 : 0 + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), + variable)), + make_product(deriv(multiplier(exp), + variable), + multiplicand(exp))) + : error(exp, "unknown expression type -- deriv"); +} + + +这个 +deriv + + 过程 函数 融合了完整的微分算法。由于它是用抽象数据表示的,所以无论我们选择如何表示代数表达式,只要我们设计出合适的选择器和构造器,它就能够起作用。这是我们接下来必须解决的问题。 +
+ + +代数表达式的表示 + + + 代数表达式表示 + +我们可以想象许多使用列表结构来表示代数表达式的方法。例如,我们可以使用反映通常代数符号的符号列表来表示 +$ax+b$ +将其表示为 +列表(a * x + b). + +list("a", "*", "x", "+", "b"). + + + + 不过,一个特别简单的选择是使用与 Lisp 用于组合的相同的带括号的前缀表示法;亦即,将 $ax+b$ 表示为 (+ (* a x) b)。然后我们将 + + +然而,如果我们在表示中反映表达式的数学结构将更方便;亦即,将 $ax+b$ 表示为 list("+", list("*", "a", "x"), "b")。在操作数前放置一个二元运算符被称为 前缀表示法 前缀表示法,与 section 中介绍的中缀表示法形成对比。使用前缀表示法,我们的 + + +数据表示法如下: +
    +
  • +变量是 符号。 只是字符串。 它们由原语谓词 is_string (primitive function) is_string (\textit{ns}) symbol?: is_string: + +is_variable_example + (variable? 'xyz) + is_variable("xyz"); + + +is_variablefor algebraic expressions +is_variable +is_variable_example +true + (define (variable? x) (symbol? x)) + function is_variable(x) { return is_string(x); } + +
  • +
  • +两个变量是相同的如果 表示它们的符号是 eq?: 表示它们的字符串相等: + +is_same_variable_example + (same-variable? 'xyz 'xyz) + is_same_variable("xyz", "xyz"); + + +is_same_variable +is_same_variable +true +is_variable +is_same_variable_example + +(define (same-variable? v1 v2) + (and (variable? v1) (variable? v2) (eq? v1 v2))) + + +function is_same_variable(v1, v2) { + return is_variable(v1) && is_variable(v2) && v1 === v2; +} + + +
  • +
  • +和与积被构造为列表: + +make_sum_example +make_sum + (make-sum (make-product 'x 3) (make-product 'y 'z)) + make_sum(make_product("x", 3), make_product("y", "z")); + head(tail(make_sum(make_product("x", 3), make_product("y", "z;)))); + + +make_sum +make_product +make_sum +make_sum_example +[ '*', [ 'x', [ 3, null ] ] ] + (define (make-sum a1 a2) (list '+ a1 a2)) (define (make-product m1 m2) (list '* m1 m2)) + function make_sum(a1, a2) { return list("+", a1, a2); } + +function make_product(m1, m2) { return list("*", m1, m2); } + +
  • +
  • +和是一个列表,其第一个元素是 符号+: 字符串"+": + +is_sum_example +make_sum + (sum? (make-sum 'x 3)) + is_sum(make_sum("x", 3)); + + +is_sum +is_sum +is_sum_example +true + (define (sum? x) (and (pair? x) (eq? (car x) '+))) + function is_sum(x) { return is_pair(x) && head(x) === "+"; } + +
  • +
  • +和的加数是和列表的第二项: + +addend_example +make_sum + (addend (make-sum 'x 3)) + addend(make_sum("x", 3)); + + +addend +addend +addend_example +'x' + (define (addend s) (cadr s)) + function addend(s) { return head(tail(s)); } + +
  • +
  • +和的被加数是和列表的第三项: + +augend_example +make_sum + (augend (make-sum 'x 3)) + augend(make_sum("x", 3)); + + +augend +augend +augend_example +3 + (define (augend s) (caddr s)) + function augend(s) { return head(tail(tail(s))); } + +
  • +
  • +积是一个列表,其第一个元素是 符号*: 字符串"*": + +is_product_example +make_sum + (is-product (make-product 'x 3)) + is_product(make_product("x", 3)); + + +is_product +is_product +true +is_product_example + (define (product? x) (and (pair? x) (eq? (car x) '*))) + function is_product(x) { return is_pair(x) && head(x) === "*"; } + +
  • +
  • +积的乘数是积列表的第二项: + +multiplier_example +make_sum + (multiplier (make-product 'x 3)) + multiplier(make_product("x", 3)); + + +multiplierselector +multiplier +multiplier_example +'x' + (define (multiplier p) (cadr p)) + function multiplier(s) { return head(tail(s)); } + +
  • +
  • +积的被乘数是积列表的第三项: + +multiplicand_example +make_sum + (multiplicand (make-product 'x 3)) + multiplicand(make_product("x", 3)); + + +multiplicand +multiplicand +multiplicand_example +3 + (define (multiplicand p) (caddr p)) + function multiplicand(s) { return head(tail(tail(s))); } + +
  • +
+因此,我们只需将这些与由体现的算法结合起来 +deriv 为了拥有一个工作的符号微分程序。让我们看看它的一些行为示例: + deriv_example_8 + deriv + [ '+', [ 1, [ 0, null ] ] ] + +(deriv '(+ x 3) 'x) + + +(+ 1 0) + + +deriv(list("+", "x", 3), "x"); + + +list("+", 1, 0) + + + + deriv_example_2 + deriv + [ '*', [ 'x', [ 0, null ] ] ] + +(deriv '(* x y) 'x) + + +(+ (* x 0) (* 1 y)) + + +deriv(list("*", "x", "y"), "x"); + + +head(tail(deriv(list("*", "x", "y"), "x"))); + + +list("+", list("*", "x", 0), list("*", 1, "y")) + + + + xyx3 + deriv + [ '*', [ 'x', [ 'y', null ] ] ] + +(deriv '(* (* x y) (+ x 3)) 'x) + + +(+ (* (* x y) (+ 1 0)) + (* (+ (* x 0) (* 1 y)) + (+ x 3))) + + +deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"); + + +head(tail(head(tail(deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"))))); + + +list("+", list("*", list("*", "x", "y"), list("+", 1, 0)), + list("*", list("+", list("*", "x", 0), list("*", 1, "y")), + list("+", "x", 3))) + + +程序生成的答案是正确的;但是,它们是未经化简的。的确 + + \[ + \begin{array}{lll} + \dfrac{d(xy)}{dx} & = & x\cdot 0+1\cdot y + \end{array} + \] + +但是我们希望程序能够知道 +$x\cdot 0 = 0$, + $1\cdot y = y$ +以便使程序知道 +$0+y = y$ +第二个例子的答案应该只是 +y 正如第三个例子所示,当表达式变得复杂时,这成为一个严重的问题。
+ +我们的问题很像我们在有理数实现中遇到的问题:代数表达式化简 代数表达式的化简 我们还没有将答案化简至最简形式。为了实现有理数的化简,我们只需要更改实现中的构造器和选择器。在这里,我们也可以采用类似的策略。我们不会改变 +deriv +完全不需要。相反,我们将改变 make-sum make_sum 这样如果两个加数都是数字, make-sum make_sum 将它们相加并返回它们的和。此外,如果其中一个加数是 0,那么 make-sum make_sum 将返回另一个加数。 + + make_sum_example_2 + +(make-sum 2 3) + + +make_sum(2, 3); + + + + make_sum + make_sum_2 + make_sum_example_2 + number_equal + 5 + +(define (make-sum a1 a2) + (cond ((=number? a1 0) a2) + ((=number? a2 0) a1) + ((and (number? a1) (number? a2)) (+ a1 a2)) + (else (list '+ a1 a2)))) + + +function make_sum(a1, a2) { + return number_equal(a1, 0) + ? a2 + : number_equal(a2, 0) + ? a1 + : is_number(a1) && is_number(a2) + ? a1 + a2 + : list("+", a1, a2); +} + + 这使用了 过程 函数 =number?, number_equal, 它检查表达式是否等于给定数字: + number_equal_example + +(number_equal 3 3)) + + +number_equal(3, 3); + + + + number_equal + number_equal + number_equal_example + true + +(define (=number? exp num) + (and (number? exp) (= exp num))) + + +function number_equal(exp, num) { + return is_number(exp) && exp === num; +} + + +同样,我们将更改 make-product make_product 来内置规则,即 0 乘以任何数都是 0,而 1 乘以任何数都是其自身: + + make_product_example_2 + +(make-product 2 3)) + + +make_product(2, 3); + + + + make_product + make_product_2 + make_product_example_2 + 6 + number_equal + +(define (make-product m1 m2) + (cond ((or (=number? m1 0) (=number? m2 0)) 0) + ((=number? m1 1) m2) + ((=number? m2 1) m1) + ((and (number? m1) (number? m2)) (* m1 m2)) + (else (list '* m1 m2)))) + + +function make_product(m1, m2) { + return number_equal(m1, 0) || number_equal(m2, 0) + ? 0 + : number_equal(m1, 1) + ? m2 + : number_equal(m2, 1) + ? m1 + : is_number(m1) && is_number(m2) + ? m1 * m2 + : list("*", m1, m2); +} + + + + deriv_2 + is_variable + is_same_variable + is_sum + make_sum_2 + make_product_2 + addend + augend + is_product + multiplier + multiplicand + +(define (deriv exp var) + (cond ((number? exp) 0) + ((variable? exp) + (if (same-variable? exp var) 1 0)) + ((sum? exp) + (make-sum (deriv (addend exp) var) + (deriv (augend exp) var))) + ((product? exp) + (make-sum + (make-product (multiplier exp) + (deriv (multiplicand exp) var)) + (make-product (deriv (multiplier exp) var) + (multiplicand exp)))) + (else + (error "unknown expression type -- DERIV" exp)))) + + +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? (is_same_variable(exp, variable)) ? 1 : 0 + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), + variable)), + make_product(deriv(multiplier(exp), + variable), + multiplicand(exp))) + : error(exp, "unknown expression type -- deriv"); +} + + +以下是此版本在我们的三个示例上的工作方式: + + here_is_how + deriv_2 + 1 + +(deriv '(+ x 3) 'x) + + +1 + + +deriv(list("+", "x", 3), "x"); + + +1 + + + + why_y + deriv_2 + 'y' + +(deriv '(* x y) 'x) + + +y + + +deriv(list("*", "x", "y"), "x"); + + +"y" + + + + deriv_list_times + deriv_2 + [ '*', [ 'x', [ 'y', null ] ] ] + + (deriv '(* (* x y) (+ x 3)) 'x) + + + (+ (* x y) (* y (+ x 3))) + + +deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"); + + +head(tail(deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"))); + + +list("+", list("*", "x", "y"), list("*", "y", list("+", "x", 3))) + + +尽管这已经有了很大的改进,但第三个例子表明,要让程序将表达式简化成我们可以认为是最简的形式,还有很长的路要走。代数化简的问题很复杂,因为,除了其他原因外,最简形式可能因目的不同而不同。代数表达式化简 + + + +展示如何扩展基本微分器以处理更多种类的表达式。微分规则 例如,实现微分规则 + + \[ + \begin{array}{lll} + \dfrac {d(u^{n})}{dx} & = & nu^{n-1}\left( \dfrac{du}{dx}\right) + \end{array} + \] + +通过向 +deriv +在程序中添加一个新子句并定义合适的 过程 函数 exponentiation?, is_exp, +base, exponent +并且定义合适的 make-exponentiation. make_exp. (您可以使用 符号** 字符串"**" 来表示幂运算。)内置这样的规则:任何数的 0 次幂为 1,任何数的 1 次幂为其自身。 + + + + deriv_expo + is_variable + is_same_variable + is_sum + make_sum + addend + augend + is_product + multiplier + multiplicand + number_equal + example_deriv_expo + 4 + +function base(e) { + return head(tail(e)); +} +function exponent(e) { + return head(tail(tail(e))); +} +function make_exp(base, exp) { + return number_equal(exp, 0) + ? 1 + : number_equal(exp, 1) + ? base + : list("**", base, exp); +} +function is_exp(x) { + return is_pair(x) && head(x) === "**"; +} +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? (is_same_variable(exp, variable) ? 1 : 0) + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), + variable)), + make_product(deriv(multiplier(exp), + variable), + multiplicand(exp))) + : is_exp(exp) + ? make_product(make_product(exponent(exp), + make_exp( + base(exp), + exponent(exp) - 1)), + deriv(base(exp), variable)) + : error(exp, "unknown expression type -- deriv"); +} + + + + + + + example_deriv_expo + +deriv(list("**", "x", 4), "x"); + + +head(tail(head(tail(deriv(list("**", "x", 4), "x"))))); + + + + + + + +扩展微分程序以处理任意数量的(两个或更多)项的和与积。然后,上面的最后一个例子可以表示为 + + deriv_3 + is_variable + is_same_variable + is_sum + make_sum_2 + make_product_2 + addend + is_product + multiplier + +;; change the representation of terms +;; by defining make_sum, make_product, is_sum, is_product, etc + +(define (deriv exp var) + (cond ((number? exp) 0) + ((variable? exp) + (if (same-variable? exp var) 1 0)) + ((sum? exp) + (make-sum (deriv (addend exp) var) + (deriv (augend exp) var))) + ((product? exp) + (make-sum + (make-product (multiplier exp) + (deriv (multiplicand exp) var)) + (make-product (deriv (multiplier exp) var) + (multiplicand exp)))) + (else + (error "unknown expression type -- DERIV" exp)))) + + +// change the representation of terms +// by defining augend and multiplicand + +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? (is_same_variable(exp, variable) ? 1 : 0) + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), + variable)), + make_product( + deriv(multiplier(exp), + variable), + multiplicand(exp))) + : error(exp, "unknown expression type -- deriv"); +} + + + + deriv_3_example + deriv_3 + +(deriv '(* x y (+ x 3)) 'x) + + +deriv(list("*", "x", "y", list("+", "x", 3)), "x"); + + +尝试仅通过更改和与积的表示来实现这一点,而不改变 +deriv + +尝试仅通过更改和与积的表示来实现这一点,而不改变 过程 函数 完全不需要。例如, +addend +一个和的第一个项会是首项 +augend +的和是其余各项的和。 + + +deriv_3_solution_example + +deriv(list("*", "x", "y", list("+", "x", 3)), "x"); + + + + +deriv_3_solution +deriv_3 +deriv_3_solution_example + +(deriv '(* x y (+ x 3)) 'x) + + +function augend(s) { + return accumulate(make_sum, 0, tail(tail(s))); +} +function multiplicand(s) { + return accumulate(make_product, 1, tail(tail(s))); +} + + + + + + + + +假设我们想要修改微分程序,使其适用于普通数学符号,其中 + "+" * "*" 中缀表示法前缀表示法 vs.的中缀运算符,而不是前缀运算符。由于微分程序是以抽象数据的形式定义的,我们可以通过仅更改定义微分器需作用的代数表达式的表示方式的谓词、选择器和构造器,来使其适应不同的表达式表示方式。 +
    +
  1. +展示如何实现这种方法以便区分以中缀形式表示的代数表达式, + +例如(x + (3 * (x + (y + 2))))这样的。 +如下例所示: + + +list("x", "+", list(3, "*", list("x", "+", list("y", "+", 2)))) + + + + +为简化任务,假设 + ++ +"+" + +和 + +* +"*" + +总是接受两个参数,并且表达式完全用括号括起来。 +
  2. +
  3. +如果我们允许使用标准代数符号例如,问题就会变得相当复杂, + +如(x + 3 * (x + y + 2))这样,它省略了不必要的括号,并假设乘法在加法之前执行。 +更接近普通中缀表示法的表示,它省略了不必要的括号,并假设乘法优先于加法,如本例: + + +list("x", "+", 3, "*", list("x", "+", "y", "+", 2)) + + + + +您能否为这种符号设计合适的谓词、选择器和构造器,使我们的导数程序仍然正常工作? +
  4. +
+ +
    +
  1. + + is_variable + is_same_variable + number_equal + example_2.61_1 + 4 + +function make_sum(a1, a2) { + return number_equal(a1, 0) + ? a2 + : number_equal(a2, 0) + ? a1 + : is_number(a1) && is_number(a2) + ? a1 + a2 + : list(a1, "+", a2); +} +function is_sum(x) { + return is_pair(x) && head(tail(x)) === "+"; +} +function addend(s) { + return head(s); +} +function augend(s) { + return head(tail(tail(s))); +} +function make_product(m1, m2) { + return number_equal(m1, 0) || number_equal(m2, 0) + ? 0 + : number_equal(m1, 1) + ? m2 + : number_equal(m2, 1) + ? m1 + : is_number(m1) && is_number(m2) + ? m1 * m2 + : list(m1, "*", m2); +} +function is_product(x) { + return is_pair(x) && head(tail(x)) === "*"; +} +function multiplier(s) { + return head(s); +} +function multiplicand(s) { + return head(tail(tail(s))); +} +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? (is_same_variable(exp, variable) ? 1 : 0) + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), + variable)), + make_product(deriv(multiplier( + exp), + variable), + multiplicand(exp))) + : error(exp, "unknown expression type -- deriv"); +} + + + + + + example_2.61_1 + +deriv(list("x", "*", 4), "x"); + + + + +
  2. +
  3. + + is_variable + is_same_variable + number_equal + example_2.61_2 + +function items_before_first(op, s) { + return is_string(head(s)) && head(s) === op + ? null + : pair(head(s), + items_before_first(op, tail(s))); +} +function items_after_first(op, s) { + return is_string(head(s)) && head(s) === op + ? tail(s) + : items_after_first(op, tail(s)); +} +function simplify_unary_list(s) { + return is_pair(s) && is_null(tail(s)) + ? head(s) + : s; +} +function contains_plus(s) { + return is_null(s) + ? false + : is_string(head(s)) && head(s) === "+" + ? true + : contains_plus(tail(s)); +} +function make_sum(a1, a2) { + return number_equal(a1, 0) + ? a2 + : number_equal(a2, 0) + ? a1 + : is_number(a1) && is_number(a2) + ? a1 + a2 + : list(a1, "+", a2); +} +// a sequence of terms and operators is a sum +// if and only if at least one + operator occurs +function is_sum(x) { + return is_pair(x) && contains_plus(x); +} +function addend(s) { + return simplify_unary_list(items_before_first("+", s)); +} +function augend(s) { + return simplify_unary_list(items_after_first("+", s)); +} +function make_product(m1, m2) { + return number_equal(m1, 0) || number_equal(m2, 0) + ? 0 + : number_equal(m1, 1) + ? m2 + : number_equal(m2, 1) + ? m1 + : is_number(m1) && is_number(m2) + ? m1 * m2 + : list(m1, "*", m2); +} +// a sequence of terms and operators is a product +// if and only if no + operator occurs +function is_product(x) { + return is_pair(x) && ! contains_plus(x); +} +function multiplier(s) { + return simplify_unary_list(items_before_first("*", s)); +} +function multiplicand(s) { + return simplify_unary_list(items_after_first("*", s)); +} +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? (is_same_variable(exp, variable) ? 1 : 0) + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), + variable)), + make_product(deriv(multiplier(exp), + variable), + multiplicand(exp))) + : error(exp, "unknown expression type -- deriv"); +} + + + + + + + example_2.61_2 + +deriv(list("x", "*", 4), "x"); + + + + +
  4. +
+
+ +
+ 代数表达式表示 + 微分符号 + 符号微分 + 代数表达式求导 +
--> diff --git a/xml/cn/chapter2/section3/subsection3.xml b/xml/cn/chapter2/section3/subsection3.xml new file mode 100644 index 000000000..4d56ed36e --- /dev/null +++ b/xml/cn/chapter2/section3/subsection3.xml @@ -0,0 +1,1687 @@ + + + Example: Representing Sets + + + + set + + In the previous examples we built representations for two kinds of + compound data objects: rational numbers and algebraic expressions. In + one of these examples we had the choice of simplifying (reducing) the + expressions at either construction time or selection time, but other + than that the choice of a representation for these structures in terms + of lists was straightforward. When we turn to the representation of + sets, the choice of a representation is not so obvious. Indeed, there + are a number of possible representations, and they differ + significantly from one another in several ways. + + + Informally, a set is simply a collection of distinct objects. To give + a more precise definition we can employ the method of data + abstraction. That is, we define set by specifying the + setoperations on + operations that are to be used on sets. These are + + union-set, + union_set, + + + intersection-set, + intersection_set, + + + + element-of-set?, + is_element_of_set, + + + and + + adjoin-set. + adjoin_set. + + is_element_of_set + + Element-of-set? + + The function is_@element_of_set + + + is a predicate that determines whether a given element is a member of a set. + adjoin_set + + + Adjoin-set + The function adjoin_@set + + + takes an object and a set as arguments and returns a set that contains the + elements of the original set and also the adjoined element. + union_set + + Union-set + The function union_@set + + + computes the union of two sets, which is the set containing each element + that appears in either argument. + intersection_set + + Intersection-set + The function + intersection_@set + + + computes the intersection of two sets, which is the set containing only + elements that appear in both arguments. From the viewpoint of data + abstraction, we are free to design any representation that implements these + operations in a way consistent with the interpretations given + above.If we want to be more formal, we can specify + consistent with the interpretations given above to mean + that the operations satisfy a collection of rules such as these: +
    +
  • + For any set S and any object + x, + + + (element-of-set? x (adjoin-set x S)) + + + is_element_of_set(x, adjoin_set(x, S)) + + + is true (informally: Adjoining an object to a set produces a + set that contains the object). +
  • +
  • + For any sets S and + T and any object + x, + + + (element-of-set? x (union-set S T)) + + + is_element_of_set(x, union_set(S, T)) + + + is equal to + + + (or (element-of-set? x S) (element-of-set? x T)) + + + + is_element_of_set(x, S) || is_element_of_set(x, T) + + + + (informally: The elements of + + (union-set S T) + union_set(S, T) + + + are the elements that are in S or in + T). +
  • +
  • + For any object x, + + (element-of-set? x '()) + + is_element_of_set(x, null) + + + is false (informally: + No object is an element of the empty set). +
  • +
+ setoperations on +
+ + + Sets as unordered lists + + + setrepresented as unordered list + unordered-list representation of sets + + + One way to represent a set is as a list of its elements in which no + element appears more than once. The empty set is represented by the + empty list. In this representation, + + element-of-set? + is_element_of_set + + + is similar to the + + procedure + function + + + memq + of section. + + member + of section. + + + It uses + + equal? + equal + + instead of + + eq? + === + + so that the set elements need not be + + symbols: + just numbers or strings: + + + is_element_of_setunordered-list representation + is_element_of_set + is_element_of_set_example + true + +(define (element-of-set? x set) + (cond ((null? set) false) + ((equal? x (car set)) true) + (else (element-of-set? x (cdr set))))) + + +function is_element_of_set(x, set) { + return is_null(set) + ? false + : equal(x, head(set)) + ? true + : is_element_of_set(x, tail(set)); +} + + + + is_element_of_set_example + adjoin_set + +(element-of-set? 15 (adjoin-set 10 (adjoin-set 15 (adjoin-set 20 nil)))) + + +is_element_of_set(15, + adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))); + + + Using this, we can write + + adjoin-set. + adjoin_set. + + + If the object to be adjoined is already in the set, we just return the set. + Otherwise, we use + + cons + pair + + to add the object to the list that represents the set: + + adjoin_setunordered-list representation + adjoin_set + is_element_of_set + adjoin_set_example + [ 10, [ 15, [ 20, null ] ] ] + +(define (adjoin-set x set) + (if (element-of-set? x set) + set + (cons x set))) + + +function adjoin_set(x, set) { + return is_element_of_set(x, set) + ? set + : pair(x, set); +} + + + + adjoin_set_example + +(adjoin-set 10 (adjoin-set 15 (adjoin-set 20 nil))) + + +adjoin_set(10, adjoin_set(15, adjoin_set(20, null))); + + + For + + + intersection-set + intersection_set + + + we can use a recursive strategy. If we know how to form the intersection + of set2 and the + + cdr + tail + + of set1, we only need to decide whether to + include the + + car + head + + of set1 in this. But this depends on whether + + (car set1) + head(set1) + + is also in set2. Here is the resulting + + procedure: + function: + + + intersection_setunordered-list representation + intersection_set + is_element_of_set + intersection_set_example + [ 10, [ 20, null ] ] + +(define (intersection-set set1 set2) + (cond ((or (null? set1) (null? set2)) '()) + ((element-of-set? (car set1) set2) + (cons (car set1) + (intersection-set (cdr set1) set2))) + (else (intersection-set (cdr set1) set2)))) + + +function intersection_set(set1, set2) { + return is_null(set1) || is_null(set2) + ? null + : is_element_of_set(head(set1), set2) + ? pair(head(set1), intersection_set(tail(set1), set2)) + : intersection_set(tail(set1), set2); +} + + + + intersection_set_example + adjoin_set + intersection_set + +(intersection-set + (adjoin-set 10 (adjoin-set 20 (adjoin-set 30 nil))) + (adjoin-set 10 (adjoin-set 15 (adjoin-set 20 nil)))) + + +intersection_set( + adjoin_set(10, adjoin_set(20, adjoin_set(30, null))), + adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))); + + + + + In designing a representation, one of the issues we should be concerned + with is efficiency. Consider the number of steps required by our set + operations. Since they all use + + element-of-set?, + is_element_of_set, + + + the speed of this operation has a major impact on the efficiency of the set + implementation as a whole. Now, in order to check whether an object is a + member of a set, + + element-of-set? + is_element_of_set + + + may have to scan the entire set. (In the worst case, the object turns out + not to be in the set.) Hence, if the set has + $n$ elements, + + element-of-set? + is_element_of_set + + + might take up to $n$ steps. Thus, the number of + steps required grows as $\Theta(n)$. The number + of steps required by + + + adjoin-set, + + + adjoin_set, + + + which uses + this operation, also grows as $\Theta(n)$. For + + intersection-set, + intersection_set, + + + which does an + + element-of-set? + is_element_of_set + + + check for each element of set1, the number of + steps required grows as the product of the sizes of the sets involved, or + $\Theta(n^{2})$ for two sets of size + $n$. The same will be true of + + union-set. + union_set. + + + + + Implement the + union_setunordered-list representation + + union-set + union_set + + operation for the unordered-list representation of sets. + + + is_element_of_set + adjoin_set + union_set_example + [ 20, null ] + +function union_set(set1, set2) { + return is_null(set1) + ? set2 + : adjoin_set(head(set1), + union_set(tail(set1), set2)); +} + + + + + + union_set_example + +union_set( + adjoin_set(10, adjoin_set(20, adjoin_set(30, null))), + adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))); + + +tail(tail(tail(union_set( + adjoin_set(10, adjoin_set(20, adjoin_set(30, null))), + adjoin_set(10, adjoin_set(15, adjoin_set(20, null))))))); + + + + + + + + We specified that a set would be represented as a list with no duplicates. + Now suppose we allow duplicates. For instance, the set + $\{1,2,3\}$ could be represented as the list + + (2 3 2 1 3 2 2). + + list(2, 3, 2, 1, 3, + 2, 2). + + Design + + procedures + functions + + + element-of-set?, + is_element_of_set, + + + + + adjoin-set, + adjoin_set, + + + union-set, + union_set, + + and + + intersection-set + intersection_set + + + that operate on this representation. How does the efficiency of each + compare with the corresponding + + procedure + function + + for the non-duplicate representation? Are there applications for which + you would use this representation in preference to the non-duplicate one? + + The functions is_element_of_set and + intersection_set remain unchanged. + Here is the new implementation of + adjoin_set and + union_set. + + is_element_of_set + intersection_set + union_set_example + [ 10, [ 15, [ 20, null ] ] ] + +function adjoin_set(x, set) { + return pair(x, set); +} +function union_set(set1, set2) { + return append(set1, set2); +} + + + In the version with no duplicates, the required number of steps for + is_element_of_set and + adjoin_set has an order of growth of + $O(n)$, where $n$ + is the number of element occurrences in the given representation, and + the required number of steps for + intersection_set and + union_set has an order of growth of + $O(n m)$, where $n$ + is the number of element occurrences in the representation of the first + set and $m$ is the number of element + occurrences in the representation of the second set. In the version that + allows duplicates, the number of steps for + adjoin_set shrinks to + $O(n)$, and the number of steps for + union_set shrinks to + $O(n)$. However, note that the number of + element occurrences may be much larger in the second version, because + many duplicates may accumulate. For applications where duplicate elements + are rare, the version that allows duplicates is preferrable. + + + + + setrepresented as unordered list + unordered-list representation of sets + + + Sets as ordered lists + + + setrepresented as ordered list + ordered-list representation of sets + + One way to speed up our set operations is to change the representation + so that the set elements are listed in increasing order. To do this, + we need some way to compare two objects so that we can say which is + bigger. For example, we could compare + + + symbols + + + strings + + + lexicographically, or + we could agree on some method for assigning a unique number to an + object and then compare the elements by comparing the corresponding + numbers. To keep our discussion simple, we will consider only the + case where the set elements are numbers, so that we can compare + elements using > and + <. We will represent a set of + numbers by listing its elements in increasing order. Whereas our + first representation above allowed us to represent the set + $\{1,3,6,10\}$ by listing the elements in any + order, our new representation allows only the list + + (1 3 6 10). + list(1, 3, 6, 10). + + + + One advantage of ordering shows up in + + element-of-set?: + is_element_of_set: + + + In checking for the presence of an item, we no longer have to scan the + entire set. If we reach a set element that is larger than the item we + are looking for, then we know that the item is not in the set: + x + is_element_of_setordered-list representation + is_element_of_set2 + is_element_of_set_example_2 + true + +(define (element-of-set? x set) + (cond ((null? set) false) + ((= x (car set)) true) + ((< x (car set)) false) + (else (element-of-set? x (cdr set))))) + + +function is_element_of_set(x, set) { + return is_null(set) + ? false + : x === head(set) + ? true + : x < head(set) + ? false + : // $\texttt{x > head(set)}$ + is_element_of_set(x, tail(set)); +} + + + + is_element_of_set_example_2 + +(element-of-set 15 (list 10 15 20)) + + +is_element_of_set(15, list(10, 15, 20)); + + + How many steps does this save? In the worst case, the item we are + looking for may be the largest one in the set, so the number of steps + is the same as for the unordered representation. On the other hand, + if we search for items of many different sizes we can expect that + sometimes we will be able to stop searching at a point near the + beginning of the list and that other times we will still need to + examine most of the list. On the average we should expect to have to + examine about half of the items in the set. Thus, the average + number of steps required will be about $n/2$. + This is still $\Theta(n)$ growth, but + it does save us, on the average, a factor of 2 in number of steps over the + previous implementation. + + + We obtain a more impressive speedup with + + intersection-set. + intersection_set. + + + In the unordered representation this operation required + $\Theta(n^2)$ steps, because we performed a + complete scan of set2 for each element of + set1. But with the ordered representation, + we can use a more clever method. Begin by comparing the initial elements, + x1 and + x2, of the two sets. If + x1 equals + x2, then that gives an element of the + intersection, and the rest of the intersection is the intersection of the + + cdrs + tails + + of the two sets. Suppose, however, that x1 + is less than x2. Since + x2 is the smallest element in + set2, we can immediately conclude that + x1 cannot appear anywhere in + set2 and hence is not in the intersection. + Hence, the intersection is equal to the intersection of + set2 with the + + cdr + tail + + of set1. Similarly, if + x2 is less than + x1, then the intersection is given by the + intersection of set1 with the + + cdr + tail + + of set2. Here is the + + procedure: + function: + + + intersection_setordered-list representation + intersection_set_ordered + intersection_set_example2 + [ 10, [ 20, null ] ] + +(define (intersection-set set1 set2) + (if (or (null? set1) (null? set2)) + '() + (let ((x1 (car set1)) (x2 (car set2))) + (cond ((= x1 x2) + (cons x1 + (intersection-set (cdr set1) + (cdr set2)))) + ((< x1 x2) + (intersection-set (cdr set1) set2)) + ((< x2 x1) + (intersection-set set1 (cdr set2))))))) + + +function intersection_set(set1, set2) { + if (is_null(set1) || is_null(set2)) { + return null; + } else { + const x1 = head(set1); + const x2 = head(set2); + return x1 === x2 + ? pair(x1, intersection_set(tail(set1), tail(set2))) + : x1 < x2 + ? intersection_set(tail(set1), set2) + : // $\texttt{x2 < x1}$ + intersection_set(set1, tail(set2)); + } +} + + + + intersection_set_example2 + intersection_set_ordered + +(intersection-set + (list 10 20 30) + (list 10 15 20)) + + +intersection_set( + list(10, 20, 30), + list(10, 15, 20)); + + + To estimate the number of steps required by this process, observe that at + each step we reduce the intersection problem to computing intersections of + smaller setsremoving the first element from + set1 or set2 + or both. Thus, the number of steps required is at most the sum of the sizes + of set1 and set2, + rather than the product of the sizes as with the unordered representation. + This is $\Theta(n)$ growth rather than + $\Theta(n^2)$a considerable speedup, + even for sets of moderate size. + + + Give an implementation of + + adjoin_setordered-list representation + + adjoin-set + adjoin_set + + using the ordered representation. By analogy with + + element-of-set? + is_element_of_set + + + show how to take advantage of the ordering to produce a + + procedure + function + + that requires on the average about half as many steps as with the unordered + representation. + + + adjoin_set_ordered + adjoin_set_example + [ 10, [ 15, [ 20, null ] ] ] + +function adjoin_set(x, set) { + return is_null(set) + ? list(x) + : x === head(set) + ? set + : x < head(set) + ? pair(x, set) + : pair(head(set), + adjoin_set(x, tail(set))); +} + + + + + + + + + + Give a $\Theta(n)$ implementation of + union_setordered-list representation + + union-set + union_set + + for sets represented as ordered lists. + + + union_set_ordered + union_set_ordered_example + [ 15, [ 20, [ 30, null ] ] ] + +function union_set(set1, set2) { + if (is_null(set1)) { + return set2; + } else if (is_null(set2)) { + return set1; + } else { + const x1 = head(set1); + const x2 = head(set2); + return x1 === x2 + ? pair(x1, union_set(tail(set1), + tail(set2))) + : x1 < x2 + ? pair(x1, union_set(tail(set1), set2)) + : pair(x2, union_set(set1, tail(set2))); + } +} + + + + union_set_ordered_example + adjoin_set_ordered + +union_set( + adjoin_set(10, adjoin_set(20, adjoin_set(30, null))), + adjoin_set(10, adjoin_set(15, adjoin_set(20, null)))); + + +tail(union_set( + adjoin_set(10, adjoin_set(20, adjoin_set(30, null))), + adjoin_set(10, adjoin_set(15, adjoin_set(20, null))))); + + + + + setrepresented as ordered list + ordered-list representation of sets + + + Sets as binary trees + + + setrepresented as binary tree + binary treeset represented as + treebinary + binary tree + binary search + searchbinaryof binary tree + + We can do better than the ordered-list representation by arranging the set + elements in the form of a tree. Each node of the tree holds one element of + the set, called the entry at that node, and a link to each + of two other (possibly empty) nodes. The left link points to + elements smaller than the one at the node, and the right + link to elements greater than the one at the node. + Figure shows some trees that represent + the set $\{1,3,5,7,9,11\}$. The same set may be + represented by a tree in a number of different ways. The only thing we + require for a valid representation is that all elements in the left subtree + be smaller than the node entry and that all elements in the right subtree be + larger. +
+
+ Various binary trees that represent the set + $\{ 1,3,5,7,9,11 \}$. + +
+
+ + The advantage of the tree representation is this: Suppose we want to check + whether a number $x$ is contained in a set. We + begin by comparing $x$ with the entry in the + top node. If $x$ is less than this, we know + that we need only search the left subtree; if $x$ + is greater, we need only search the right subtree. Now, if the tree is + balanced, each of these subtrees will be about half the size + of the original. Thus, in one step we have reduced the problem of + searching a tree of size $n$ to searching a tree + of size $n/2$. Since the size of the tree is + halved at each step, we should expect that the number of steps needed to + search a tree of size $n$ grows as + $\Theta(\log n)$.Halving the size of + the problem at each step is the distinguishing characteristic of + logarithmic growth + logarithmic growth, as we saw with the fast-exponentiation algorithm of + section and the half-interval + search method of + section. For + large sets, this will be a significant speedup over the previous + representations. + + + We can represent trees by using + binary treerepresented with lists + lists. Each node will be a list of + three items: the entry at the node, the left subtree, and the right + subtree. A left or a right subtree of the empty list will indicate + that there is no subtree connected there. We can describe this + representation by the following + proceduresfunctions:We + are representing sets in terms of trees, and trees in terms of + listsin effect, a data abstraction built upon a data abstraction. + We can regard the + + procedures + functions + + entry, + + left-branch, + left_branch, + + + right-branch, + right_branch, + + + and + + make-tree + make_tree + + as a way of isolating the abstraction of a binary tree from + the particular way we might wish to represent such a tree in terms of list + structure. + + entry + left_branch + right_branch + make_tree + make_tree_function + make_tree_example + 20 + +(define (entry tree) (car tree)) + +(define (left-branch tree) (cadr tree)) + +(define (right-branch tree) (caddr tree)) + +(define (make-tree entry left right) + (list entry left right)) + + +function entry(tree) { return head(tree); } + +function left_branch(tree) { return head(tail(tree)); } + +function right_branch(tree) { return head(tail(tail(tree))); } + +function make_tree(entry, left, right) { + return list(entry, left, right); +} + + + + make_tree_example + +(entry + (left-branch + (right-branch + (make-tree + 10 + 'nil + (make-tree + 30 + (make-tree 20 'nil 'nil) + 'nil))))) + + +entry( + left_branch( + right_branch( + make_tree(10, + null, + make_tree(30, + make_tree(20, null, null), + null))))); + + + + + Now we can write + + the element-of-set? procedure + is_element_of_set + + + using the strategy described above: + + is_element_of_setbinary-tree representation + make_tree_function + is_element_of_set_example_3 + true + +(define (element-of-set? x set) + (cond ((null? set) false) + ((= x (entry set)) true) + ((< x (entry set)) + (element-of-set? x (left-branch set))) + ((> x (entry set)) + (element-of-set? x (right-branch set))))) + + +function is_element_of_set(x, set) { + return is_null(set) + ? false + : x === entry(set) + ? true + : x < entry(set) + ? is_element_of_set(x, left_branch(set)) + : // $\texttt{x > entry(set)}$ + is_element_of_set(x, right_branch(set)); +} + + + + is_element_of_set_example_3 + +is_element_of_set(20, + make_tree(10, + null, + make_tree(30, + make_tree(20, null, null), + null))); + + + + + Adjoining an item to a set is implemented similarly and also requires + $\Theta(\log n)$ steps. To adjoin an item + x, we compare + x with the node entry to determine whether + x should be added to the right or to the left + branch, and having adjoined + x to the appropriate branch we piece this + newly constructed branch together with the original entry and the other + branch. If x is equal to the entry, we just + return the node. If we are asked to adjoin + x to an empty tree, we generate a tree that + has x as the entry and empty right and left + branches. Here is the + + procedure: + function: + + + adjoin_set_example_2 + +(adjoin-set 10 (adjoin-set 15 (adjoin-set 20 nil))) + + +adjoin_set(10, adjoin_set(15, adjoin_set(20, null))); + + +head(tail(head(tail(adjoin_set(10, adjoin_set(15, adjoin_set(20, null))))))); + + + + adjoin_setbinary-tree representation + adjoin_set2 + make_tree_function + adjoin_set_example_2 + [ 10, [ null, [ null, null ] ] ] + +(define (adjoin-set x set) + (cond ((null? set) (make-tree x '() '())) + ((= x (entry set)) set) + ((< x (entry set)) + (make-tree (entry set) + (adjoin-set x (left-branch set)) + (right-branch set))) + ((> x (entry set)) + (make-tree (entry set) + (left-branch set) + (adjoin-set x (right-branch set)))))) + + +function adjoin_set(x, set) { + return is_null(set) + ? make_tree(x, null, null) + : x === entry(set) + ? set + : x < entry(set) + ? make_tree(entry(set), + adjoin_set(x, left_branch(set)), + right_branch(set)) + : // $\texttt{x > entry(set)}$ + make_tree(entry(set), + left_branch(set), + adjoin_set(x, right_branch(set))); +} + + + + + The above claim that searching the tree can be performed in a logarithmic + number of steps rests on the assumption that the tree is + balanced binary tree + binary treebalanced + balanced, i.e., that the + left and the right subtree of every tree have approximately the same + number of elements, so that each subtree contains about half the + elements of its parent. But how can we be certain that the trees we + construct will be balanced? Even if we start with a balanced tree, + adding elements with + + adjoin-set + adjoin_set + + may produce an unbalanced result. Since the position of a newly adjoined + element depends on how the element compares with the items already in the + set, we can expect that if we add elements randomly the tree + will tend to be balanced on the average. But this is not a guarantee. For + example, if we start with an empty set and adjoin the numbers 1 through 7 + in sequence we end up with the highly unbalanced tree shown in + figure. In this tree all the left + subtrees are empty, so it has no advantage over a simple ordered list. One + way to solve this problem is to define an operation that transforms an + arbitrary tree into a balanced tree with the same elements. Then we can perform this transformation after every few + + adjoin-set + adjoin_set + + operations to keep our set in balance. There are also other ways to solve + this problem, most of which involve designing new data structures for which + searching and insertion both can be done in + $\Theta(\log n)$ + steps.Examples of such structures include + treeB-tree + treered-black + B-tree + red-black tree + B-trees and red-black trees. There is a large literature + on data structures devoted to this problem. See + Cormen, Thomas H. + Leiserson, Charles E. + Rivest, Ronald L. + Stein, Clifford + Cormen, Leiserson, Rivest, and Stein 2022. + + +
+
+ + Unbalanced tree produced by adjoining 1 through 7 in sequence. + + +
+
+
+ + + + Each of the following two + + procedures + functions + + converts a + binary treeconverting to a list + list(s)converting a binary tree to a + binary tree to a list. + + tree_to_list_$\ldots$ + tree_to_list_1 + make_tree_function + +(define (tree->list-1 tree) + (if (null? tree) + '() + (append (tree->list-1 (left-branch tree)) + (cons (entry tree) + (tree->list-1 (right-branch tree)))))) + + +function tree_to_list_1(tree) { + return is_null(tree) + ? null + : append(tree_to_list_1(left_branch(tree)), + pair(entry(tree), + tree_to_list_1(right_branch(tree)))); +} + + + + tree_to_list_2 + make_tree_function + +(define (tree->list-2 tree) + (define (copy-to-list tree result-list) + (if (null? tree) + result-list + (copy-to-list (left-branch tree) + (cons (entry tree) + (copy-to-list (right-branch tree) + result-list))))) + (copy-to-list tree '())) + + +function tree_to_list_2(tree) { + function copy_to_list(tree, result_list) { + return is_null(tree) + ? result_list + : copy_to_list(left_branch(tree), + pair(entry(tree), + copy_to_list(right_branch(tree), + result_list))); + } + return copy_to_list(tree, null); +} + + +
    +
  1. + Do the two + + procedures + functions + + produce the same result for every tree? If not, how do the results + differ? What lists do the two + + procedures + functions + + produce for the trees in figure? + +
  2. +
  3. + Do the two + + procedures + functions + + have the same order of growth in the number of steps required to + convert a balanced tree with $n$ elements + to a list? If not, which one grows more slowly? +
  4. +
+ +
    +
  1. + The two procedures produce the same results. For the trees in + figure, the result will always + be list(1, 3, 5, 7, 9, 11). +
  2. +
  3. + A balanced tree with $n$ elements has a + height of $O(\log{n})$ and + $O(n)$ nodes. To convert the tree into a + list using function + tree_to_list_1, we call + tree_to_list_1 + $O(n)$ times. We call + append at each node of the tree, but at + each level, we apply append + with a combined $O(n)$ elements in the + first arguments. Thus, the run time of + tree_to_list_1 has an order of + growth of $O(n\log{n})$. Instead of + append, the function + tree_to_list_2 gets away with + calling pair at each node, and + thus tree_to_list_2 has an order + of growth of $O(n)$. +
  4. +
+
+
+ + + + +
+
+ + Unbalanced tree produced by adjoining 1 through 7 in sequence. + + +
+
+ + + + + The following + + procedure + function + + + list->tree + list_to_tree + + + binary treeconverting a list to a + list(s)converting to a binary tree + converts an ordered list to a balanced binary tree. The helper + + procedure + function + + + partial-tree + partial_tree + + + takes as arguments an integer $n$ and list of + at least $n$ elements and constructs a balanced + tree containing the first $n$ elements of the + list. The result returned by + + partial-tree + partial_tree + + + is a pair (formed with + + cons) + pair) + + whose + + car + head + + is the constructed tree and whose + + cdr + tail + + is the list of elements not included in the tree. + + list_to_tree + list_to_tree + make_tree_function + list_to_tree_example + [ 10, [ null, [ null, null ] ] ] + +(define (list->tree elements) + (car (partial-tree elements (length elements)))) + +(define (partial-tree elts n) + (if (= n 0) + (cons '() elts) + (let ((left-size (quotient (- n 1) 2))) + (let ((left-result (partial-tree elts left-size))) + (let ((left-tree (car left-result)) + (non-left-elts (cdr left-result)) + (right-size (- n (+ left-size 1)))) + (let ((this-entry (car non-left-elts)) + (right-result (partial-tree (cdr non-left-elts) + right-size))) + (let ((right-tree (car right-result)) + (remaining-elts (cdr right-result))) + (cons (make-tree this-entry left-tree right-tree) + remaining-elts)))))))) + + +function list_to_tree(elements) { + return head(partial_tree(elements, length(elements))); +} +function partial_tree(elts, n) { + if (n === 0) { + return pair(null, elts); + } else { + const left_size = math_floor((n - 1) / 2); + const left_result = partial_tree(elts, left_size); + const left_tree = head(left_result); + const non_left_elts = tail(left_result); + const right_size = n - (left_size + 1); + const this_entry = head(non_left_elts); + const right_result = partial_tree(tail(non_left_elts), right_size); + const right_tree = head(right_result); + const remaining_elts = tail(right_result); + return pair(make_tree(this_entry, left_tree, right_tree), + remaining_elts); + } +} + + + + list_to_tree_example + +list_to_tree(list(10, 20, 30)); + + +head(tail(list_to_tree(list(10, 20, 30)))); + + +
    +
  1. + Write a short paragraph explaining as clearly as you can how + + partial-tree + partial_tree + + + works. Draw the tree produced by + + list->tree + list_to_tree + + + for the list + + (1 3 5 7 9 11). + + list(1, 3, 5, 7, 9, 11). + + + +
  2. +
  3. + What is the order of growth in the number of steps required by + + list->tree + list_to_tree + + + to convert a list of $n$ elements? +
  4. +
+ +
    +
  1. + The function + partial_tree(elts, n) + returns a pair whose head is a balanced tree for the first + $\lfloor (n - 1) / 2 \rfloor$ + elements of elts, and whose + tail is the list containing the remaining elements of + elts. It works by calling itself + recursively, to construct the left subtree and right subtree, and + then makes the tree, and the required return pair. Thus, the overall + function list_to_tree just needs + to call partial_tree with the + given list and its length, and return the head of the result. +

    + The tree for + list(1, 3, 5, 7, 9, 11) + is the tree on the right in + figure. +
  2. +
  3. + The order of growth for the run time of function + list_to_tree is + $O(n)$ because for every node of + the result tree, only a constant amount of work is needed. +
  4. +
+
+
+ + + Use the results of exercises + and to give + $\Theta(n)$ implementations of + union_setbinary-tree representation + + union-set + union_set + + and + intersection_setbinary-tree representation + + intersection-set + intersection_set + + + for sets implemented as (balanced) binary trees. + Exercises + are due to + Hilfinger, Paul + Paul Hilfinger. + + + + union_set_as_tree_solution + union_set_ordered + list_to_tree + tree_to_list_2 + union_set_tree_example + +function union_set_as_tree(set1, set2) { + const list1 = tree_to_list_2(set1); + const list2 = tree_to_list_2(set2); + return list_to_tree(union_set(list1, list2)); +} + + + + union_set_tree_example + +tree_to_list_2(union_set_as_tree( + list_to_tree(list(1, 3, 5, 7)), + list_to_tree(list(2, 4, 6, 8)) ) ); + + + + intersection_set_as_tree_solution + intersection_set_ordered + list_to_tree + tree_to_list_2 + intersection_set_tree_example + +function intersection_set_as_tree(set1, set2) { + const list1=tree_to_list_2(set1); + const list2=tree_to_list_2(set2); + return list_to_tree(intersection_set(list1, list2)); +} + + + + intersection_set_tree_example + +tree_to_list_2(intersection_set_as_tree( + list_to_tree(list(1, 3, 5, 8)), + list_to_tree(list(2, 3, 6, 8)) ) ); + + + + + setrepresented as binary tree + binary treeset represented as + + + Sets and information retrieval + + + + We have examined options for using lists to represent sets and have + seen how the choice of representation for a data object can have a + large impact on the performance of the programs that use the data. + Another reason for concentrating on sets is that the techniques + discussed here appear again and again in applications involving + information retrieval. + + + data basesetas set of records + setdata base as + Consider a data base containing a large number of individual records, + record, in a data base + such as the personnel files for a company or the transactions in an + accounting system. A typical data-management system spends a large + amount of time accessing or modifying the data in the records and + therefore requires an efficient method for accessing records. This is + done by identifying a part of each record to serve as an identifying + key of a recordin a data base + key. A key can be anything that uniquely identifies the + record. For a personnel file, it might be an employees ID number. + For an accounting system, it might be a transaction number. Whatever + the key is, when we define the record as a data structure we should + include a + key + key selector + + procedure + function + + that retrieves the key associated with a given record. + + + Now we represent the data base as a set of records. To locate the record + with a given key we use a + + procedure + function + + lookup, which takes as arguments a key and a + data base and which returns the record that has that key, or false if there + is no such record. + + Lookup + The function lookup + + + is implemented in almost the same way as + + element-of-set?. + is_element_of_set. + + + For example, if the set of records is implemented as an unordered list, we + could use + + record + +function make_record(key, data) { + return pair(key, data); +} +function key(record) { + return head(record); +} +function data(record) { + return tail(record); +} + + + + lookupin set of records + record + lookup_example + [ 3, 'Earth' ] + +(define (lookup given-key set-of-records) + (cond ((null? set-of-records) false) + ((equal? given-key (key (car set-of-records))) + (car set-of-records)) + (else (lookup given-key (cdr set-of-records))))) + + +function lookup(given_key, set_of_records) { + return is_null(set_of_records) + ? false + : equal(given_key, key(head(set_of_records))) + ? head(set_of_records) + : lookup(given_key, tail(set_of_records)); +} + + + + lookup_example + +lookup(3, list(make_record(2, "Venus"), + make_record(5, "Jupiter"), + make_record(4, "Mars"), + make_record(3, "Earth"), + make_record(6, "Saturn"))); + + + + + Of course, there are better ways to represent large sets than as unordered + lists. Information-retrieval systems in which records have to be + randomly accessed are typically implemented by a tree-based + method, such as the binary-tree representation discussed previously. + In designing such a system the methodology of data abstraction + can be a great help. The designer can create an initial implementation + using a simple, straightforward representation such as unordered lists. + This will be unsuitable for the eventual system, but it can be useful in + providing a quick and dirty data base with which to test the + rest of the system. Later on, the data representation can be modified to + be more sophisticated. If the data base is accessed in terms of abstract + selectors and constructors, this change in representation will not require + any changes to the rest of the system. + + + Implement the lookup + + procedure + function + + for the case where the set of records is structured as a binary tree, + ordered by the numerical values of the keys. + + + + ex_set_lookup_binary_tree + make_tree_function + record + lookup_example2 + [ 3, 'Earth' ] + +function lookup(given_key, tree_of_records) { + if (is_null(tree_of_records)) { + return null; + } else { + const this_entry = entry(tree_of_records); + const this_key = key(this_entry); + return given_key === this_key + ? this_entry + : given_key < this_key + ? lookup(given_key, + left_branch(tree_of_records)) + : lookup(given_key, + right_branch(tree_of_records)); + } +} + + + + lookup_example2 + make_tree_function + +const my_fav_planets = + make_tree(make_record(4, "Mars"), + make_tree(make_record(2, "Venus"), + null, + make_tree(make_record(3, "Earth"), + null, null)), + make_tree(make_record(6, "Saturn"), + make_tree(make_record(5, "Jupiter"), + null, null), + null)); + +lookup(3, my_fav_planets); + + + + +
+ diff --git a/xml/cn/chapter2/section3/subsection4.xml b/xml/cn/chapter2/section3/subsection4.xml new file mode 100644 index 000000000..474aa4ead --- /dev/null +++ b/xml/cn/chapter2/section3/subsection4.xml @@ -0,0 +1,1243 @@ + + + Example: Huffman Encoding Trees + + + + Huffman code + + + This section provides practice in the use of list structure and data + abstraction to manipulate sets and trees. The application is to + methods for representing data as sequences of ones and zeros (bits). + For example, the + ASCII code + codeASCII + ASCII standard code used to represent text in + computers encodes each + character, ASCII encoding + character as a sequence of seven bits. Using + seven bits allows us to distinguish $2^7$, or + 128, possible different characters. In general, if we want to distinguish + $n$ different symbols, we will need to use + $\log_2 n$ bits per symbol. If all our messages + are made up of the eight symbols A, B, C, D, E, F, G, and H, we can choose + a code with three bits per character, for example + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A000 + + C010 + + E100 + + G110
B001D011F101H111
+ With this code, the message +
+BACADAEAFABBAAAGAH
+ is encoded as the string of 54 bits +
+001000010000011000100000101000001001000000000110000111
+
+ + Codes such as ASCII and the A-through-H code above are known as + fixed-length code + codefixed-length + fixed-length codes, because they represent each symbol in the + message with the same number of bits. It is sometimes advantageous to use + variable-length code + codevariable-length + variable-length codes, in which different symbols may be + represented by different numbers of bits. For example, + Morse code + codeMorse + Morse code does not use the same number of dots and dashes for each letter + of the alphabet. In particular, E, the most frequent letter, is represented + by a single dot. In general, if our messages are such that some symbols + appear very frequently and some very rarely, we can encode data more + efficiently (i.e., using fewer bits per message) if we assign shorter + codes to the frequent symbols. Consider the following alternative code for + the letters A through H: + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A0 + + C1010 + + E1100 + + G1110
B100D1011F1101H1111
+ With this code, the same message as above is encoded as the string +
+100010100101101100011010100100000111001111 +
+ This string contains 42 bits, so it saves more than 20% in space in + comparison with the fixed-length code shown above. +
+ + One of the difficulties of using a variable-length code is knowing + when you have reached the end of a symbol in reading a sequence of + zeros and ones. Morse code solves this problem by using a special + separator code + separator code (in this case, a pause) after the sequence of + dots and dashes for each letter. Another solution is to design the + code in such a way that no complete code for any symbol is the + beginning (or prefix) of the code for another symbol. Such a + code is called a + prefix code + codeprefix + prefix code. In the example above, A is encoded by 0 and B is + encoded by 100, so no other symbol can have a code that begins with 0 or + with 100. + + + In general, we can attain significant savings if we use + variable-length prefix codes that take advantage of the relative + frequencies of the symbols in the messages to be encoded. One + particular scheme for doing this is called the Huffman encoding + method, after its discoverer, + Huffman, David + David Huffman. A Huffman code can be represented as a + binary treefor Huffman encoding + treeHuffman + binary tree whose leaves are the symbols that are encoded. At each + non-leaf node of the tree there is a set containing all the symbols in the + leaves that lie below the node. In addition, each symbol at a leaf is + assigned a weight (which is its relative frequency), and each non-leaf + node contains a weight that is the sum of all the weights of the leaves + lying below it. The weights are not used in the encoding or the decoding + process. We will see below how they are used to help construct the tree. + + +
+
+ A Huffman encoding tree. + +
+ Figure shows the Huffman tree for the + A-through-H code given above. The weights at the leaves indicate that the + tree was designed for messages in which A appears with relative frequency + 8, B with relative frequency 3, and the other letters each with relative + frequency 1. +
+ + Given a Huffman tree, we can find the encoding of any symbol by + starting at the root and moving down until we reach the leaf that + holds the symbol. Each time we move down a left branch we add a 0 to + the code, and each time we move down a right branch we add a 1. (We + decide which branch to follow by testing to see which branch either is + the leaf node for the symbol or contains the symbol in its set.) For + example, starting from the root of the tree in + figure, we arrive at the leaf for D by + following a right branch, then a left branch, then a right branch, then a + right branch; hence, the code for D is 1011. + + + To decode a bit sequence using a Huffman tree, we begin at the root + and use the successive zeros and ones of the bit sequence to determine + whether to move down the left or the right branch. Each time we come + to a leaf, we have generated a new symbol in the message, at which + point we start over from the root of the tree to find the next symbol. + For example, suppose we are given the tree above and the sequence + 10001010. Starting at the root, we move down the right branch (since + the first bit of the string is1), then down the left branch (since + the second bit is0), then down the left branch (since the third bit + is also0). This brings us to the leaf forB, so the first + symbol of the decoded message isB. Now we start again at the root, + and we make a left move because the next bit in the string is0. + This brings us to the leaf forA. Then we start again at the root + with the rest of the string 1010, so we move right, left, right, left and + reachC. Thus, the entire message is BAC. + + + + Generating Huffman trees + + + + Given an alphabet of symbols and their relative frequencies, + how do we construct the best code? (In other words, which + tree will encode messages with the fewest bits?) Huffman gave an algorithm + for doing this and showed that the resulting code is indeed the best + variable-length code for messages where the relative frequency of the + symbols matches the frequencies with which the code was constructed. + optimalityof Huffman code + Huffman codeoptimality of + We will not prove this optimality of Huffman codes here, but we will + show how Huffman trees are constructed.See + Hamming, Richard Wesley + Hamming 1980 + for a discussion of the mathematical properties of Huffman codes. + + + The algorithm for generating a Huffman tree is very simple. The idea + is to arrange the tree so that the symbols with the lowest frequency + appear farthest away from the root. Begin with the set of leaf nodes, + containing symbols and their frequencies, as determined by the initial data + from which the code is to be constructed. Now find two leaves with + the lowest weights and merge them to produce a node that has these + two nodes as its left and right branches. The weight of the new node + is the sum of the two weights. Remove the two leaves from the + original set and replace them by this new node. Now continue this + process. At each step, merge two nodes with the smallest weights, + removing them from the set and replacing them with a node that has + these two as its left and right branches. The process stops when + there is only one node left, which is the root of the entire tree. + Here is how the Huffman tree of figure was + generated: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Initial leaves + + $\{$(A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1)$\}$ +
+ Merge + + $\{$(A 8) (B 3) ($\{$C D$\}$ 2) (E 1) (F 1) (G 1) (H 1)$\}$ +
+ Merge + + $\{$(A 8) (B 3) ($\{$C D$\}$ 2) ($\{$E F$\}$ 2) (G 1) (H 1)$\}$ +
+ Merge + + $\{$(A 8) (B 3) ($\{$C D$\}$ 2) ($\{$E F$\}$ 2) ($\{$G H$\}$ 2)$\}$ +
+ Merge + + $\{$(A 8) (B 3) ($\{$C D$\}$ 2) ($\{$E F G H$\}$ 4)$\}$ +
+ Merge + + $\{$(A 8) ($\{$B C D$\}$ 5) ($\{$E F G H$\}$ 4)$\}$ +
+ Merge + + $\{$(A 8) ($\{$B C D E F G H$\}$ 9)$\}$ +
+ Final merge + + $\{$($\{$A B C D E F G H$\}$ 17)$\}$ +
+ The algorithm does not always specify a unique tree, because there may + not be unique smallest-weight nodes at each step. Also, the choice of + the order in which the two nodes are merged (i.e., which will be the + right branch and which will be the left branch) is arbitrary. +
+ + + Representing Huffman trees + + + + In the exercises below we will work with a system that uses + Huffman trees to encode and decode messages and generates Huffman + trees according to the algorithm outlined above. We will begin by + discussing how trees are represented. + + + Leaves of the tree are represented by a list consisting of the + + symbol leaf, + string "leaf", + + + the symbol at the leaf, and the weight: + + make_leaf + is_leaf + symbol_leaf + weight_leaf + make_leaf + make_leaf_example + 8 + +(define (make-leaf symbol weight) + (list 'leaf symbol weight)) + +(define (leaf? object) + (eq? (car object) 'leaf)) + +(define (symbol-leaf x) (cadr x)) + +(define (weight-leaf x) (caddr x)) + + +function make_leaf(symbol, weight) { + return list("leaf", symbol, weight); +} +function is_leaf(object) { + return head(object) === "leaf"; +} +function symbol_leaf(x) { return head(tail(x)); } + +function weight_leaf(x) { return head(tail(tail(x))); } + + + + make_leaf_example + +const my_leaf = make_leaf("A", 8); + +weight_leaf(my_leaf); + + + A general tree will be a list of + + + a string "code_tree", + + + a left branch, a right branch, a set + of symbols, and a weight. The set of symbols will be simply a list of + the symbols, rather than some more sophisticated set representation. + When we make a tree by merging two nodes, we obtain the weight of the + tree as the sum of the weights of the nodes, and the set of symbols as + the union of the sets of symbols for the nodes. Since our symbol sets are + represented as lists, we can form the union by using the + append + + procedure + function + + we defined in section: + + make_code_tree + make_code_tree + make_code_tree_example + [ 'leaf', [ 'A', [ 8, null ] ] ] + tree_property + +(define (make-code-tree left right) + (list left + right + (append (symbols left) (symbols right)) + (+ (weight left) (weight right)))) + + +function make_code_tree(left, right) { + return list("code_tree", left, right, + append(symbols(left), symbols(right)), + weight(left) + weight(right)); +} + + + + make_code_tree_example + + + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); + +make_code_tree(my_leaf_1, my_leaf_2); + + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); + +head(tail(make_code_tree(my_leaf_1, my_leaf_2))); + + + If we make a tree in this way, we have the following selectors: + + left_branch + right_branch + symbols + weight + tree_property + make_leaf + tree_property_example + 11 + +(define (left-branch tree) (car tree)) + +(define (right-branch tree) (cadr tree)) + +(define (symbols tree) + (if (leaf? tree) + (list (symbol-leaf tree)) + (caddr tree))) + +(define (weight tree) + (if (leaf? tree) + (weight-leaf tree) + (cadddr tree))) + + +function left_branch(tree) { return head(tail(tree)); } + +function right_branch(tree) { return head(tail(tail(tree))); } + +function symbols(tree) { + return is_leaf(tree) + ? list(symbol_leaf(tree)) + : head(tail(tail(tail(tree)))); +} +function weight(tree) { + return is_leaf(tree) + ? weight_leaf(tree) + : head(tail(tail(tail(tail(tree))))); +} + + + + tree_property_example + make_code_tree + make_leaf + tree_property_example + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); +const my_tree = make_code_tree(my_leaf_1, my_leaf_2); + +weight(my_tree); + + + The + + procedures + functions + + symbols and + weight must do something slightly different + depending on whether they are called with a leaf or a general tree. + These are simple examples of + generic procedurefunction + generic + + generic + + procedures + functions + + + (procedures + (functions + that can handle more than one kind of data), which we will have much more + to say about in sections + and. + + + + + The decoding + + procedure + function + + + + + + The following + + procedure + function + + implements the decoding algorithm. It takes as arguments a list of zeros + and ones, together with a Huffman tree. + + decode + decode_function + make_leaf + make_code_tree + tree_property + decode_function_example + [ 'B', [ 'B', [ 'A', null ] ] ] + +(define (decode bits tree) + (define (decode-1 bits current-branch) + (if (null? bits) + '() + (let ((next-branch + (choose-branch (car bits) current-branch))) + (if (leaf? next-branch) + (cons (symbol-leaf next-branch) + (decode-1 (cdr bits) tree)) + (decode-1 (cdr bits) next-branch))))) + (decode-1 bits tree)) + +(define (choose-branch bit branch) + (cond ((= bit 0) (left-branch branch)) + ((= bit 1) (right-branch branch)) + (else (error "bad bit -- CHOOSE-BRANCH" bit)))) + + +function decode(bits, tree) { + function decode_1(bits, current_branch) { + if (is_null(bits)) { + return null; + } else { + const next_branch = choose_branch(head(bits), + current_branch); + return is_leaf(next_branch) + ? pair(symbol_leaf(next_branch), + decode_1(tail(bits), tree)) + : decode_1(tail(bits), next_branch); + } + } + return decode_1(bits, tree); +} + +function choose_branch(bit, branch) { + return bit === 0 + ? left_branch(branch) + : bit === 1 + ? right_branch(branch) + : error(bit, "bad bit -- choose_branch"); +} + + + + decode_function_example + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); + +const my_tree = make_code_tree(my_leaf_1, my_leaf_2); + +decode(list(0, 1, 1, 0), my_tree); + + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); + +const my_tree = make_code_tree(my_leaf_1, my_leaf_2); + +tail(decode(list(0, 1, 1, 0), my_tree)); + + + The + + procedure + function + + + decode-1 + decode_1 + + takes two arguments: the list of remaining bits and the current position in + the tree. It keeps moving down the tree, choosing a left or + a right branch according to whether the next bit in the list is a zero or a + one. (This is done with the + + procedure + function + + + choose-branch.) + choose_branch.) + + + When it reaches a leaf, it returns the symbol at that leaf as the next + symbol in the message by + + + consing + it onto the result of decoding the rest of the message, + starting at the root of the tree. + + + adjoining + it to the result of decoding the rest of the message, + starting at the root of the tree. + + + Note the error check in the final clause of + + choose-branch, + choose_branch, + + + which complains if the + + procedure + function + + finds something other than a zero or a one in the input data. + + + + Sets of weighted elements + + + + In our representation of trees, each non-leaf node contains a set of + symbols, which we have represented as a simple list. However, the + tree-generating algorithm discussed above requires that we also work + with sets of leaves and trees, successively merging the two smallest + items. Since we will be required to repeatedly find the smallest item + in a set, it is convenient to use an ordered representation for this + kind of set. + + + We will represent a set of leaves and trees as a list of elements, + arranged in increasing order of weight. + The following + + + adjoin-set + + + adjoin_set + + + + procedure + function + + for constructing sets is similar to the one + described in exercise; however, items + are compared by their weights, and the element being added to the set is + never already in it. + + adjoin_setweightedfor weighted sets + adjoin_set3 + tree_property + adjoin_set3_example + [ 'leaf', [ 'B', [ 3, null ] ] ] + +(define (adjoin-set x set) + (cond ((null? set) (list x)) + ((< (weight x) (weight (car set))) (cons x set)) + (else (cons (car set) + (adjoin-set x (cdr set)))))) + + +function adjoin_set(x, set) { + return is_null(set) + ? list(x) + : weight(x) < weight(head(set)) + ? pair(x, set) + : pair(head(set), adjoin_set(x, tail(set))); +} + + + + adjoin_set3_example + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); + +adjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null)); + + +const my_leaf_1 = make_leaf("A", 8); +const my_leaf_2 = make_leaf("B", 3); + +head(adjoin_set(my_leaf_1, adjoin_set(my_leaf_2, null))); + + + + + + The following + + procedure + function + + takes a list of symbol-frequency pairs such as + + ((A 4) (B 2) (C 1) (D 1)) + + + + + list(list("A", 4), list("B", 2), list("C", 1), list("D", 1)) + + + + + and constructs an initial ordered set of leaves, ready to be merged + according to the Huffman algorithm: + + make_leaf_set + make_leaf_set + make_leaf + adjoin_set3 + make_leaf_set_example + [ 'leaf', [ 'leaf', [ 'A', null ] ] ] + +(define (make-leaf-set pairs) + (if (null? pairs) + '() + (let ((pair (car pairs))) + (adjoin-set (make-leaf (car pair) ; symbol + (cadr pair)) ; frequency + (make-leaf-set (cdr pairs)))))) + + +function make_leaf_set(pairs) { + if (is_null(pairs)) { + return null; + } else { + const first_pair = head(pairs); + return adjoin_set( + make_leaf(head(first_pair), // symbol + head(tail(first_pair))), // frequency + make_leaf_set(tail(pairs))); + } +} + + + + make_leaf_set_example + +make_leaf_set( list( list("A", 4), + list("B", 2), + list("C", 1), + list("D", 1) ) ); + + +head(make_leaf_set( list( list("A", 4), + list("B", 2), + list("C", 1), + list("D", 1) ) ) ); + + + + + + + Define + Declare + + an encoding tree and a sample message: + + sample_tree + make_leaf + make_code_tree + +(define sample-tree + (make-code-tree (make-leaf 'A 4) + (make-code-tree + (make-leaf 'B 2) + (make-code-tree (make-leaf 'D 1) + (make-leaf 'C 1))))) + +(define sample-message '(0 1 1 0 0 1 0 1 0 1 1 1 0)) + + +const sample_tree = make_code_tree(make_leaf("A", 4), + make_code_tree(make_leaf("B", 2), + make_code_tree( + make_leaf("D", 1), + make_leaf("C", 1)))); +const sample_message = list(0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0); + + + Use the decode + + procedure + function + + to decode the message, and give the result. + + + + decode_solution + decode_function + sample_tree + +decode(sample_message, sample_tree); +// should be: ["A", ["D", ["A", ["B", ["B", ["C", ["A", null]]]]]]] + + + + + + + + The encode + + procedure + function + + takes as arguments a message and a tree and produces the list of bits that + gives the encoded message. + + encode + encode + encode_symbol + +(define (encode message tree) + (if (null? message) + '() + (append (encode-symbol (car message) tree) + (encode (cdr message) tree)))) + + +function encode(message, tree) { + return is_null(message) + ? null + : append(encode_symbol(head(message), tree), + encode(tail(message), tree)); +} + + + + encode_symbol + + // encode_symbol function to be written by students + + + + + Encode-symbol is a procedure, + which you must write, that returns the list of bits that encodes a given + symbol according to a given tree. + + + The function encode_symbol, + which you must write, returns the list of bits that encodes a given symbol + according to + a given tree. + + + You should design + + encode-symbol + encode_symbol + + + so that it signals an error if the symbol is not in the tree at all. + Test your + + procedure + function + + by encoding the result you obtained in + exercise with the sample tree and + seeing whether it is the same as the original sample message. + + + encode_symbol_solution + encode_symbol_example + true + +function encode_symbol(symbol, tree) { + function contains_symbol(symbol, current_tree) { + return ! is_null(member(symbol, symbols(current_tree))); + } + if (is_leaf(tree)) { + return null; + } else { + const left_tree = left_branch(tree); + const right_tree = right_branch(tree); + return contains_symbol(symbol, left_tree) + ? pair(0, encode_symbol(symbol, left_tree)) + : contains_symbol(symbol, right_tree) + ? pair(1, encode_symbol(symbol, right_tree)) + : error("symbol not found -- encode_symbol"); + } +} + + + + encode_symbol_example + decode_function + encode + sample_tree + +equal(encode(decode(sample_message, sample_tree), + sample_tree), + sample_message); + + + + + + + The following + + procedure + function + + takes as its argument a list of symbol-frequency pairs (where no symbol + appears in more than one pair) and generates a Huffman encoding tree + according to the Huffman algorithm. + + generate_huffman_tree + generate_huffman_tree + make_leaf_set + successive_merge + +(define (generate-huffman-tree pairs) + (successive-merge (make-leaf-set pairs))) + + +function generate_huffman_tree(pairs) { + return successive_merge(make_leaf_set(pairs)); +} + + + + successive_merge + + // successive_merge function to be written by student + + + + + Make-leaf-set is the procedure + given above that transforms the + list of pairs into an ordered set of leaves. + Successive-merge + is the procedure + you must write, using make-code-tree to + successively merge the smallest-weight elements of the set until there + is only one element left, which is the desired Huffman tree. + + + The function make_leaf_set + that transforms the list of pairs into an ordered set of leaves is + given above. Write the function + successive_merge using + make_code_tree to successively + merge the smallest-weight elements of the set until there is only one + element left, which is the desired Huffman tree. + + + (This + + procedure + function + + is slightly tricky, but not really complicated. If you find yourself + designing a complex + + procedure, + function, + then you are almost certainly doing something wrong. You can take + significant advantage of the fact that we are using an ordered set + representation.) + + + + successive_merge_solution + make_code_tree + adjoin_set3 + generate_huffman_tree + generate_huffman_tree_example + true + +function successive_merge(leaves) { + return length(leaves) === 1 + ? head(leaves) + : successive_merge( + adjoin_set( + make_code_tree(head(leaves), + head(tail(leaves))), + tail(tail(leaves)))); +} + + + + + + generate_huffman_tree_example + +const sample_tree = + make_code_tree(make_leaf("A", 4), + make_code_tree( + make_leaf("B", 2), + make_code_tree( + make_leaf("D", 1), + make_leaf("C", 1)))); +const sample_frequencies = list(list("A", 4), + list("B", 2), + list("C", 1), + list("D", 1)); + +equal(sample_tree, + generate_huffman_tree(sample_frequencies)); + + + + + + + The following eight-symbol alphabet with associated relative + frequencies was designed to efficiently encode the lyrics of 1950s + rock songs, 1950s + rock songs. (Note that the symbols of an + alphabet need not be individual letters.) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ A + + 2 + + + + + + NA + + 16 +
+ BOOM + + 1 + + + + SHA + + 3 +
+ GET + + 2 + + + + YIP + + 9 +
+ JOB + + 2 + + + + WAH + + 1 +
+ Use + + generate-huffman-tree + generate_huffman_tree + + + (exercise) to generate a + corresponding Huffman tree, and use encode + (exercise) to encode the following + message: + + + + + + + + + + + + + + + + + + + +
Get a job
Sha na na na na na na na na
Get a job
Sha na na na na na na na na
Wah yip yip yip yip yip yip yip yip yip
Sha boom
+ How many bits are required for the encoding? What is the smallest number + of bits that would be needed to encode this song if we used a fixed-length + code for the eight-symbol alphabet? + + + boom_yip + successive_merge_solution + generate_huffman_tree + encode_symbol_solution + encode + 84 + +const lyrics_frequencies = + list(list("A", 2), + list("NA", 16), + list("BOOM", 1), + list("SHA", 3), + list("GET", 2), + list("YIP", 9), + list("JOB", 2), + list("WAH", 2)); +const lyrics_tree = generate_huffman_tree(lyrics_frequencies); +const lyrics = list( + 'GET', 'A', 'JOB', + 'SHA', 'NA', 'NA', 'NA', 'NA', 'NA', 'NA', 'NA', 'NA', + 'GET', 'A', 'JOB', 'SHA', 'NA', 'NA', 'NA', 'NA', 'NA', + 'NA', 'NA', 'NA', 'WAH', 'YIP', 'YIP', 'YIP', 'YIP', + 'YIP', 'YIP', 'YIP', 'YIP', 'YIP', 'SHA', 'BOOM' + ); + +length(encode(lyrics, lyrics_tree)); +// 84 + + + We have an alphabet of $n = 8$ symbols, and a + message of $m = 36$ symbols. Then the minimum + number of bits to encode a specific symbol using a fixed-length code is + $\lceil \log_2{n} \rceil = 3$. Thus the minimum + number of bits to encode all the lyrics is + $m \lceil\log_2{n}\rceil = 36 \times 3 = 108$. + + +
+ + + Suppose we have a Huffman tree for an alphabet of + $n$ symbols, and that the relative frequencies + of the symbols are 1, 2, 4, , + $2^{n-1}$. Sketch the tree for + $n$=5; for $n$=10. + In such a tree (for general $n$) how may bits + are required to encode the most frequent symbol? the least frequent symbol? + + + + The tree will be unbalanced, similar to the tree given in + figure. Encoding the most + frequent symbol requires one bit, whereas + $n - 1$ bits are required to encode the + least frequent symbol. + + + + + + Consider the encoding + + procedure + function + that you designed in exercise. What + is the + Huffman codeorder of growth of encoding + order of growth in the number of steps needed to encode a symbol? + Be sure to include the number of steps needed to search the symbol list at + each node encountered. To answer this question in general is difficult. + Consider the special case where the relative frequencies of the + $n$ symbols are as described in + exercise, and give the order of + growth (as a function of $n$) of the number of + steps needed to encode the most frequent and least frequent symbols in the + alphabet. + + Consider the special case in + exercise. At each step down the path + of length $n$, we need to do a linear search in + a list of length $n, n-1, \ldots, 1$. In the + worst case, there are + $O(n \times n / 2) = O(n^2)$ number of steps. + $O(n^2)$. + + + + Huffman code +
diff --git a/xml/cn/chapter2/section4/section4.xml b/xml/cn/chapter2/section4/section4.xml new file mode 100644 index 000000000..bb3266302 --- /dev/null +++ b/xml/cn/chapter2/section4/section4.xml @@ -0,0 +1,177 @@ +
+ 抽象数据的多重表示 + + + + + + 数据抽象 + + + 我们引入了数据抽象,这是一种用于构建系统的方法,使程序的大部分可以独立于实现程序操作的数据对象的选择之外进行指定。例如,我们在 + section看到如何将设计使用有理数的程序的任务与用计算机语言实现有理数的任务分开 + 的基本机制来构建复合数据。关键思路是构建一个抽象屏障 + ——在这种情况下,有理数的选择器和构造器 + + (make-rat, + (make_rat, + + numer, + denom )将有理数的使用方式与其底层表示的列表结构隔离开来。类似的抽象屏障将执行有理算术的 + + 过程 + 函数 + + 的细节隔离开来 + + (add-rat, + (add_rat, + + + sub-rat, + sub_rat, + + + mul-rat, + mul_rat, + + 和 + + div-rat) + div_rat) + + 与使用有理数的高层 + + 过程 + 函数 + + 隔离开。生成的程序具有在figure中显示的结构。 + + + 这些数据抽象屏障是控制复杂性的强大工具。通过隔离数据对象的底层表示,我们可以将设计大型程序的任务划分为可以单独执行的较小任务。但这种数据抽象还不够强大,因为并非总是有意义地谈论数据对象的底层表示。 + + + 一方面,一个数据对象可能有不止一种有用的表示,我们可能希望设计能够处理多种表示形式的系统。举个简单的例子,复数可以用两种几乎等效的方式表示:直角坐标形式(实部和虚部)和极坐标形式(模和角)。有时直角坐标形式更合适,有时极坐标形式更合适。事实上,完全可以想象一个系统,其中复数可以以这两种方式表示,并且用于操作复数的 + + 过程 + 函数 + + 可以处理任一表示形式。 + + + 更重要的是,编程系统通常是由多人在长时间内设计的,并且受到随时间变化的需求的影响。在这样的环境中,不可能所有人事先就数据表示的选择达成一致。因此,除了将表示与使用隔离的数据抽象屏障外,我们还需要将不同设计选择彼此隔离并允许不同选择在单个程序中共存的抽象屏障。此外,由于大型程序通常是通过组合 + + 预先存在的 + 预先存在的 + + 模块而创建,这些模块是在隔离中设计的,因此我们需要允许程序员将模块 + 加性 + 加性地合并到更大系统中的约定,也就是说,不需要重新设计或重新实现这些模块。 + + + 在本节中,我们将学习如何处理程序的不同部分可能采用不同表示的数据。这需要构建通用 + 过程函数 + 通用 + 通用 + 过程函数过程函数 + ,这些过程或函数可以对可能以多种方式表示的数据进行操作。我们构建通用 + + 过程 + 函数 + + 的主要技术是使用带有类型标签类型标签的数据对象,即包含有关如何处理信息的数据对象。我们还将讨论 + 数据导向编程 + 数据导向编程,这是一种强大且便捷的实现策略,用于通过通用操作在构建系统时进行加性组合。 + + + 我们从简单的复数示例开始。我们将看到类型标签和数据导向风格如何使我们能够为复数设计独立的直角坐标和极坐标表示,同时保持抽象的概念【38:18†cn.txt】。 +复数算术 + 算术在复数上 + 复数 + 数据对象。 + 我们将通过定义算术【50:3†cn.txt】 + + procedures + 函数 + + 对于复数 + + (add-complex, + (add_complex, + + + + sub-complex, + sub_complex, + + + + mul-complex, + mul_complex, + + + 和 + + div-complex) + div_complex) + + + 根据通用选择器访问复数的部分,这与数字的表示方式无关。生成的复数系统,如图中所示【30:15†cn.txt】。 + + + figure, + + + figure, + + + 包含两种不同类型的 + + 在复数系统中的抽象屏障【80:0†cn.txt】 + + 抽象屏障。 + 水平 + 抽象屏障 + 起到与 + figure 中相同的作用 + 。它们将高层操作与低层表示隔离开。此外,还有一个垂直屏障,使我们能够单独设计和安装替代表示。 + + +
+
+ + 复数系统中的数据抽象屏障 。 + + +
+ + +
+
+ + 复数系统中的数据抽象屏障 。 + + +
+
+
+
+ + 在section中,我们将展示如何使用类型标签和数据导向风格来开发一个通用算术包。这提供了 + + 过程 + 函数 + + (add,mul,等等)可以用来操作各种数字,并且当需要数字的新类型时可以轻松扩展。在section中,我们将展示如何在执行符号代数的系统中使用通用算术【96:2†cn.txt】。 + + + + &subsection2.4.1; + + + &subsection2.4.2; + + + &subsection2.4.3; + +
diff --git a/xml/cn/chapter2/section4/subsection1.xml b/xml/cn/chapter2/section4/subsection1.xml new file mode 100644 index 000000000..4668b8046 --- /dev/null +++ b/xml/cn/chapter2/section4/subsection1.xml @@ -0,0 +1,362 @@ + + + + 复数的表示 + + + + 复数直角坐标 vs. 极坐标形式 + + + 我们将开发一个系统,用于对复数 + 执行算术运算,作为一个简单但不现实的程序示例,该程序使用通用运算。我们首先讨论复数作为有序对的两种可能表示形式:直角坐标(实部和虚部)和极坐标(模长和角度)。在实际计算系统中,由于直角坐标和极坐标之间转换的舍入误差,直角坐标形式大多数时候比极坐标形式更可取。这就是为什么复数示例是不现实的。然而,它为使用通用运算设计系统提供了清楚的说明,并为本章后面开发的更重要的系统提供了良好的介绍。 Section + 将展示如何通过使用类型标签和通用运算,使两种表示形式可以在一个系统中共存。 + + + 类似有理数,复数自然表示为有序对。复数集可视为一个具有两个正交轴的二维空间,一个是实数轴,另一个是虚数轴。(见 + figure。)从这一视角来看,复数$z=x+iy$(其中 + $i^{2} =-1$)可以看作平面中一个点,其实部坐标为$x$,虚部坐标为$y$。在这种表示中,复数的加法归结为坐标的加法: + + \[ + \begin{array}{lll} + \mbox{实部}(z_{1}+z_{2}) & = & + \mbox{实部}(z_{1})+\mbox{实部}(z_{2}) \\[1ex] + \mbox{虚部}(z_{1} +z_{2}) & = & + \mbox{虚部}(z_1)+\mbox{虚部}(z_2) + \end{array} + \] + + + + 当乘复数时,更自然的是考虑将复数表示为极坐标形式,作为幅度和角度($r$ 和 $A$,参见 + figure)。两个复数的乘积是一个通过一个复数的长度来拉伸另一个复数并通过另一个的角度旋转得到的向量: + + \[ + \begin{array}{lll} + \mbox{模长}(z_{1}\cdot z_{2}) & = & + \mbox{模长}(z_{1})\cdot\mbox{模长}(z_{2})\\[1ex] + \mbox{角度}(z_{1}\cdot z_{2}) & = & + \mbox{角度}(z_{1})+\mbox{角度}(z_{2}) + \end{array} + \] + + + + 因此,复数有两种不同的表示形式,适用于不同的运算。然而,从使用复数写程序的人的角度来看,数据抽象原则建议无论计算机使用哪种表示形式,所有用于操作复数的运算都应可以使用。例如,能够找到用直角坐标指定的复数的模长通常是有用的。同样,能够确定用极坐标指定的复数的实部通常是有用的。 +
+
+ 复数作为平面中的点。 + +
+
+ + + 为了设计这样的系统,我们可以遵循与在 有理数包 + 中设计有理数包相同的数据抽象策略。假设复数操作是通过四个选择器实现的: + + 实部, + 实部, + + + 虚部, + 虚部, + + + 模长, + 模长, + + 和 +angle . Also assume that we have two + + 过程 + 函数 + + for constructing complex numbers: + + make-from-real-imag + + make_from_real_imag + + + 返回具有指定实部和虚部的复数,以及 + + + make-from-mag-ang + + + make_from_mag_ang + + + 返回具有指定幅度和角度的复数。这些 + + 过程 + 函数 + + 具有这样一个属性,对于任何复数 + z , both + + +(make-from-real-imag (real-part z) (imag-part z)) + + +make_from_real_imag(real_part(z), imag_part(z)); + + + 和 + + +(make-from-mag-ang (magnitude z) (angle z)) + + +make_from_mag_ang(magnitude(z), angle(z)); + + + 生成等于的复数 +z. + + + + 使用这些构造函数和选择器,我们可以像在节 中为有理数所做的那样,使用构造函数和选择器指定的抽象数据来实现复数的算术运算。如上面的公式所示,我们可以在实部和虚部的基础上对复数进行加减法运算,而在模长和角度的基础上进行复数的乘除法运算: + + add_complex + sub_complex + mul_complex + div_complex + complex_number_calculation + +(define (add-complex z1 z2) + (make-from-real-imag (+ (real-part z1) (real-part z2)) + (+ (imag-part z1) (imag-part z2)))) + +(define (sub-complex z1 z2) + (make-from-real-imag (- (real-part z1) (real-part z2)) + (- (imag-part z1) (imag-part z2)))) + +(define (mul-complex z1 z2) + (make-from-mag-ang (* (magnitude z1) (magnitude z2)) + (+ (angle z1) (angle z2)))) + +(define (div-complex z1 z2) + (make-from-mag-ang (/ (magnitude z1) (magnitude z2)) + (- (angle z1) (angle z2)))) + + +function add_complex(z1, z2) { + return make_from_real_imag(real_part(z1) + real_part(z2), + imag_part(z1) + imag_part(z2)); +} +function sub_complex(z1, z2) { + return make_from_real_imag(real_part(z1) - real_part(z2), + imag_part(z1) - imag_part(z2)); +} +function mul_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) * magnitude(z2), + angle(z1) + angle(z2)); +} +function div_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) / magnitude(z2), + angle(z1) - angle(z2)); +} + + + + + 为了完成复数包,我们必须选择一种表示方式,并且必须用原语数和原语列表结构实现构造函数和选择器。有两种明显的方法可以做到这一点:我们可以将复数以直角坐标形式表示为一个对(实部,虚部),或者以极坐标形式表示为一个对(模长,角度)。我们该选择哪一种? + + + + 为了使不同的选择具体化,假设有两个程序员,Ben Bitdiddle 和 Alyssa P. Hacker,他们正在独立设计复数系统的表示。 + Ben 选择用复数直角坐标表示 + 将复数表示为直角坐标形式。选择这种方式后,选择复数的实部和虚部变得很简单,构造具有给定实部和虚部的复数也是如此。 + 为了找到模长和角度,或者构造一个具有给定模长和角度的复数,他使用三角关系 + + \[ + \begin{array}{lllllll} + x & = & r\ \cos A & \quad \quad \quad & r & = & \sqrt{x^2 +y^2} \\ + y & = & r\ \sin A & & A &= & \arctan (y,x) + \end{array} + \] + + 将实部和虚部关联起来的 ( +$x$, + $y$ ) 与模长和角度 + $(r, A)$. 反正切函数 + 在这里 + + + 通过 Schemes + 反正切 + math_atan2 (原语函数) + math_atan2Math.atan2 + atan 过程计算, + + + 通过 JavaScripts + 反正切 + math_atan2 (原语函数) + math_atan2Math.atan2 + math_atan2 函数计算, + + + 被定义为接受两个参数 + $y$和 $x$ + 并返回其正切为 $y/x$ 的角度。 + 参数的符号决定了角度的象限。 + 因此,Ben的表示方式是通过以下选择器 + 和构造函数给出的: + + real_partrectangular representation + imag_partrectangular representation + magnituderectangular representation + anglerectangular representation + make_from_real_imagrectangular representation + make_from_mag_angrectangular representation + make_complex_number1 + complex_number_calculation + square_definition + make_complex_number_example + -3 + +(define (real-part z) (car z)) + +(define (imag-part z) (cdr z)) + +(define (magnitude z) + (sqrt (+ (square (real-part z)) (square (imag-part z))))) + +(define (angle z) + (atan (imag-part z) (real-part z))) + +(define (make-from-real-imag x y) (cons x y)) + +(define (make-from-mag-ang r a) + (cons (* r (cos a)) (* r (sin a)))) + + +function real_part(z) { return head(z); } + +function imag_part(z) { return tail(z); } + +function magnitude(z) { + return math_sqrt(square(real_part(z)) + square(imag_part(z))); +} +function angle(z) { + return math_atan2(imag_part(z), real_part(z)); +} +function make_from_real_imag(x, y) { return pair(x, y); } + +function make_from_mag_ang(r, a) { + return pair(r * math_cos(a), r * math_sin(a)); +} + + + + + make_complex_number_example + +const my_co_num_1 = make_from_real_imag(2.5, -0.5); +const my_co_num_2 = make_from_real_imag(2.5, -0.5); + +const result = add_complex(my_co_num_1, + mul_complex(my_co_num_2, + my_co_num_2)); + +imag_part(result); + + + + + + + 相比之下,Alyssa 选择将复数表示为 + 复数极坐标表示 极坐标形式。 + + 对她来说,选择模长和角度是直接的,但她必须使用 + 三角关系 三角关系来获取实部和虚部。 + Alyssa的表示是: + + real_partpolar representation + imag_partpolar representation + magnitudepolar representation + anglepolar representation + make_from_real_imagpolar representation + make_from_mag_angpolar representation + make_complex_number2 + complex_number_calculation + square_definition + make_complex_number_example + -3 + +(define (real-part z) + (* (magnitude z) (cos (angle z)))) + +(define (imag-part z) + (* (magnitude z) (sin (angle z)))) + +(define (magnitude z) (car z)) + +(define (angle z) (cdr z)) + +(define (make-from-real-imag x y) + (cons (sqrt (+ (square x) (square y))) + (atan y x))) + +(define (make-from-mag-ang r a) (cons r a)) + + +function real_part(z) { + return magnitude(z) * math_cos(angle(z)); +} +function imag_part(z) { + return magnitude(z) * math_sin(angle(z)); +} +function magnitude(z) { return head(z); } + +function angle(z) { return tail(z); } + +function make_from_real_imag(x, y) { + return pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x)); +} +function make_from_mag_ang(r, a) { return pair(r, a); } + + + + + 数据抽象的规范确保相同的 + + + add-complex, + + + add_@complex, + + + + + sub-complex, + + + sub_complex, + + + + + mul-complex, + + + mul_complex, + + + 和 + + + div-complex + + + div_complex + + + 实现能与 Ben 以及 Alyssa 的表示方式一起工作。 + +
diff --git a/xml/cn/chapter2/section4/subsection2.xml b/xml/cn/chapter2/section4/subsection2.xml new file mode 100644 index 000000000..af2d356bf --- /dev/null +++ b/xml/cn/chapter2/section4/subsection2.xml @@ -0,0 +1,536 @@ + + + 带标签数据 + + + + 复数表示为带标签数据 + 带标签数据 + 数据带标签 + + + 数据抽象的一种方式是将其视为最少承诺原则的应用。 + 最少承诺原则 + 最少承诺原则, 原则 + 最少承诺原则。 在第节中实现复数系统时,我们可以使用 Ben 的直角坐标表示或 Alyssa 的极坐标表示。选择器和构造器形成的抽象屏障使我们能够将具体数据对象表示选择推迟到最后一刻,从而在系统设计中保持最大的灵活性。 + + + 最少承诺原则可以被推向更极端的程度。 + 如果我们愿意,我们可以在设计选择器和构造器之后仍然保持表示的模糊性,并选择使用 Ben 的表示和 Alyssa 的表示。不过,如果将两种表示都包括在一个系统中,我们将需要某种方式来区分极坐标形式的数据和直角坐标形式的数据。否则,例如,如果我们被要求找到对magnitude为$(3,4)$的对的模长,我们将不知道是回答 5(按直角坐标形式解释)还是 3(按极坐标形式解释)。一种简单的方法是包括一个类型标签,即类型标签 + + 符号 rectangular + 字符串 "rectangular" + + + 或者 + + polar作为 + + "polar"作为 + + + 每个复数的一部分。然后,当我们需要处理一个复数时,我们可以使用标签来决定应用哪个选择器。 + + + 为了操作带标签数据,我们假设我们有 + + 过程 + 函数 + + + type-tag + type_tag + + 和 contents 从数据对象中提取标签和实际内容(在复数的情况下为极坐标或直角坐标)。我们还将假设有一个 + + 过程 + 函数 + + + attach-tag + attach_tag + + ,它接受一个标签和内容并生成一个带标签的数据对象。实现这一点的一种简单方法是使用普通列表结构: + + attach_tag + type_tag + contents + attach_tag + attach_tag_example + 'frequency_list' + +(define (attach-tag type-tag contents) + (cons type-tag contents)) + +(define (type-tag datum) + (if (pair? datum) + (car datum) + (error "Bad tagged datum -- TYPE-TAG" datum))) + +(define (contents datum) + (if (pair? datum) + (cdr datum) + (error "Bad tagged datum -- CONTENTS" datum))) + + +function attach_tag(type_tag, contents) { + return pair(type_tag, contents); +} +function type_tag(datum) { + return is_pair(datum) + ? head(datum) + : error(datum, "bad tagged datum -- type_tag"); +} +function contents(datum) { + return is_pair(datum) + ? tail(datum) + : error(datum, "bad tagged datum -- contents"); +} + + + + attach_tag_example + +const f_1 = list("A", 4); +const my_frequency_1 = + attach_tag("frequency_list", f_1); + +type_tag(my_frequency_1); + + + + + + + 使用这些过程, + + + 使用 + type_tag, + + + 我们可以定义谓词 + + rectangular? + is_rectangular + + + 和 + + + polar?, + is_polar + + 分别识别直角坐标和极坐标数字: + + is_rectangular + is_polar + rectangular_or_polar + rectangular_or_polar_example + attach_tag + +(define (rectangular? z) + (eq? (type-tag z) 'rectangular)) + +(define (polar? z) + (eq? (type-tag z) 'polar)) + + +function is_rectangular(z) { + return type_tag(z) === "rectangular"; +} +function is_polar(z) { + return type_tag(z) === "polar"; +} + + + + + 有了类型标签,Ben 和 Alyssa 现在可以修改他们的代码,使他们的两种不同表示能够在同一系统中共存。每当 Ben 构造一个复数时,他将其标记为直角坐标。每当 Alyssa 构造一个复数时,她将其标记为极坐标。此外,Ben 和 Alyssa 必须确保他们的 + + 过程 + 函数 + </SPLITINLINE> + 的名称不冲突。一种方法是让 Ben 附加后缀【40:10†cn.txt】。 rectangular 给他每个表示过程的名称添加后缀,并让 Alyssa 添加【44:4†cn.txt】。 polar 到她们的名字。以下是来自第节的 Ben更新的直角坐标表示【48:4†cn.txt】。 + real_part_rectangular + imag_part_rectangular + magnitude_rectangular + angle_rectangular + make_from_real_imag_rectangular + make_from_mag_ang_rectangular + make_complex_number_rectangular + attach_tag + square_definition + make_complex_number_rectangular_example + 1.932653061713073 + +(define (real-part-rectangular z) (car z)) + +(define (imag-part-rectangular z) (cdr z)) + +(define (magnitude-rectangular z) + (sqrt (+ (square (real-part-rectangular z)) + (square (imag-part-rectangular z))))) + +(define (angle-rectangular z) + (atan (imag-part-rectangular z) + (real-part-rectangular z))) + +(define (make-from-real-imag-rectangular x y) + (attach-tag 'rectangular (cons x y))) + +(define (make-from-mag-ang-rectangular r a) + (attach-tag 'rectangular + (cons (* r (cos a)) (* r (sin a))))) + + +function real_part_rectangular(z) { return head(z); } + +function imag_part_rectangular(z) { return tail(z); } + +function magnitude_rectangular(z) { + return math_sqrt(square(real_part_rectangular(z)) + + square(imag_part_rectangular(z))); +} +function angle_rectangular(z) { + return math_atan2(imag_part_rectangular(z), + real_part_rectangular(z)); +} +function make_from_real_imag_rectangular(x, y) { + return attach_tag("rectangular", pair(x, y)); +} +function make_from_mag_ang_rectangular(r, a) { + return attach_tag("rectangular", + pair(r * math_cos(a), r * math_sin(a))); +} + + + + make_complex_number_rectangular_example + +const bens_co_num = make_from_mag_ang_rectangular( + 3.0, 0.7); + +imag_part_rectangular(contents(bens_co_num)); + + + 以下是 Alyssa 更新的极坐标表示: + + real_part_polar + imag_part_polar + magnitude_polar + angle_polar + make_from_real_imag_polar + make_from_mag_ang_polar + make_complex_number_polar + attach_tag + square_definition + make_complex_number_polar_example + 1.932653061713073 + +(define (real-part-polar z) + (* (magnitude-polar z) (cos (angle-polar z)))) + +(define (imag-part-polar z) + (* (magnitude-polar z) (sin (angle-polar z)))) + +(define (magnitude-polar z) (car z)) + +(define (angle-polar z) (cdr z)) + +(define (make-from-real-imag-polar x y) + (attach-tag 'polar + (cons (sqrt (+ (square x) (square y))) + (atan y x)))) + +(define (make-from-mag-ang-polar r a) + (attach-tag 'polar (cons r a))) + + +function real_part_polar(z) { + return magnitude_polar(z) * math_cos(angle_polar(z)); +} +function imag_part_polar(z) { + return magnitude_polar(z) * math_sin(angle_polar(z)); +} +function magnitude_polar(z) { return head(z); } + +function angle_polar(z) { return tail(z); } + +function make_from_real_imag_polar(x, y) { + return attach_tag("polar", + pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x))); +} + +function make_from_mag_ang_polar(r, a) { + return attach_tag("polar", pair(r, a)); +} + + + + make_complex_number_polar_example + +const alyssas_co_num = make_from_mag_ang_polar( + 3.0, 0.7); + +imag_part_polar(contents(alyssas_co_num)); + + + + + 选择器通用选择器 + 通用 过程函数通用选择器 + 每个通用选择器都实现为一个 + + 过程 + 函数 + + ,该过程检查其参数的标签并调用适当的 + + 过程 + 函数 + + ,用于处理该类型的数据。例如,为了获得复数的实部, + + real-part + real_part + + 检查标签以确定是否使用 Ben 的 + + real-part-rectangular + real_part_rectangular + + + 或 Alyssa 的 + + real-part-polar. + real_part_polar. + + + 无论哪种情况,我们都使用 【16:8†cn.txt】。 contents 提取裸露的、无标签的数据并根据需要将其发送到直角坐标或极坐标 + + 过程 + 函数 + : + + real_partwith tagged data + imag_partwith tagged data + magnitudewith tagged data + anglewith tagged data + make_complex_number + rectangular_or_polar + make_complex_number_rectangular + make_complex_number_polar + make_complex_number_example_2 + 1.932653061713073 + +(define (real-part z) + (cond ((rectangular? z) + (real-part-rectangular (contents z))) + ((polar? z) + (real-part-polar (contents z))) + (else (error "Unknown type -- REAL-PART" z)))) + +(define (imag-part z) + (cond ((rectangular? z) + (imag-part-rectangular (contents z))) + ((polar? z) + (imag-part-polar (contents z))) + (else (error "Unknown type -- IMAG-PART" z)))) + +(define (magnitude z) + (cond ((rectangular? z) + (magnitude-rectangular (contents z))) + ((polar? z) + (magnitude-polar (contents z))) + (else (error "Unknown type -- MAGNITUDE" z)))) + +(define (angle z) + (cond ((rectangular? z) + (angle-rectangular (contents z))) + ((polar? z) + (angle-polar (contents z))) + (else (error "Unknown type -- ANGLE" z)))) + + +function real_part(z) { + return is_rectangular(z) + ? real_part_rectangular(contents(z)) + : is_polar(z) + ? real_part_polar(contents(z)) + : error(z, "unknown type -- real_part"); +} +function imag_part(z) { + return is_rectangular(z) + ? imag_part_rectangular(contents(z)) + : is_polar(z) + ? imag_part_polar(contents(z)) + : error(z, "unknown type -- imag_part"); +} +function magnitude(z) { + return is_rectangular(z) + ? magnitude_rectangular(contents(z)) + : is_polar(z) + ? magnitude_polar(contents(z)) + : error(z, "unknown type -- magnitude"); +} +function angle(z) { + return is_rectangular(z) + ? angle_rectangular(contents(z)) + : is_polar(z) + ? angle_polar(contents(z)) + : error(z, "unknown type -- angle"); +} + + + + make_complex_number_example_2 + +const alyssas_co_num = make_from_mag_ang_polar( + 3.0, 0.7); + +imag_part(alyssas_co_num); + + + + + 为了实现复数算术运算,我们可以使用相同的 + + 过程 + 函数 + + + add-complex, + add_complex, + + + + sub-complex, + sub_complex, + + + + mul-complex, + mul_complex, + + + 和 + + div-complex + div_complex + + 来自第节, + 因为它们调用的选择器是通用的,因此可以与任何表示一起使用。例如, + + 过程 + 函数 + + + add-complex + add_complex + + 仍然是 + + +(define (add-complex z1 z2) + (make-from-real-imag (+ (real-part z1) (real-part z2)) + (+ (imag-part z1) (imag-part z2)))) + + +function add_complex(z1, z2) { + return make_from_real_imag(real_part(z1) + real_part(z2), + imag_part(z1) + imag_part(z2)); +} + + + + + 最后,我们必须选择是使用 Ben 的表示还是 Alyssa 的表示来构造复数。一个合理的选择是,在我们具有实部和虚部时构造直角坐标数,而在我们具有模长和角度时构造极坐标数: + + make_from_real_imag + make_from_mag_ang + make_complex_number_generic + make_complex_number + make_complex_number_rectangular + make_complex_number_polar + make_complex_number_generic_example + 1.932653061713073 + +(define (make-from-real-imag x y) + (make-from-real-imag-rectangular x y)) + +(define (make-from-mag-ang r a) + (make-from-mag-ang-polar r a)) + + +function make_from_real_imag(x, y) { + return make_from_real_imag_rectangular(x, y); +} +function make_from_mag_ang(r, a) { + return make_from_mag_ang_polar(r, a); +} + + + + make_complex_number_generic_example + +const alyssas_co_num = make_from_mag_ang( + 3.0, 0.7); + +imag_part(alyssas_co_num); + + + + + + +
+
+ 通用复数算术系统的结构。 + +
+ + +
+
+ + 结构 + 复数算术系统结构 + 的通用复数算术系统。 + + +
+
+
+ $\!$结果复数系统的结构如 + + + 图所示。 + + + 图所示。 + + + 该系统被分解为三个相对独立的部分:复数算术运算,Alyssa 的极坐标实现,以及 Ben 的直角坐标实现。 極座標和直角坐標實現可以由 Ben 和 Alyssa 分別完成,這兩者都可以用作第三個程序員實現復數算術的 + + 过程 + 函数 + + ,以抽象构造器/选择器接口的方式。 +
+ + 因为每个数据对象都有其类型标签,所以选择器以通用方式操作 + 选择器通用选择器 + 通用 过程函数通用选择器 + 。即,每个选择器的定义行为取决于应用的数据类型。注意将不同表示进行接口的通用机制:在给定的表示实现中(比如 Alyssa 的极坐标包),复数是一个无类型的对(幅度、角度)。当通用选择器操作一个极坐标类型的数字时,它会去掉标签并将内容传给 Alyssa 的代码。相反,当 Alyssa 构造一个用于通用的数字时,她将其标记为一种类型,以便能被高层的 + + 过程识别。 + 函数。 + + 这种在数据对象从一个层次到另一个层次传递时去除和附加标签的纪律可以是一个重要的组织策略,如我们将在第节中看到的那样。 + 复数表示为带标签数据 + 带标签数据 + 数据带标签 + +
diff --git a/xml/cn/chapter2/section4/subsection3.xml b/xml/cn/chapter2/section4/subsection3.xml new file mode 100644 index 000000000..256992220 --- /dev/null +++ b/xml/cn/chapter2/section4/subsection3.xml @@ -0,0 +1,1714 @@ + + + 数据导向编程和可加性 + + + + 数据导向编程 + 可加性 + + + 检查数据类型并调用适当 + 过程 + 函数 + 的一般策略被称为模块性通过类型分派分派按类型类型基于类型分派按类型分派。这是一种在系统设计中实现模块性的强大策略。然而,按照节实现分派有两个显著的弱点。一个弱点是通用接口 + 过程 + 函数 + + (real-part, + (real_part, + + imag-part, + imag_part, + magnitude和angle)必须知道所有不同的表示。例如,假设我们想把一种新的复数表示集成到我们的复数系统中。我们需要用一种类型标识这种新表示,然后在每个通用接口 + 过程 + 函数 + 中添加一个子句,以检查新类型并应用适当的选择器以适应这种表示。 + + + 该技术的另一个弱点是,尽管各个表示可以单独设计,但我们必须确保整个系统中没有两个 + 过程 + 函数 + 具有相同的名称。这就是Ben和Alyssa为什么必须更改他们在节的原始 + 过程 + 函数 + 名称的原因。 + + + 这两种弱点的根本问题在于,实施通用接口的技术不是可加性的。实现通用选择器的人员 + + 过程 + 函数 + + 必须在每次安装新表示时修改这些 + + 过程 + 函数 + + ,而接口个别表示的人员必须修改他们的代码以避免名称冲突。在每一种情况下,必须对代码进行的更改是直接的,但仍然要进行这些更改,这成为了不便和错误的来源。对于复数系统而言,这不是什么大问题,但假设复数的不同表示不止两种而是成百上千种。而且假设有许多通用选择器需要在抽象数据接口中维护。事实上,假设没有一个程序员知道所有的接口 + + 过程 + 函数 + + 或所有的表示。在诸如大型数据库管理系统的程序中,这个问题是现实的,必须加以解决。 + + + 我们需要的是一种进一步模块化系统设计的方法。这由被称为数据导向编程的编程技术提供。要理解数据导向编程是如何工作的,应首先注意到,每当我们处理一组通用于不同类型的泛型操作时,我们实际上是在处理一个二维表格,其中一个轴是可能的操作,另一个轴是可能的类型。表中的条目是为每种类型的参数实现每个操作的 + + 过程 + 函数 + + 。在前一节开发的复数系统中,操作名称、数据类型和实际 + + 过程 + 函数 + + 之间的对应关系分散在通用接口 + + 过程 + 函数 + + 的各种条件语句中。但相同的信息本可以在一个表中组织起来,如 + + + 图. + + + 图. + + + 所示。 + + + + 数据导向编程是一种直接与这样的用于数据导向编程表格协同工作的编程技术。之前,我们通过一组 + 过程 + 函数 + 实现了将复数算法代码与两个表示包接口的机制,这些函数各自执行显式的类型分派。这里我们将接口实现为一个单一的 + 过程 + 函数 + ,它在表中查找操作名称和参数类型的组合以找到要应用的正确 + 过程 + 函数 + ,然后应用于该参数的内容。如果这样做,那么为系统添加新的表示包时,我们无需更改任何现有的 + 过程; + 函数; + 我们只需在表中添加新的条目。 + + + + +
+ 复数系统的操作表。 + +
+ + +
+ 复数系统的操作表。 + +
+
+
+ To implement this plan, assume that we have two + + 过程, + 函数, + + putget ,用于操作操作与类型表操作与类型表:
    +
  • + 放入 + + + (放入 + $\langle \textit{操作} \rangle\ \langle \textit{类型} + \rangle \ \langle \textit{项目} \rangle$ + + ) + + + put(<!-- + -->op<!-- + -->, <!-- + -->type<!-- + -->, <!-- + -->item<!-- + -->) + + +

    + 在表中安装 + + + $\langle \textit{项目} \rangle$ + + + item + + + ,由 + + + $\langle \textit{操作} \rangle$ 和 + $\langle \textit{类型} \rangle$索引。 + + + op 和 + type索引。 + + +
  • +
  • + 获取 + + + (获取 + $\langle \textit{操作} \rangle\ \langle + \textit{类型}$ + ) + + + get(<!-- + -->op<!-- + -->, <!-- + -->type<!-- + -->) + + +

    + 在表中查找 + + + $\langle \textit{操作} \rangle$、 + $\langle \textit{类型} \rangle$ + + + op、 + type + + + 项并返回找到的项目。如果未找到任何项目, + 获取返回 + + 假。 + + 一个使用名称undefined(预声明的名称) + undefined引用并由基本谓词 + is_undefined(基本函数) + is_undefined + is_undefined识别的独特基本值。 + 在任何JavaScript实现中,名称undefined + 是预声明的,除了用于引用该基本值外,不应用于其他目的。 + + + +
  • +
+ 现在,我们可以假设 +put 和 + get 包含在我们的语言中。在chapter(section)中,我们将看到如何实现这些以及其他用于操纵表的操作。 +
+ + 以下是数据导向编程在复数系统中的应用方式。开发直角坐标表示的Ben按照最初的方法实现他的代码。他定义了一组 + 过程, + 函数 + 或一个直角坐标表示直角坐标,并通过在表中添加条目来接口到系统的其他部分,这些条目告诉系统如何在直角坐标数上操作。这是通过调用以下 + 过程: + 函数: + 实现的。 + + operation_table_from_chapter_3 + +// operation_table, put and get +// from chapter 3 (section 3.3.3) + + + + + install_rectangular_package_usage + +install_rectangular_package(); + + + + install_rectangular_package + install_rectangular_package + operation_table_from_chapter_3 + operation_table + attach_tag + square_definition + 'done' + install_rectangular_package_usage + +(define (install-rectangular-package) + ;; internal procedures + (define (real-part z) (car z)) + (define (imag-part z) (cdr z)) + (define (make-from-real-imag x y) (cons x y)) + (define (magnitude z) + (sqrt (+ (square (real-part z)) + (square (imag-part z))))) + (define (angle z) + (atan (imag-part z) (real-part z))) + (define (make-from-mag-ang r a) + (cons (* r (cos a)) (* r (sin a)))) + + ;; interface to the rest of the system + (define (tag x) (attach-tag 'rectangular x)) + (put 'real-part '(rectangular) real-part) + (put 'imag-part '(rectangular) imag-part) + (put 'magnitude '(rectangular) magnitude) + (put 'angle '(rectangular) angle) + (put 'make-from-real-imag 'rectangular + (lambda (x y) (tag (make-from-real-imag x y)))) + (put 'make-from-mag-ang 'rectangular + (lambda (r a) (tag (make-from-mag-ang r a)))) + 'done) + + +function install_rectangular_package() { + // internal functions + function real_part(z) { return head(z); } + function imag_part(z) { return tail(z); } + function make_from_real_imag(x, y) { return pair(x, y); } + function magnitude(z) { + return math_sqrt(square(real_part(z)) + square(imag_part(z))); + } + function angle(z) { + return math_atan2(imag_part(z), real_part(z)); + } + function make_from_mag_ang(r, a) { + return pair(r * math_cos(a), r * math_sin(a)); + } + + // interface to the rest of the system + function tag(x) { return attach_tag("rectangular", x); } + put("real_part", list("rectangular"), real_part); + put("imag_part", list("rectangular"), imag_part); + put("magnitude", list("rectangular"), magnitude); + put("angle", list("rectangular"), angle); + put("make_from_real_imag", "rectangular", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "rectangular", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} + + + + + 注意,这里的内部 + + 过程 + 函数 + + 是Ben在节中独立工作时编写的相同 + + 过程 + 函数 + 。为了与系统其他部分接口,不需要进行任何更改。此外,由于这些 + + 过程定义 + 函数声明 + + 是安装时的内部 + + 过程, + 函数, + + Ben不需要担心与直角坐标包外的其他 + + 过程 + 函数 + + 的名称冲突。为了与系统其他部分接口,Ben将他的 + + 实部 + real_part + + + 过程 + 函数 + + 安装在操作名称 + + 实部 + real_part + + 和类型 + + + (直角坐标), + list("rectangular"), + + + 下,类似地用于其他选择器。 + 我们使用的是 + + (直角坐标) + list("rectangular") + + + 列表,而不是 + + 符号直角坐标 + 字符串"rectangular" + + + ,以便可能存在具有多个不同类型参数的操作。 接口还定义了外部系统使用的构造器。构造器安装时的类型不必是列表,因为构造器总是用于创建特定类型的对象。 这些与Ben内部定义的构造器相同,只是在它们上面附加了标签。 + + + + Alyssas + 极坐标表示 + 极坐标 + 极坐标包是类似的: + + install_polar_package + install_polar_package + operation_table_from_chapter_3 + operation_table + attach_tag + square_definition + install_polar_package_usage + 'done' + +(define (install-polar-package) + ;; internal procedures + (define (magnitude z) (car z)) + (define (angle z) (cdr z)) + (define (make-from-mag-ang r a) (cons r a)) + (define (real-part z) + (* (magnitude z) (cos (angle z)))) + (define (imag-part z) + (* (magnitude z) (sin (angle z)))) + (define (make-from-real-imag x y) + (cons (sqrt (+ (square x) (square y))) + (atan y x))) + + ;; interface to the rest of the system + (define (tag x) (attach-tag 'polar x)) + (put 'real-part '(polar) real-part) + (put 'imag-part '(polar) imag-part) + (put 'magnitude '(polar) magnitude) + (put 'angle '(polar) angle) + (put 'make-from-real-imag 'polar + (lambda (x y) (tag (make-from-real-imag x y)))) + (put 'make-from-mag-ang 'polar + (lambda (r a) (tag (make-from-mag-ang r a)))) + 'done) + + +function install_polar_package() { + // internal functions + function magnitude(z) { return head(z); } + function angle(z) { return tail(z); } + function make_from_mag_ang(r, a) { return pair(r, a); } + function real_part(z) { + return magnitude(z) * math_cos(angle(z)); + } + function imag_part(z) { + return magnitude(z) * math_sin(angle(z)); + } + function make_from_real_imag(x, y) { + return pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x)); + } + + // interface to the rest of the system + function tag(x) { return attach_tag("polar", x); } + put("real_part", list("polar"), real_part); + put("imag_part", list("polar"), imag_part); + put("magnitude", list("polar"), magnitude); + put("angle", list("polar"), angle); + put("make_from_real_imag", "polar", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "polar", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} + + + + install_polar_package_usage + install_polar_package + +install_polar_package(); + + + + + 即使Ben和Alyssa仍然使用各自的原始 + + 过程 + 函数 + + 并定义为相同的名称(例如, + + 实部), + real_part), + + 这些声明现在属于不同的内部 + + 过程 + 函数 + + (参见节),因此没有名称冲突。 + + + 复数算术选择器通过一个通用操作 + 过程 + 函数 + 访问表,该通用操作被称为 + apply-generic, + apply_generic, + + ,它将通用操作应用于一些参数。 + apply-generic + + 函数 + apply_generic + + 会根据操作名称和参数类型在表中查找,并在存在时应用结果 + 过程 + 函数 + : + + + + apply-generic使用了在 + 点尾注释过程用于程序参数 + 练习中描述的点尾注释,因为不同的通用操作可能具有不同数量的参数。在 + + apply-generic, + + apply_generic, + + + op的值是 + + apply-generic + + apply_generic + + + 的第一个参数,而args的值是剩余参数的列表。 +

+ +
+ + + apply-generic还使用了 + 原语过程apply,它接受两个参数,一个过程和一个列表。apply利用列表中的元素作为参数来应用该过程。 + + + 函数apply_generic使用了 + 段apply_in_underlying_javascript + apply_in_underlying_javascript中给出的函数, + 它接受两个参数,一个函数和一个列表,并用列表中的元素作为参数来应用该函数。参见部分 + + (脚注), + + + (脚注2), + + + + 例如, + + + (apply + (list 1 2 3 4)) + + + apply_in_underlying_javascript(sum_of_squares, list(1, 3)) + + + 返回10。
+ + apply_definition + +// In Source, most functions have a fixed number of arguments. +// (The function list is the only exception, to this so far.) +// The function apply_in_underlying_javascript allows us to +// apply any given function fun to all elements of the argument +// list args, as if they were separate arguments +function apply(fun, args) { + return apply_in_underlying_javascript(fun, args); +} + + + + apply_generic + apply_generic + apply_definition + +(define (apply-generic op . args) + (let ((type-tags (map type-tag args))) + (let ((proc (get op type-tags))) + (if proc + (apply proc (map contents args)) + (error + "No method for these types -- APPLY-GENERIC" + (list op type-tags)))))) + + +function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + return ! is_undefined(fun) + ? apply_in_underlying_javascript(fun, map(contents, args)) + : error(list(op, type_tags), + "no method for these types -- apply_generic"); +} + + + 使用 + + apply-generic, + apply_generic, + + + 我们可以定义我们的通用选择器如下: + + real_partdata-directed + imag_partdata-directed + magnitudedata-directed + angledata-directed + generic_selectors + apply_generic + generic_selectors_example + 9 + +(define (real-part z) (apply-generic 'real-part z)) +(define (imag-part z) (apply-generic 'imag-part z)) +(define (magnitude z) (apply-generic 'magnitude z)) +(define (angle z) (apply-generic 'angle z)) + + +function real_part(z) { return apply_generic("real_part", list(z)); } + +function imag_part(z) { return apply_generic("imag_part", list(z)); } + +function magnitude(z) { return apply_generic("magnitude", list(z)); } + +function angle(z) { return apply_generic("angle", list(z)); } + + + + generic_selectors_example + install_rectangular_package + install_rectangular_package_usage + install_polar_package + complex_number_calculation + generic_constructors + +const my_complex_number = + make_from_real_imag(1.0, 4.5); + +const result = + add_complex(my_complex_number, + my_complex_number); + +imag_part(result); + + + 注意,如果向系统中添加新的表示,这些都不会发生任何变化。 +
+ + 我们还可以从表中提取构造器,以供包外的程序使用,这些程序通过实部和虚部以及模长和角度构造复数。如在节中,我们在有实部和虚部时构造直角坐标数,而在有模长和角度时构造极坐标数: + + make_complex_from_real_imag + make_complex_from_mag_ang + generic_constructors + generic_selectors + generic_selectors_example + 9 + +(define (make-from-real-imag x y) + ((get 'make-from-real-imag 'rectangular) x y)) + +(define (make-from-mag-ang r a) + ((get 'make-from-mag-ang 'polar) r a)) + + +function make_from_real_imag(x, y) { + return get("make_from_real_imag", "rectangular")(x, y); +} +function make_from_mag_ang(r, a) { + return get("make_from_mag_ang", "polar")(r, a); +} + + + + + + 节描述了一个执行符号微分微分符号符号微分的程序: + + deriv_2_4 + is_variable + is_same_variable + is_sum + make_sum + addend + augend + is_product + multiplier + multiplicand + xyx4 + [ '*', [ 'x', [ 'y', null ] ] ] + +(define (deriv exp var) + (cond ((number? exp) 0) + ((variable? exp) (if (same-variable? exp var) 1 0)) + ((sum? exp) + (make-sum (deriv (addend exp) var) + (deriv (augend exp) var))) + ((product? exp) + (make-sum + (make-product (multiplier exp) + (deriv (multiplicand exp) var)) + (make-product (deriv (multiplier exp) var) + (multiplicand exp)))) + ;; more rules can be added here + (else (error "unknown expression type -- DERIV" exp)))) + + +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? is_same_variable(exp, variable) ? 1 : 0 + : is_sum(exp) + ? make_sum(deriv(addend(exp), variable), + deriv(augend(exp), variable)) + : is_product(exp) + ? make_sum(make_product(multiplier(exp), + deriv(multiplicand(exp), variable)), + make_product(deriv(multiplier(exp), variable), + multiplicand(exp))) + // more rules can be added here + : error(exp, "unknown expression type -- deriv"); +} + + + + xyx4 + deriv_2_4 + +(deriv '(* (* x y) (+ x 4)) 'x) + + +(+ (* (* x y) (+ 1 0)) +(* (+ (* x 0) (* 1 y)) +(+ x 4))) + + +deriv(list("*", list("*", "x", "y"), list("+", "x", 4)), "x"); + + +head(tail(head(tail(deriv(list("*", list("*", "x", "y"), list("+", "x", 4)), "x"))))); + + +list("+", list("*", list("*", x, y), list("+", 1, 0)), + list("*", list("+", list("*", x, 0), list("*", 1, y)), + list("+", x, 4))) + + + 我们可以将此程序视为对要微分的表达式类型进行分派。在这种情况下,数据的类型标签是代数运算符符号,例如 + + (如+) + (如"+") + + ,执行的操作是 + deriv . 我们可以通过重写基本导数 + 过程 + 函数 + 为数据导向风格来转换这个程序 + + deriv (symbolic)data-directed + deriv_generic + is_variable + is_same_variable + deriv_x_example + +(define (deriv exp var) + (cond ((number? exp) 0) + ((variable? exp) (if (same-variable? exp var) 1 0)) + (else ((get 'deriv (operator exp)) (operands exp) + var)))) + +(define (operator exp) (car exp)) + +(define (operands exp) (cdr exp)) + + +function deriv(exp, variable) { + return is_number(exp) + ? 0 + : is_variable(exp) + ? is_same_variable(exp, variable) ? 1 : 0 + : get("deriv", operator(exp))(operands(exp), variable); +} +function operator(exp) { return head(exp); } + +function operands(exp) { return tail(exp); } + + + + deriv_x_example + +(deriv '(+ x 3) 'x) + + +(+ 1 0) + + +deriv("x", "x"); +// 1 + + +
    +
  1. + 解释上面所做的事情。为什么我们不能将谓词 + + number? + is_number + + 和 + + variable? + is_variable + + + 并入数据导向分派中? +
  2. +
  3. + 编写求和及乘积导数的 + + 过程 + 函数 + + ,以及将它们安装到上述程序使用的表中的辅助代码。 +
  4. +
  5. + 选择任何你喜欢的额外微分规则,例如指数规则(练习),并将它安装到这个数据导向系统中。 +
  6. +
  7. + 在这个简单的代数操纵器中,表达式的类型是将它结合在一起的代数运算符。然而,假设我们以相反的方式索引 + + 过程 + 函数 + + ,这样在deriv中的分派行看起来像 + + +((get (operator exp) 'deriv) (operands exp) var) + + +get(operator(exp), "deriv")(operands(exp), variable); + + + 对导数系统有哪些相应的更改是必需的? +
  8. +
+ + +
    +
  1. + 解释上面所做的事情。为什么我们不能将谓词 + + number? + is_number + + + 和 + + same-variable? + is_same_variable + + 并入数据导向分派中? +

    + 运算符符号非常方便地作为操作符表中的类型键。对于数字和变量,尽管我们可以为这些类型的表达式引入名称,如果我们改变表达式作为列表的表示方式,会没有如此明显的键。 +
  2. +
  3. + 编写求和及乘积导数的 + + 过程 + 函数 + + 以及将它们安装到上述程序使用的表中的辅助代码。 +

    + + deriv_generic_sum_product + deriv_generic + deriv_generic_data + operation_table_from_chapter_3 + operation_table + deriv_xyx3_generic_example + [ '*', [ 'x', [ 'y', null ] ] ] + +(define (deriv-sum exp var) + (make-sum (deriv (addend exp) var) + (deriv (augend exp) var))) + +(define (deriv-product exp var) + (make-sum (make-product (multiplier exp) + (deriv (multiplicand exp) var)) + (make-product (deriv (multiplier exp) var) + (multiplicand exp)))) + +(define (install-deriv) + (put 'deriv '+ deriv-sum) + (put 'deriv '* deriv-product) + 'done) + + +function deriv_sum(operands, variable) { + return make_sum(deriv(addend(operands), variable), + deriv(augend(operands), variable)); +} +function deriv_product(operands, variable) { + return make_sum(make_product(multiplier(operands), + deriv(multiplicand(operands), + variable)), + make_product(deriv(multiplier( + operands), + variable), + multiplicand(operands))); +} +function install_deriv() { + put("deriv", "+", deriv_sum); + put("deriv", "*", deriv_product); + return "done"; +} +install_deriv(); + + + + deriv_generic_data + +function make_sum(a1, a2) { + return list("+", a1, a2); +} + +function make_product(m1, m2) { + return list("*", m1, m2); +} + +function addend(operands) { + return head(operands); +} + +function augend(operands) { + return head(tail(operands)); +} + +function multiplier(operands) { + return head(operands); +} + +function multiplicand(operands) { + return head(tail(operands)); +} + + + + deriv_xyx3_generic_example + +(deriv '(* (* x y) (+ x 3)) 'x) + + +(+ (* (* x y) (+ 1 0)) +(* (+ (* x 0) (* 1 y)) +(+ x 3))) + + +deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"); +// [ "+", +// [["*", [["*", ["x", ["y", null]]], +// [["+", [1, [0, null]]], null]]], +// [["*", +// [["+", +// [["*", ["x", [0, null]]], +// [["*", [1, ["y", null]]], null]]], +// [["+", ["x", [3, null]]], null] ] ], +// null ]]] + + +head(tail(head(tail(deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"))))); + + +
  4. +
  5. + + 选择任何你喜欢的额外微分规则,例如指数的规则(练习),并将其安装到这个数据导向系统中。 + +

    + + deriv_expo_data + +function make_exponentiation(base, exp) { + return list("**", base, exp); +} +function base(operands) { + return head(operands); +} +function exponent(operands) { + return head(tail(operands)); +} + + + + example_deriv_expo_generic + +deriv(list("**", "x", 4), "x"); + + +head(tail(head(tail(head(tail(tail(deriv(list("**", "x", 4), "x")))))))); + + + + + + deriv_expo_put_it_all_together + deriv_generic + deriv_generic_data + deriv_expo_data + operation_table_from_chapter_3 + operation_table + example_deriv_expo_generic + 'x' + +(define (deriv-exponentiation expr var) + (let ((base (base expr)) + (exponent (exponent expr))) + (make-product exponent + (make-product (make-exponentiation base (make-sum exponent -1)) + (deriv base var))))) + +(define (install-exponentiation-extension) + (put 'deriv '** deriv-exponentiation) + 'done) + + +function deriv_exponentiation(operands, variable) { + const bas = base(operands); + const exp = exponent(operands); + return make_product(exp, + make_product(make_exponentiation(bas, make_sum(exp, -1)), + deriv(bas, variable))); +} +function install_exponentiation_extension() { + put("deriv", "**", deriv_exponentiation); + return "done"; +} +install_exponentiation_extension(); + + + +
  6. +
  7. + + 在这个简单的代数操作器中,表达式的类型是将其结合在一起的代数运算符。然而,假设我们以相反的方式索引 + + 过程 + 函数 + + ,这样deriv中的分派行看起来像 + + +((get (operator exp) 'deriv) (operands exp) var) + + +get(operator(exp), "deriv")(operands(exp), variable); + + + 对导数系统有哪些相应的更改是必需的? + +

    + 我们需要更改微分库安装过程中的参数顺序: + + +(put '+ 'deriv deriv-sum ) +(put '* 'deriv deriv-product) +(put '** 'deriv deriv-exponentiation) + + +put("+", "deriv", deriv_sum); +put("*", "deriv", deriv_product); +put("**", "deriv", deriv_exponentiation); + + +
  8. +
+
+
+ + Insatiable数据库Insatiable Enterprises 公司人事 Enterprises, Inc., 是一个高度分散的集团公司,由位于世界各地的大量独立部门组成。该公司的计算机设施刚刚通过一个巧妙的网络接口方案互连,使得整个网络对任何用户而言看似为一台单一计算机。Insatiable 的总裁在首次尝试利用网络从各部门文件中提取管理信息时,却发现虽然所有部门文件都被实现为 + + Scheme, + + + JavaScript, + + 数据结构,但每个部门使用的具体数据结构却不尽相同。各部门经理紧急召开会议,以寻找一种既能满足总部需求又能保持各部门现有自治性的策略来整合文件。 +

+ + 显示如何用数据库数据导向编程数据导向编程实现这种策略。作为示例,假设每个部门的人事记录包含一个文件,里面有以员工姓名为关键字的一组记录。集合的结构因部门而异。此外,每个员工的记录本身也是一个集合(在各部门间的结构不同),其中包含的信息以诸如标识符为关键字。 + address 和 + salary 。尤其是: +
    +
  1. + 为总部实现一个 + + get-record + get_record + + + 过程 + 函数 + + ,该过程可以从指定的人事文件中检索特定员工的记录。 + + 过程 + 函数 + + 应适用于任何部门的文件。解释个别部门的文件应该如何结构化。特别是,必须提供什么类型的信息? +
  2. +
  3. + 为总部实现一个 + + get-salary + get_salary + + + 过程 + 函数 + + ,该过程可以从任何部门的人事文件中返回特定员工记录的薪资信息。为了使该操作正常工作,记录应该如何结构化? +
  4. +
  5. + 为总部实现一个 + + find-employee-record + find_employee_record + + + + 过程。 + 函数。 + + 该过程应搜索所有部门的文件以找到给定员工的记录并返回该记录。假设这个 + + 过程 + 函数 + + 的参数是员工的姓名和所有部门文件的列表。 +
  6. +
  7. + 当 Insatiable 收购一家新公司时,为了将新的人员信息纳入中央系统,必须进行哪些更改? +
  8. +
+ +
    +
  1. + 为总部实现一个 + + get-record + get_record + + + + 过程 + 函数 + + ,该过程可以从指定的人事文件中检索特定员工的记录。 + + 过程 + 函数 + + 应适用于任何部门的文件。解释个别部门的文件应该如何结构化。特别是,必须提供什么类型的信息? +

    + 我们正在使用一个在段中标记的函数,为每个部门的文件标记一个唯一标识符。我们假设每个部门都提供了一个get_record函数的实现,并将其安装在公司范围的操作表中。 + + +function make_insatiable_file(division, file) { + return pair(division, file); +} +function insatiable_file_division(insatiable_file) { + return head(insatiable_file); +} +function insatiable_file_content(insatiable_file) { + return tail(insatiable_file); +} +function get_record(employee_name, insatiable_file) { + const the_division + = insatiable_file_division(insatiable_file); + const division_record = get("get_record", the_division) + (employee_name, + insatiable_file_content( + insatiable_file)); + return ! is_undefined(record) + ? attach_tag(the_division, division_record) + : undefined; +} + + +
  2. +
  3. + 为总部实现一个 + + get-salary + get_salary + + + + 过程 + 函数 + + ,该过程可以从任何部门的人事文件中返回特定员工记录的薪水信息。为了使该操作正常工作,记录应该如何结构化? +

    + 每个部门都需要实现诸如get_salary的函数,并将它们安装在Insatiable的操作表中。然后,Insatiable的函数get_salary可以像这样: + + +function make_insatiable_record(division, record) { + return pair(division, record); +} +function insatiable_record_division(insatiable_record) { + return head(insatiable_record); +} +function insatiable_record_content(insatiable_record) { + return tail(insatiable_record); +} +function get_salary(insatiable_record) { + const the_division = + insatiable_record_division(insatiable_record); + return get("get_salary", the_division) + (insatiable_record_content); +} + + + 请注意,我们依赖于get_record返回的任何员工记录都与其部门标签相关,这被用于通用函数get_salary中,以便从操作表中检索正确的实现。 +
  4. +
  5. + + 为总部实现一个 + + find-employee-record + find_employee_record + + 过程。 + 函数。 + + 该过程应搜索所有部门的文件以找到给定员工的记录并返回该记录。假设这个 + 过程 + 函数 + + 的参数是员工的姓名和所有部门文件的列表。 + + + +function find_employee_record(employee_name, + personnel_files) { + if (is_null(personnel_files)) { + return undefined; + } else { + const insatiable_record + = get_record(employee_name, + head(personnel_files)); + return ! is_undefined(insatiable_record) + ? insatiable_record + : find_employee_record(employee_name, + tail(personnel_files)); + } +} + + +
  6. +
  7. + + 当 Insatiable 收购一家新公司时,为了将新的人员信息纳入中央 + 系统,必须进行哪些更改? + +

    + 我们需要为每个新收购的公司做以下事情: +
      +
    • + 确定一个名称作为与新部门有关的任何数据项的标签。 +
    • +
    • + 编写所有特定于部门的函数,例如 + get_salary + 并使用部门标签将它们安装在公司范围的操作表中。 +
    • +
    • + 将员工文件添加到 + personnel_files列表中。 + 请注意,这是一个破坏性操作类似于操作表的扩展在于数据结构被永久地和不可逆转地修改; 详见段。 +
    • +
    +
  8. +
+
+ +
+ 数据导向编程 + 可加性 + + + 消息传递 + + + 消息传递 + + + 数据导向编程的关键思想是通过处理显式的操作与类型表(例如 + + + 图中的表) + + + 图中的表)。 + + 来在程序中处理通用操作。在 + 节中,我们使用的编程风格是通过让每个操作处理自己的分派来组织所需的类型分派。实际上,这将操作与类型表分解为行,每个通用操作 + 过程 + 函数 + 表示表的一行。 + + + 一种替代的实现策略是将表分解为列,与其使用基于数据类型的智能操作,不如使用基于操作名称的智能数据对象。我们可以通过安排这样一种方式来实现,例如让一个数据对象(如直角坐标数)被表示为一个 + 过程 + 函数 + ,该过程(或函数)将所需的操作名称作为输入并执行所指示的操作。在这种规则中, + + make-from-real-imag + make_from_real_imag + + 可以写成 + + make_from_real_imagmessage-passing + make_from_real_imag_message_passing + square_definition + message_passing_example + 9 + +(define (make-from-real-imag x y) + (define (dispatch op) + (cond ((eq? op 'real-part) x) + ((eq? op 'imag-part) y) + ((eq? op 'magnitude) + (sqrt (+ (square x) (square y)))) + ((eq? op 'angle) (atan y x)) + (else + (error "Unknown op -- MAKE-FROM-REAL-IMAG" op)))) + dispatch) + + +function make_from_real_imag(x, y) { + function dispatch(op) { + return op === "real_part" + ? x + : op === "imag_part" + ? y + : op === "magnitude" + ? math_sqrt(square(x) + square(y)) + : op === "angle" + ? math_atan2(y, x) + : error(op, "unknown op -- make_from_real_imag"); + } + return dispatch; +} + + +function make_from_real_imag(x, y) { + function dispatch(op) { + return op === "real_part" + ? x + : op === "imag_part" + ? y + : op === "magnitude" + ? math_sqrt(square(x) + square(y)) + : op === "angle" + ? math_atan2(y, x) + : error(op, "unknown op -- make_from_real_imag"); + } + return dispatch; +} + +function make_from_mag_ang(r, a) { + function dispatch(op) { + return op === "real_part" + ? r * math_cos(a) + : op === "imag_part" + ? r * math_sin(a) + : op === "magnitude" + ? r + : op === "angle" + ? a + : error(op, "unknown op -- make_from_real_imag"); + } + return dispatch; +} + +function apply_generic(op, arg) { + return head(arg)(op); +} +function real_part(z) { + return apply_generic("real_part", list(z)); +} +function imag_part(z) { + return apply_generic("imag_part", list(z)); +} +function magnitude(z) { + return apply_generic("magnitude", list(z)); +} +function angle(z) { + return apply_generic("angle", list(z)); +} +function add_complex(z1, z2) { + return make_from_real_imag( + real_part(z1) + real_part(z2), + imag_part(z1) + imag_part(z2)); +} +function sub_complex(z1, z2) { + return make_from_real_imag( + real_part(z1) - real_part(z2), + imag_part(z1) - imag_part(z2)); +} +function mul_complex(z1, z2) { + return make_from_mag_ang( + magnitude(z1) * magnitude(z2), + angle(z1) + angle(z2)); +} +function div_complex(z1, z2) { + return make_from_mag_ang( + magnitude(z1) / magnitude(z2), + angle(z1) - angle(z2)); +} +// operation_table, put and get +// from chapter 3 (section 3.3.3) +function assoc(key, records) { + return is_null(records) + ? undefined + : equal(key, head(head(records))) + ? head(records) + : assoc(key, tail(records)); +} +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 + : "undefined operation -- table"; + } + return dispatch; +} +const operation_table = make_table(); +const get = operation_table("lookup"); +const put = operation_table("insert"); +function attach_tag(type_tag, contents) { + return pair(type_tag, contents); +} +function type_tag(datum) { + return is_pair(datum) + ? head(datum) + : error(datum, "bad tagged datum -- type_tag"); +} +function contents(datum) { + return is_pair(datum) + ? tail(datum) + : error(datum, "bad tagged datum -- contents"); +} +function install_rectangular_package() { + function real_part(z) { return head(z); } + function imag_part(z) { return tail(z); } + function make_from_real_imag(x, y) { return pair(x, y); } + function magnitude(z) { + return math_sqrt(square(real_part(z)) + + square(imag_part(z))); + } + function angle(z) { + return math_atan2(imag_part(z), real_part(z)); + } + function make_from_mag_ang(r, a) { + return pair(r * math_cos(a), r * math_sin(a)); + } + // interface to the rest of the system + function tag(x) { + return attach_tag("rectangular", x); + } + put("real_part", list("rectangular"), real_part); + put("imag_part", list("rectangular"), imag_part); + put("magnitude", list("rectangular"), magnitude); + put("angle", list("rectangular"), angle); + put("make_from_real_imag", "rectangular", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "rectangular", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} + +install_rectangular_package(); +function install_polar_package() { + // internal functions + function magnitude(z) { return head(z); } + function angle(z) { return tail(z); } + function make_from_mag_ang(r, a) { return pair(r, a); } + function real_part(z) { + return magnitude(z) * math_cos(angle(z)); + } + function imag_part(z) { + return magnitude(z) * math_sin(angle(z)); + } + function make_from_real_imag(x, y) { + return pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x)); + } + + // interface to the rest of the system + function tag(x) { return attach_tag("polar", x); } + put("real_part", list("polar"), real_part); + put("imag_part", list("polar"), imag_part); + put("magnitude", list("polar"), magnitude); + put("angle", list("polar"), angle); + put("make_from_real_imag", "polar", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "polar", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} + +install_polar_package(); + + + 相应的 + apply-generic + apply_generic + + + + 过程, + 函数, + + 应用通用操作到一个参数时,现在简单地将操作名传递给数据对象并让对象完成工作:这种组织的一个限制是它只允许单参数通用 + 过程 + 函数 + + + apply_genericwith message passing + apply_generic_message_passing + make_from_real_imag_message_passing + message_passing_example + +(define (apply-generic op arg) (arg op)) + + +function apply_generic(op, arg) { return head(arg)(op); } + + + + + + message_passing_example + +const my_complex_number = + make_from_real_imag(1.0, 4.5); + +const result = + add_complex(my_complex_number, + my_complex_number); + +imag_part(result); + + + + generic_selectors_message_passing + apply_generic_message_passing + generic_selectors_example + +(define (real-part z) (apply-generic 'real-part z)) +(define (imag-part z) (apply-generic 'imag-part z)) +(define (magnitude z) (apply-generic 'magnitude z)) +(define (angle z) (apply-generic 'angle z)) + + +function real_part(z) { + return apply_generic("real_part", list(z)); +} +function imag_part(z) { + return apply_generic("imag_part", list(z)); +} +function magnitude(z) { + return apply_generic("magnitude", list(z)); +} +function angle(z) { + return apply_generic("angle", list(z)); +} + + + 注意 + + make-from-real-imag + make_from_real_imag + + + 返回的值是一个 + + 过程内部 + 函数内部 + + dispatch + + 过程。 + 函数。 + + 这是当 + apply-generic + apply_generic + + 请求执行操作时被调用的 + 过程 + 函数 + + + + 这种编程风格被称为消息传递。这个名称来自于这样一个图象:一个数据对象是一个接收到所请求操作名称作为消息的实体。我们已经在节中看到过一个消息传递的例子,在那里我们展示了如何用没有数据对象仅用 + + cons, + pair, + + + car, + head, + + 和 + + cdr + tail + + 可以被定义为只有 + + 过程。 + 函数。 + + 在这里我们看到消息传递不是一个数学技巧,而是一种组织通用操作系统的有用技术。在本章的剩余部分中,我们将继续使用数据导向编程,而非消息传递,来讨论通用算术操作。在中,我们将返回到消息传递,并且我们将看到它在结构化模拟程序中可以是一种强有力的工具。 + + + + 实现构造器 + make_from_mag_ang消息传递 + + make-from-mag-ang + make_from_mag_ang + + + 以消息传递风格。这个 + + 过程 + 函数 + + 应当类似于上述给出的 + + make-from-real-imag + make_from_real_imag + + + + 过程。 + 函数。 + + + + make_from_mag_ang_message_passing + make_from_real_imag_message_passing + message_passing_example_2 + 4.589053123706931 + + + +function make_from_mag_ang(r, a) { + function dispatch(op) { + return op === "real_part" + ? r * math_cos(a) + : op === "imag_part" + ? r * math_sin(a) + : op === "magnitude" + ? r + : op === "angle" + ? a + : error(op, "unknown op -- make_from_real_imag"); + } + return dispatch; +} + + + + + + + message_passing_example_2 + +const my_complex_number = + make_from_mag_ang(3.0, 0.7); + +const result = + add_complex(my_complex_number, + my_complex_number); + +real_part(result); + + + + + + + + 随着具有通用操作的大型系统的发展,可能需要新的数据对象类型或新的操作。对于三种策略中的每一种具有显式分派比较不同风格分派的通用操作、数据导向风格和消息传递风格描述在系统中为了增加新类型或新操作而必须进行的更改。对于需要经常添加新类型的系统,哪种组织最合适?对于需要经常添加新操作的系统,哪种最合适? + + +
    +
  • + 具有显式分派的通用操作:对于每种新类型,我们需要触及每个通用接口函数,添加一个新案例。 +
  • +
  • + 数据导向风格:在这里,通用接口函数的实现可以简洁地打包在每个新类型的安装库中。我们也可以为新操作提供安装库。 +
  • +
  • + 消息传递风格:与数据导向风格类似,我们需要为每种新类型编写一个库。在这种情况下,库由一个分派函数组成,每个通用接口函数都有一个案例。 +
  • +
+ 总的来说,当我们需要频繁添加新操作时,使用数据导向风格是最好的,而在我们频繁添加新类型时,消息传递风格更为合适。 +
+
+
diff --git a/xml/cn/chapter2/section5/section5.xml b/xml/cn/chapter2/section5/section5.xml new file mode 100644 index 000000000..221cf842d --- /dev/null +++ b/xml/cn/chapter2/section5/section5.xml @@ -0,0 +1,103 @@ +
+ 通用操作的系统 + + + + + + 上一节中,我们看到如何设计可以以多种方式表示数据对象的系统。关键是通过通用接口将指定数据操作的代码链接到多个表示 + + 过程。 + 函数。 + 现在我们将看到如何使用相同的思路,不仅定义在不同表示之间通用的操作,还定义在不同类型参数上的 + 算术通用 + 通用操作。我们已经看到几种不同的算术运算包:原始算术 ( 【12:10†cn.txt】) 【4:0†source】+, + -, *, + / ) 内建于我们的语言中, 有理数运算 + + (add_rat, + (add_rat, + + + sub_rat, + sub_rat, + + + mul_rat, + mul_rat, + + + div_rat) + div_rat) + + 章节,以及在我们实现的复数算术中 + 章节。现在我们将使用数据导向技术来构建一个包含我们已构建的所有算术包的算术运算包。 + + + + + 图 + + + 图 + + + 显示了我们将要构建的系统结构。注意 + 抽象屏障在通用算术系统中 + 抽象屏障。从使用数字的人的角度来看,这是一个单一的 + + 过程 + 函数 + + add 运行在提供的任何数值上。 + + add + 函数 add + + + 是一个通用接口的一部分,该接口允许通过通用程序访问独立的普通算术、有理算术和复数算术包, +任何单独的算术包(例如复数包)本身可以通过通用 + + 过程 + 函数 + + (例如 + + add-complex) + add_complex) + + + 访问,这些操作结合了为不同表示(如直角和极坐标)设计的包。此外,系统结构是可加的,因此可以单独设计各个算术包并将它们组合以生成通用算术系统。 + 消息传递 + + +
+
+ 通用算术系统。 + +
+ + +
+
+ + 通用 + 通用算术操作系统结构 + 算术系统。 + + +
+
+
+
+ + + &subsection2.5.1; + + + &subsection2.5.2; + + + &subsection2.5.3; + +
diff --git a/xml/cn/chapter2/section5/subsection1.xml b/xml/cn/chapter2/section5/subsection1.xml new file mode 100644 index 000000000..0092a3c8d --- /dev/null +++ b/xml/cn/chapter2/section5/subsection1.xml @@ -0,0 +1,1012 @@ + + + 通用算术操作 + + + + 通用算术操作 + + 设计通用算术操作的任务类似于设计通用复数操作的任务。例如,我们希望有一个通用加法 + + 过程 + 函数 + + add,它在普通数字上像普通原始加法 + +一样运作,就像在有理数上 + + add-rat + add_rat + + ,在复数上像 + + add-complex + add_complex + + 一样。我们可以通过采用我们在实现通用复数选择器时使用的相同策略来实现add和其他通用算术操作。我们将为每种数字附加一个类型标签,并使通用 + + 过程 + 函数 + + 根据其参数的数据类型分派到一个适当的软件包。 + + + 通用算术 + + 过程 + 函数 + + 定义如下: + + add (通用) + sub (通用) + mul (通用) + div (通用) + ops + apply_generic + +(define (add x y) (apply-generic 'add x y)) +(define (sub x y) (apply-generic 'sub x y)) +(define (mul x y) (apply-generic 'mul x y)) +(define (div x y) (apply-generic 'div x y)) + + +function add(x, y) { return apply_generic("add", list(x, y)); } + +function sub(x, y) { return apply_generic("sub", list(x, y)); } + +function mul(x, y) { return apply_generic("mul", list(x, y)); } + +function div(x, y) { return apply_generic("div", list(x, y)); } + + + + + 我们开始安装一个用于处理 + 通用在通用算术系统中 + 普通数 (在通用算术系统中) + 普通数字的软件包,即我们语言的原语数字。 + + 会 + + 为这些附加 + + scheme-number符号。 + + 字符串"javascript_number"。 + + + 此包中的算术操作是原语算术 + + 过程 + 函数 + + (因此无需定义额外的 + + 过程 + 函数 + + 来处理未打标签的数字)。由于这些操作每个都需要两个 + 参数,它们被安装在由列表键控的表中 + + + (scheme-number scheme-number): + + list("javascript_number", "javascript_number") + + + packageJavaScript-number + javascript_number package + install_javascript_number_package + install_javascript_number_package + ops + operation_table_from_chapter_3 + operation_table + attach_tag + +(define (install-scheme-number-package) + (define (tag x) + (attach-tag 'scheme-number x)) + (put 'add '(scheme-number scheme-number) + (lambda (x y) (tag (+ x y)))) + (put 'sub '(scheme-number scheme-number) + (lambda (x y) (tag (- x y)))) + (put 'mul '(scheme-number scheme-number) + (lambda (x y) (tag (* x y)))) + (put 'div '(scheme-number scheme-number) + (lambda (x y) (tag (/ x y)))) + (put 'make 'scheme-number + (lambda (x) (tag x))) + 'done) + + +function install_javascript_number_package() { + function tag(x) { + return attach_tag("javascript_number", x); + } + put("add", list("javascript_number", "javascript_number"), + (x, y) => tag(x + y)); + put("sub", list("javascript_number", "javascript_number"), + (x, y) => tag(x - y)); + put("mul", list("javascript_number", "javascript_number"), + (x, y) => tag(x * y)); + put("div", list("javascript_number", "javascript_number"), + (x, y) => tag(x / y)); + put("make", "javascript_number", + x => tag(x)); + return "done"; +} + + + + + 使用 + + Scheme-number 包 + JavaScript-number 包 + + 的用户将通过以下 + + 过程: + 函数: + + 创建(带标签的)普通数字: + + actually_install_javascript_number_package + +install_javascript_number_package(); + + + + make_javascript_number + install_javascript_number_package_usage + install_javascript_number_package + actually_install_javascript_number_package + install_javascript_number_package_usage_example + [ 'javascript_number', 9 ] + +(define (make-scheme-number n) + ((get 'make 'scheme-number) n)) + + +function make_javascript_number(n) { + return get("make", "javascript_number")(n); +} + + + + install_javascript_number_package_usage_example + +const n1 = make_javascript_number(4); +const n2 = make_javascript_number(5); + +add(n1, n2); + + + + + + 现在通用算术系统的框架已到位, + 我们可以轻松地包含新类型的数字。这里有一个执行有理数算术的包。注意,由于可加性的好处,我们可以使用中的有理数代码,而无需修改,作为包中的内部 + + 过程 + 函数 + + : + + packagerational-number + rational package + rational-number arithmeticinterfaced to generic arithmetic system + install_rational_package + make_rational + benefit_of_additivity + ops + operation_table_from_chapter_3 + operation_table + attach_tag + gcd_definition + benefit_of_additivity_example + [ 'rational', [ 11, 15 ] ] + +(define (install-rational-package) + ;; internal procedures + (define (numer x) (car x)) + (define (denom x) (cdr x)) + (define (make-rat n d) + (let ((g (gcd n d))) + (cons (/ n g) (/ d g)))) + (define (add-rat x y) + (make-rat (+ (* (numer x) (denom y)) + (* (numer y) (denom x))) + (* (denom x) (denom y)))) + (define (sub-rat x y) + (make-rat (- (* (numer x) (denom y)) + (* (numer y) (denom x))) + (* (denom x) (denom y)))) + (define (mul-rat x y) + (make-rat (* (numer x) (numer y)) + (* (denom x) (denom y)))) + (define (div-rat x y) + (make-rat (* (numer x) (denom y)) + (* (denom x) (numer y)))) + + ;; interface to rest of the system + (define (tag x) (attach-tag 'rational x)) + (put 'add '(rational rational) + (lambda (x y) (tag (add-rat x y)))) + (put 'sub '(rational rational) + (lambda (x y) (tag (sub-rat x y)))) + (put 'mul '(rational rational) + (lambda (x y) (tag (mul-rat x y)))) + (put 'div '(rational rational) + (lambda (x y) (tag (div-rat x y)))) + + (put 'make 'rational + (lambda (n d) (tag (make-rat n d)))) + 'done) + +(define (make-rational n d) + ((get 'make 'rational) n d)) + + +function install_rational_package() { + // internal functions + function numer(x) { return head(x); } + function denom(x) { return tail(x); } + function make_rat(n, d) { + const g = gcd(n, d); + return pair(n / g, d / g); + } + function add_rat(x, y) { + return make_rat(numer(x) * denom(y) + numer(y) * denom(x), + denom(x) * denom(y)); + } + function sub_rat(x, y) { + return make_rat(numer(x) * denom(y) - numer(y) * denom(x), + denom(x) * denom(y)); + } + function mul_rat(x, y) { + return make_rat(numer(x) * numer(y), + denom(x) * denom(y)); + } + function div_rat(x, y) { + return make_rat(numer(x) * denom(y), + denom(x) * numer(y)); + } + // interface to rest of the system + function tag(x) { + return attach_tag("rational", x); + } + put("add", list("rational", "rational"), + (x, y) => tag(add_rat(x, y))); + put("sub", list("rational", "rational"), + (x, y) => tag(sub_rat(x, y))); + put("mul", list("rational", "rational"), + (x, y) => tag(mul_rat(x, y))); + put("div", list("rational", "rational"), + (x, y) => tag(div_rat(x, y))); + put("make", "rational", + (n, d) => tag(make_rat(n, d))); + return "done"; +} + +function make_rational(n, d) { + return get("make", "rational")(n, d); +} + + + + benefit_of_additivity_example + +install_rational_package(); + +const r1 = make_rational(1, 3); +const r2 = make_rational(2, 5); + +add(r1, r2); + + + + + 我们可以安装一个类似的软件包来处理复数,使用标签 + + complex。 + "complex" + + 在创建软件包时,我们从表中提取由直角和极坐标包定义的操作 + + make-from-real-imag + make_from_real_imag + + + 和 + + make-from-mag-ang + make_from_mag_ang + + + 。 + 可加性 + 可加性使我们可以使用相同的内部操作, + + add-complex, + add_complex, + + + + sub-complex, + sub_complex, + + + + mul-complex, + mul_complex, + + + 和 + + div-complex + div_complex + + + 过程 + 函数 + + ,来自。 + + packagecomplex-number + complex package + complex-number arithmeticinterfaced to generic arithmetic system + install_complex_package + install_complex_package + ops + generic_selectors + operation_table_from_chapter_3 + operation_table + install_rectangular_package + install_rectangular_package_usage + install_polar_package_usage + attach_tag + actually_install_complex_package + 'done' + +(define (install-complex-package) + ;; imported procedures from rectangular and polar packages + (define (make-from-real-imag x y) + ((get 'make-from-real-imag 'rectangular) x y)) + (define (make-from-mag-ang r a) + ((get 'make-from-mag-ang 'polar) r a)) + + ;; internal procedures + (define (add-complex z1 z2) + (make-from-real-imag (+ (real-part z1) (real-part z2)) + (+ (imag-part z1) (imag-part z2)))) + (define (sub-complex z1 z2) + (make-from-real-imag (- (real-part z1) (real-part z2)) + (- (imag-part z1) (imag-part z2)))) + (define (mul-complex z1 z2) + (make-from-mag-ang (* (magnitude z1) (magnitude z2)) + (+ (angle z1) (angle z2)))) + (define (div-complex z1 z2) + (make-from-mag-ang (/ (magnitude z1) (magnitude z2)) + (- (angle z1) (angle z2)))) + + ;; interface to rest of the system + (define (tag z) (attach-tag 'complex z)) + (put 'add '(complex complex) + (lambda (z1 z2) (tag (add-complex z1 z2)))) + (put 'sub '(complex complex) + (lambda (z1 z2) (tag (sub-complex z1 z2)))) + (put 'mul '(complex complex) + (lambda (z1 z2) (tag (mul-complex z1 z2)))) + (put 'div '(complex complex) + (lambda (z1 z2) (tag (div-complex z1 z2)))) + (put 'make-from-real-imag 'complex + (lambda (x y) (tag (make-from-real-imag x y)))) + (put 'make-from-mag-ang 'complex + (lambda (r a) (tag (make-from-mag-ang r a)))) + 'done) + + +function install_complex_package() { + // imported functions from rectangular and polar packages + function make_from_real_imag(x, y) { + return get("make_from_real_imag", "rectangular")(x, y); + } + function make_from_mag_ang(r, a) { + return get("make_from_mag_ang", "polar")(r, a); + } + // internal functions + function add_complex(z1, z2) { + return make_from_real_imag(real_part(z1) + real_part(z2), + imag_part(z1) + imag_part(z2)); + } + function sub_complex(z1, z2) { + return make_from_real_imag(real_part(z1) - real_part(z2), + imag_part(z1) - imag_part(z2)); + } + function mul_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) * magnitude(z2), + angle(z1) + angle(z2)); + } + function div_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) / magnitude(z2), + angle(z1) - angle(z2)); + } + // interface to rest of the system + function tag(z) { return attach_tag("complex", z); } + put("add", list("complex", "complex"), + (z1, z2) => tag(add_complex(z1, z2))); + put("sub", list("complex", "complex"), + (z1, z2) => tag(sub_complex(z1, z2))); + put("mul", list("complex", "complex"), + (z1, z2) => tag(mul_complex(z1, z2))); + put("div", list("complex", "complex"), + (z1, z2) => tag(div_complex(z1, z2))); + put("make_from_real_imag", "complex", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "complex", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} + + + + + 复数包以外的程序可以通过实部和虚部或模长和角度来构造复数。请注意,最初在直角和极坐标包中定义的底层 + + 过程, + 函数, + + 被导出到复数包中,并从那里导出到外部世界。 + + actually_install_complex_package + +install_complex_package(); + + + + make_complex_from_real_imag + make_complex_from_mag_ang + install_complex_package_usage + install_complex_package + actually_install_complex_package + [ 'rectangular', [ 8.387912809451864, 5.397127693021015 ] ] + install_complex_package_example + +(define (make-complex-from-real-imag x y) + ((get 'make-from-real-imag 'complex) x y)) + +(define (make-complex-from-mag-ang r a) + ((get 'make-from-mag-ang 'complex) r a)) + + +function make_complex_from_real_imag(x, y){ + return get("make_from_real_imag", "complex")(x, y); +} +function make_complex_from_mag_ang(r, a){ + return get("make_from_mag_ang", "complex")(r, a); +} + + + + install_complex_package_example + +const r = make_complex_from_real_imag(4, 3); +const p = make_complex_from_mag_ang(5, 0.5); + +add(r, p); // results in a complex number in rectangular coordinates +// mul(r, p); // results in a complex number in polar coordinates + + +const r = make_complex_from_real_imag(4, 3); +const p = make_complex_from_mag_ang(5, 0.5); +tail(add(r, p)); + + + + + 我们这里有一个 + 类型标签两级 + 的两级标签系统。一个典型的复数,例如 +$3+4i$ 在直角形式中,如 + + + 图 + + + 图 + + + 所示表示。外层标签 + (complex) + ("complex") + + 用于将数字引导到复数包。进入 + 复数包后,下一标签 + + (rectangular) + ("rectangular") + + + 用于将数字引导到直角包。在一个大型而复杂的系统中可能会有许多级别,每个级别通过通用操作与下一个级别接口。当一个数据对象被传递 + 向下时,用于将其引导到适当包的外层标签会被去掉(通过应用 +contents ) 以及下一级标签(如果有)变得可见,以便用于进一步分派。 + + +
+
+ + 矩形形式中 $3+4i$ 的表示。 + + +
+ + +
+
+ + 矩形形式中 $3+4i$ 的表示。 + + +
+
+
+
+ + 在上述软件包中,我们使用了 + + add-rat, + add_rat, + + + add-complex, + add_complex, + + 和其他算术 + + 过程 + 函数 + + ,正如最初编写的一样。一旦这些声明是不同安装 + + 过程 + 函数 + + 的内部,它们就不再需要彼此不同的名称:我们可以简单地在两个包中分别命名为add, + sub,mul,和 + div。 + + + Louis Reasoner 试图求值表达式 + + (magnitude z) + magnitude(z) + + + 其中 +z 是如 + + + 图 + + + 图 + + + 所示的对象。令他惊讶的是,结果不是 +$5$ + 他从 + + apply-generic, + apply_generic, + + + 得到了一个错误信息,提示该操作没有方法。 +magnitude + 的类型 + + (complex)。 + list("complex")。 + + + 他向 Alyssa P. Hacker 展示了这个互动,Alyssa 说问题是复数选择器从未为 + + complex + "complex" + + 数而定义,只为 + + polar + "polar" + + 和 + + rectangular + "rectangular" + + + 数字定义。要解决这个问题,你只需将以下内容添加到complex包中: + + use_generic_magnitude + install_complex_package_usage + actually_install_complex_package + use_generic_magnitude_example + 5 + +(put 'real-part '(complex) real-part) +(put 'imag-part '(complex) imag-part) +(put 'magnitude '(complex) magnitude) +(put 'angle '(complex) angle) + + +put("real_part", list("complex"), real_part); +put("imag_part", list("complex"), imag_part); +put("magnitude", list("complex"), magnitude); +put("angle", list("complex"), angle); + + + + use_generic_magnitude_example + use_generic_magnitude_example + +const z = make_complex_from_real_imag(3, 4); + +magnitude(z); + + + 详细描述为什么这可以工作。作为一个例子,跟踪所有在求值表达式 + + (magnitude z) + magnitude(z) + + 时调用的 + + 过程 + 函数 + + ,其中 +z + 是如 + + + 图 + + + 图 + + + 所示的对象。特别是, + + apply-generic + apply_generic + + + 被调用了多少次?在每种情况下被分派到哪个 + + 过程 + 函数 + + ? + + 将上述行放入包中将会打开 +z ,这些行起到了传递作用。 + + apply-generic + apply_generic + + + 将被调用两次。我们将使用替换模型跟踪程序的求值过程。 + + +magnitude(z); +apply_generic("magnitude", list(z)); +// In this case: +// type_tags = map(type_tag, list(z)) +// Which evaluates to: +// type_tags = list("complex"); +// and +// fun = get("magnitude", list("complex")); +// which, due to the addition of +// put("magnitude", list("complex"), magnitude); +// fun = magnitude; +apply(magnitude, map(contents, list(z))); +apply(magnitude, pair("rectangular", pair(3, 4))); +magnitude(pair("rectangular"), pair(3, 4)); +apply_generic("magnitude", list(pair("rectangular"), pair(3, 4))); +// type_tags = map(type_tag, list(z)) evaluates to list("rectangular") +// fun = get("magnitude", list("rectangular")) which evaluates to +// z => math_sqrt(square(real_part(z)) + square(imag_part(z))) +// z => math_sqrt(square(head(z)) + square(tail(z))) +apply(fun, map(contents, list(pair("rectangular"), pair(3, 4)))) +apply(fun, pair(3, 4)) +(z => math_sqrt(square(head(z)) + square(tail(z))))(pair(3, 4)); +math_sqrt(square(head(pair(3, 4))) + square(tail(pair(3, 4)))) +... +math_sqrt(square(3) + square(4)); +... +math_sqrt(9 + 16); +math_sqrt(25); +5 + + +;;*** Use substitution rule: +(magnitude z) + +;;** First apply-generic: +(apply-generic 'magnitude z) +;; where z is the whole object including symbol 'complex. + +;;recall +(define (apply-generic op . args) + (let ((type-tags (map type-tag args))) + (let ((proc (get op type-tags))) + (if proc + (apply proc (map contents args)) + (error + "No method for these types -- APPLY-GENERIC" + (list op type-tags)))))) +;; substitution +(let ((type-tags '(complex)) ... )) +(let ((proc (get op '(complex))) ... )) +(let ((proc magnitude) ... )) +(if proc... ;; true +(apply magnitude (contents z)) +(magnitude z-prime) ;; where z-prime is the contents (the cdr) of the original + ;; object, that is, with the 'complex stripped off. + +;;** Second apply-generic: +(let ((type-tags '(rectangular)) ... )) +(let ((proc (get op '(rectangular))) ... )) +(let ((proc (get 'magnitude '(rectangular))) ... )) +(let ((proc (lambda (z) (sqrt (+ (square (real-part z)) + (square (imag-part z)))))) ... ))) + +(if proc... ;; true +(apply (lambda (z) (sqrt (+ (square (real-part z)) + (square (imag-part z))))) (contents z-prime)) +(sqrt (+ (square 3) (square 4))) +5 + + + + + + + + 内部 + JavaScript内部类型系统 + 数据类型在 JavaScript 中 + is_number (原语函数)数据类型 and + is_string (原语函数)数据类型 and + attach_tag使用 JavaScript 数据类型 + type_tag使用 JavaScript 数据类型 + contents使用 JavaScript 数据类型 + 过程 + 函数 + + 在 + + scheme-number + javascript_number + + + 包中本质上不过是对原语的调用 + + 过程 + 函数 + ++, - ,等等。我们的类型标签系统要求每个数据对象都必须附加一个类型,因此无法直接使用语言的原语。然而,事实上,所有 + + Lisp + JavaScript + + 实现确实有一个类型系统,它们在内部使用。原语谓词如 + + symbol? + is_string + + 和 + + number? + is_number + + 判断数据对象是否具有特定类型。修改 + + type-tag, + type_tag, + + 的定义 +contents ,和 + + attach-tag + attach_tag + + 来自以便我们的通用系统利用 + Schemes + JavaScripts + + 内部类型系统。也就是说,系统应与之前一样运作,只是普通数字应简单地表示为 + + Scheme + JavaScript + + 数字,而不是其 + + car + head + + 是 + + 符号 scheme-number。 + + 字符串 "javascript_number"。 + + + + + + 以下修改将利用 JavaScript 数字实现: + + +function attach_tag(type_tag, contents) { + return is_number(contents) + ? pair("javascript_number", contents) + : pair(type_tag, contents); +} +function type_tag(datum) { + return is_number(datum) + ? "javascript_number" + : is_pair(datum) + ? head(datum) + : error(datum, "bad tagged datum -- type_tag"); +} +function contents(datum) { + return is_number(datum) + ? datum + : is_pair(datum) + ? tail(datum) + : error(datum, "bad tagged datum -- contents"); +} + + +(define (attach-tag type-tag contents) + (if (number? contents) + contents + (cons type-tag contents))) + +(define (type-tag datum) +(cond ((number? datum) 'scheme-number) + ((pair? datum) (car datum)) + (else (error "Wrong datum -- TYPE-TAG" datum)))) + +(define (contents datum) +(cond ((number? datum) datum) + ((pair? datum) (cdr datum)) + (else (error "Wrong datum -- CONTENTS" datum)))) + + + + + + + 定义一个通用相等性谓词 + is_equal (通用相等性谓词) + 相等性通用在通用算术系统中 + + equ? + is_equal + + ,用于测试两个数的相等性,并将其安装在通用算术包中。此操作应适用于普通数字,有理数和复数。 + + + + +// provided by GitHub user clean99 + +function is_equal(x, y) { + return apply_generic("is_equal", list(x, y)); +} + +function install_javascript_number_package() { + // ... + + put("is_equal", list("javascript_number", "javascript_number"), + (x, y) => x === y); + + // ... +} + +function install_rational_package() { + // ... + + function is_equal(x, y) { + return numer(x) * denom(y) === numer(y) * denom(x); + } + + put("is_equal", list("rational", "rational"), is_equal); + + // ... +} + +function install_complex_package() { + // ... + + function is_equal(z1, z2) { + return real_part(z1) === real_part(z2) + ? imag_part(z1) === imag_part(z2) + : false; + } + + put("is_equal", list("complex", "complex"), + is_equal); + + //... +} + + + + + + + 定义一个通用谓词 + is_equal_to_zero (通用) + 零值测试 (通用) + + =zero? + is_equal_to_zero + + + ,用于测试其参数是否为零,并将其安装在通用算术包中。此操作应适用于普通数字,有理数和复数。 + + + + +// provided by GitHub user clean99 + +function is_equal_to_zero(x) { + return apply_generic("is_equal_to_zero", list(x)); +} + +function install_javascript_number_package() { + // ... + + put("is_equal_to_zero", "javascript_number", + x => x === 0); + + // ... +} + +function install_rational_package() { + // ... + + function is_equal_to_zero(x) { + return numer(x) === 0; + } + + put("is_equal_to_zero", "rational", + is_equal_to_zero); + + // ... +} + +function install_complex_package() { + // ... + + function is_equal_to_zero(z) { + return real_part(z) === 0 + ? imag_part(z) === 0 + : false; + } + + put("is_equal_to_zero", "complex", + is_equal_to_zero); + + //... +} + + + + + + 通用算术操作 +
diff --git a/xml/cn/chapter2/section5/subsection2.xml b/xml/cn/chapter2/section5/subsection2.xml new file mode 100644 index 000000000..feab20db6 --- /dev/null +++ b/xml/cn/chapter2/section5/subsection2.xml @@ -0,0 +1,899 @@ + + 不同类型数据的组合 + + + + 我们已经看到如何定义一个统一的算术系统,该系统包含普通数、复数、有理数和我们可能决定发明的任何其他类型的数字,但我们忽略了一个重要问题。到目前为止,我们定义的操作将不同的数据类型视为完全独立的。因此,添加两个普通数字或两个复数等是有单独的软件包。我们尚未考虑的是定义跨类型边界的运算的意义,如将复数与普通数相加。我们非常费尽心思地在程序的各个部分之间设置屏障,以便它们可以独立开发和理解。我们希望以某种精心控制的方式引入跨类型运算,以便在不严重违反模块边界的情况下支持它们。 + 一种处理 操作跨类型 跨类型操作 类型跨类型操作 跨类型操作的方法是为每种可能的、操作有效的数据类型组合设计不同的 过程 函数。例如,我们可以扩展复数包,以便它提供一个 过程 函数 用于将复数加到普通数,并使用标签 (complex scheme-number)list("complex", "javascript_number"):我们还需要提供一个几乎相同的 过程 函数 来处理类型

(scheme_number complex) list("javascript_number", "complex")
+ add_complex_to_javascript_number_example + +const c = make_complex_from_real_imag(4, 3); +const n = make_javascript_number(7); + +add(c, n); + + + + add_complex_to_javascript_num + add_complex_to_javascript_number + install_javascript_number_package_usage + install_complex_package_usage + add_complex_to_javascript_number_example + [ 'complex', [ 'rectangular', [ 11, 3 ] ] ] + +;; to be included in the complex package +(define (add-complex-to-schemenum z x) + (make-from-real-imag (+ (real-part z) x) + (imag-part z))) + +(put 'add '(complex scheme-number) + (lambda (z x) (tag (add-complex-to-schemenum z x)))) + + +// to be included in the complex package +function add_complex_to_javascript_num(z, x) { + return make_complex_from_real_imag(real_part(z) + x, imag_part(z)); +} +put("add", list("complex", "javascript_number"), + (z, x) => tag(add_complex_to_javascript_num(z, x))); + + +
+ 这种技术有效,但非常繁琐。在这样的系统中,引入新类型的成本不仅仅是为该类型构建 过程 函数 的软件包,还包括实现跨类型操作的 过程 函数 的构建和安装。这可能会比定义该类型自身操作所需的代码量更多。该方法还影响了我们以加法方式组合独立包的能力,或至少限制了单个包的实现者需要考虑其他包的程度。例如,在上面的例子中,处理复数和普通数的混合操作似乎合理地应由复数包负责。然而,将有理数和复数组合可能由复数包、有理数包或某个使用这两个包提取操作的第三方包完成。在许多包和许多跨类型操作的系统设计中,对包之间责任划分制定一致的策略可能是一个艰巨的任务。 + + 强制类型转换 + + 强制类型转换 + 在完全不相关的操作作用于完全不相关的类型的情况下,实现显式的跨类型操作虽然繁琐,但已是最佳方案。幸运的是,通常我们可以通过利用类型系统中潜在的附加结构做得更好。通常,不同的数据类型并不是完全独立的,可能存在某种方法可以将一种类型的对象视为另一种类型的对象。这个过程被称为强制类型转换。例如,如果要求我们将一个普通数与一个复数进行算术组合,我们可以将普通数视为虚部为零的复数。这将问题转换为组合两个复数的问题,可以通过复数算术包以常规方式处理。 + 一般来说,我们可以通过设计 强制类型转换过程函数 强制类型转换 过程 函数 来实现该想法,将一种类型的对象转换为另一种类型的等效对象。以下是一个典型的强制类型转换 过程, 函数, 将给定的普通数字转换为具有该实部和零虚部的复数: + javascript_number_to_complex + javascript_number_to_complex + +(define (scheme-number->complex n) + (make-complex-from-real-imag (contents n) 0)) + + +function javascript_number_to_complex(n) { + return make_complex_from_real_imag(contents(n), 0); +} + + + 强制类型转换用于强制转换 强制类型转换 我们将在一个特殊的强制类型转换表中安装这些强制类型转换 过程 函数 ,按这两种类型的名称进行索引: + put_get_coercion + +(define coercion-list '()) + +(define (clear-coercion-list) + (set! coercion-list '())) + +(define (put-coercion type1 type2 item) + (if (get-coercion type1 type2) coercion-list + (set! coercion-list + (cons (list type1 type2 item) + coercion-list)))) + +(define (get-coercion type1 type2) + (define (get-type1 listItem) + (car listItem)) + (define (get-type2 listItem) + (cadr listItem)) + (define (get-item listItem) + (caddr listItem)) + (define (get-coercion-iter list type1 type2) + (if (null? list) #f + (let ((top (car list))) + (if (and (equal? type1 (get-type1 top)) + (equal? type2 (get-type2 top))) (get-item top) + (get-coercion-iter (cdr list) type1 type2))))) + (get-coercion-iter coercion-list type1 type2)) + + +let coercion_list = null; + +function clear_coercion_list() { + coercion_list = null; +} + +function put_coercion(type1, type2, item) { + if (is_null(get_coercion(type1, type2))) { + coercion_list = pair(list(type1, type2, item), + coercion_list); + } else { + return coercion_list; + } +} + +function get_coercion(type1, type2) { + function get_type1(list_item) { + return head(list_item); + } + function get_type2(list_item) { + return head(tail(list_item)); + } + function get_item(list_item) { + return head(tail(tail(list_item))); + } + function get_coercion_iter(items) { + if (is_null(items)) { + return undefined; + } else { + const top = head(items); + return equal(type1, get_type1(top)) && + equal(type2, get_type2(top)) + ? get_item(top) + : get_coercion_iter(tail(items)); + } + } + return get_coercion_iter(coercion_list); +} + + + + put_coercion_usage + put_get_coercion + javascript_number_to_complex + install_complex_package_usage + put_coercion_usage_example + put_get_coercion + + +(put-coercion 'scheme-number 'complex scheme-number->complex) + + +put_coercion("javascript_number", "complex", + javascript_number_to_complex); + + + + put_coercion_usage_example + +get_coercion("javascript_number", "complex"); + + (我们假设有 put-coercion put_coercion get-coercion get_coercion 过程 函数 可用于操作此表。) 通常,表中的某些槽位将为空,因为通常不能将每种类型的任意数据对象强制转换为所有其他类型。例如,无法将任意复数转换为普通数,因此不会包含一般的 complex->scheme-number complex_to_javascript_number 过程 函数 在表中。 + 一旦强制类型转换表建立,我们可以通过修改第节的 apply-generic apply_generic 过程 函数 以一种统一的方式处理强制类型转换。当要求应用一个操作时,我们首先检查操作是否对参数的类型定义,就像以前一样。如果是,我们将调度到操作与类型表中找到的 过程 函数 。否则,我们尝试强制类型转换。为简单起见,我们仅考虑存在两个参数的情况。请参见练习了解一般情况。 我们检查强制类型转换表,以查看是否可以将第一种类型的对象强制转换为第二种类型。如果可以,我们进行强制类型转换后再尝试操作。如果第一种类型的对象通常不能被转换为第二种类型,则我们反过来尝试看看是否可以将第二个参数转换为第一个参数的类型。最后,如果没有已知的方法能将任一类型转换为另一种类型,我们就放弃。以下是 过程: 函数: apply_generic带强制转换 + base_operation_table + +// operation_table, put and get +// from chapter 3 (section 3.3.3) +function assoc(key, records) { + return is_null(records) + ? undefined + : equal(key, head(head(records))) + ? head(records) + : assoc(key, tail(records)); +} +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)); + if (is_undefined(record)) { + return undefined; + } else { + return 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 + : "undefined operation -- table"; + } + return dispatch; +} +const operation_table = make_table(); +const get = operation_table("lookup"); +const put = operation_table("insert"); + +// In Source, most functions have a fixed number of arguments. +// (The function list is the only exception, to this so far.) +// The function apply_in_underlying_javascript allows us to +// apply any given function fun to all elements of the argument +// list args, as if they were separate arguments +function apply(fun, args) { + return apply_in_underlying_javascript(fun, args); +} +function add(x, y) { + return apply_generic("add", list(x, y)); +} +function sub(x, y) { + return apply_generic("sub", list(x, y)); +} +function mul(x, y) { + return apply_generic("mul", list(x, y)); +} +function div(x, y) { + return apply_generic("div", list(x, y)); +} + +function attach_tag(type_tag, contents) { + return pair(type_tag, contents); +} +function type_tag(datum) { + return is_pair(datum) + ? head(datum) + : error(datum, "bad tagged datum -- type_tag"); +} +function contents(datum) { + return is_pair(datum) + ? tail(datum) + : error(datum, "bad tagged datum -- contents"); +} + + + + javascript_number_package + base_operation_table + +function install_javascript_number_package() { + function tag(x) { + return attach_tag("javascript_number", x); + } + put("add", list("javascript_number", "javascript_number"), + (x, y) => tag(x + y)); + put("sub", list("javascript_number", "javascript_number"), + (x, y) => tag(x - y)); + put("mul", list("javascript_number", "javascript_number"), + (x, y) => tag(x * y)); + put("div", list("javascript_number", "javascript_number"), + (x, y) => tag(x / y)); + put("make", "javascript_number", + x => tag(x)); + return "done"; +} +install_javascript_number_package(); + +function make_javascript_number(n) { + return get("make", "javascript_number")(n); +} + + + + complex_number_package + base_operation_table + +// generic selector functions for complex numbers + +function real_part(z) { + return apply_generic("real_part", list(z)); +} +function imag_part(z) { + return apply_generic("imag_part", list(z)); +} +function magnitude(z) { + return apply_generic("magnitude", list(z)); +} +function angle(z) { + return apply_generic("angle", list(z)); +} +function square(x) { + return x * x; +} + +function install_rectangular_package() { + function real_part(z) { return head(z); } + function imag_part(z) { return tail(z); } + function make_from_real_imag(x, y) { return pair(x, y); } + function magnitude(z) { + return math_sqrt(square(real_part(z)) + + square(imag_part(z))); + } + function angle(z) { + return math_atan2(imag_part(z), real_part(z)); + } + function make_from_mag_ang(r, a) { + return pair(r * math_cos(a), r * math_sin(a)); + } + // interface to the rest of the system + function tag(x) { + return attach_tag("rectangular", x); + } + put("real_part", list("rectangular"), real_part); + put("imag_part", list("rectangular"), imag_part); + put("magnitude", list("rectangular"), magnitude); + put("angle", list("rectangular"), angle); + put("make_from_real_imag", "rectangular", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "rectangular", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} +install_rectangular_package(); + +function install_polar_package() { + // internal functions + function magnitude(z) { return head(z); } + function angle(z) { return tail(z); } + function make_from_mag_ang(r, a) { return pair(r, a); } + function real_part(z) { + return magnitude(z) * math_cos(angle(z)); + } + function imag_part(z) { + return magnitude(z) * math_sin(angle(z)); + } + function make_from_real_imag(x, y) { + return pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x)); + } + + // interface to the rest of the system + function tag(x) { return attach_tag("polar", x); } + put("real_part", list("polar"), real_part); + put("imag_part", list("polar"), imag_part); + put("magnitude", list("polar"), magnitude); + put("angle", list("polar"), angle); + put("make_from_real_imag", "polar", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "polar", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} +install_polar_package(); + +function install_complex_package() { + // imported functions from rectangular and polar packages + function make_from_real_imag(x, y) { + return get("make_from_real_imag", "rectangular")(x, y); + } + function make_from_mag_ang(r, a) { + return get("make_from_mag_ang", "polar")(r, a); + } + + // internal functions + function add_complex(z1, z2) { + return make_from_real_imag(real_part(z1) + + real_part(z2), + imag_part(z1) + + imag_part(z2)); + } + function sub_complex(z1, z2) { + return make_from_real_imag(real_part(z1) - + real_part(z2), + imag_part(z1) - + imag_part(z2)); + } + function mul_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) * + magnitude(z2), + angle(z1) + + angle(z2)); + } + function div_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) / + magnitude(z2), + angle(z1) - + angle(z2)); + } + + // interface to rest of the system + function tag(z) { + return attach_tag("complex", z); + } + put("add", list("complex", "complex"), + (z1, z2) => tag(add_complex(z1, z2))); + put("sub", list("complex", "complex"), + (z1, z2) => tag(sub_complex(z1, z2))); + put("mul", list("complex", "complex"), + (z1, z2) => tag(mul_complex(z1, z2))); + put("div", list("complex", "complex"), + (z1, z2) => tag(div_complex(z1, z2))); + put("make_from_real_imag", "complex", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "complex", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} +install_complex_package(); + +function make_complex_from_real_imag(x, y){ + return get("make_from_real_imag", "complex")(x, y); +} +function make_complex_from_mag_ang(r, a){ + return get("make_from_mag_ang", "complex")(r, a); +} + + + + coercion_support + +// coercion support + +let coercion_list = null; + +function clear_coercion_list() { + coercion_list = null; +} + +function put_coercion(type1, type2, item) { + if (is_undefined(get_coercion(type1, type2))) { + coercion_list = pair(list(type1, type2, item), + coercion_list); + } else { + return coercion_list; + } +} + +function get_coercion(type1, type2) { + function get_type1(list_item) { + return head(list_item); + } + function get_type2(list_item) { + return head(tail(list_item)); + } + function get_item(list_item) { + return head(tail(tail(list_item))); + } + function get_coercion_iter(items) { + if (is_null(items)) { + return undefined; + } else { + const top = head(items); + return equal(type1, get_type1(top)) && + equal(type2, get_type2(top)) + ? get_item(top) + : get_coercion_iter(tail(items)); + } + } + return get_coercion_iter(coercion_list); +} + + + + apply_generic_with_coercion_example + base_operation_table + javascript_number_package + complex_number_package + coercion_support + +function javascript_number_to_complex(n) { + return make_complex_from_real_imag(contents(n), 0); +} + +put_coercion("javascript_number", "complex", + javascript_number_to_complex); + +const c = make_complex_from_real_imag(4, 3); +const n = make_javascript_number(7); + +add(c, n); + + + + apply_generic_with_coercion + apply_generic_with_coercion_example + [ 'complex', [ 'rectangular', [ 11, 3 ] ] ] + +(define (apply-generic op . args) + (let ((type-tags (map type-tag args))) + (let ((proc (get op type-tags))) + (if proc + (apply proc (map contents args)) + (if (= (length args) 2) + (let ((type1 (car type-tags)) + (type2 (cadr type-tags)) + (a1 (car args)) + (a2 (cadr args))) + (let ((t1->t2 (get-coercion type1 type2)) + (t2->t1 (get-coercion type2 type1))) + (cond (t1->t2 + (apply-generic op (t1->t2 a1) a2)) + (t2->t1 + (apply-generic op a1 (t2->t1 a2))) + (else + (error "No method for these types" + (list op type-tags)))))) + (error "No method for these types" + (list op type-tags))))))) + + +function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + if (! is_undefined(fun)) { + return apply(fun, map(contents, args)); + } else { + if (length(args) === 2) { + const type1 = head(type_tags); + const type2 = head(tail(type_tags)); + const a1 = head(args); + const a2 = head(tail(args)); + const t1_to_t2 = get_coercion(type1, type2); + const t2_to_t1 = get_coercion(type2, type1); + return ! is_undefined(t1_to_t2) + ? apply_generic(op, list(t1_to_t2(a1), a2)) + : ! is_undefined(t2_to_t1) + ? apply_generic(op, list(a1, t2_to_t1(a2))) + : error(list(op, type_tags), + "no method for these types"); + } else { + return error(list(op, type_tags), + "no method for these types"); + } + } +} + + + + 这种强制类型转换方案比定义显式跨类型操作的方法有许多优点,如上所述。虽然我们仍然需要编写强制类型转换 过程 函数 来关联这些类型(可能 【46:0†cn.txt】 $n^2$ + 过程 函数 用于一个系统 $n$ 类型),我们只需要为每对类型编写一个 过程 函数 ,而不是为每个类型集合和每个通用操作编写一个不同的 过程 函数 如果我们足够聪明,通常可以用少于 $n^2$ 的强制类型转换 过程。 函数。 例如,如果我们知道如何从类型1转换到类型2,再从类型2转换到类型3,那么我们可以利用这些知识从类型1转换到类型3。这可以极大减少我们在系统中新增类型时需要显式提供的强制类型转换 过程 函数 的数量。如果我们愿意在系统中构建所需的复杂性,我们可以让系统搜索类型之间关系的,并自动生成那些能从显式提供的强制类型转换 过程 函数 推导出来的过程。我们依赖的是类型之间适当的变换仅依赖于类型本身,而不依赖于将要应用的操作。 + + 另一方面,对于某些应用,我们的强制类型转换方案可能不够通用。即使要组合的对象都无法转换为对方的类型,通过将这两个对象转换为第三种类型,仍然可能进行该操作。为了处理这种复杂性,同时保持程序的模块性,通常有必要构建利用类型间关系中更深层结构的系统,正如我们接下来将讨论的那样。 + + 类型层次结构 + + 类型层次结构 + 类型层次结构 + 上述强制类型转换方案依赖于类型对之间自然关系的存在。通常,不同类型之间的关系具有更“全局”的结构。例如,假设我们正在构建一个通用算术系统,以处理整数、有理数、实数和复数。在这样的系统中,将整数视为一种特殊的有理数,再将其视为一种特殊的实数,最终视为一种特殊的复数是非常自然的。实际上,我们拥有一个所谓的类型层次结构,例如,整数是有理数的 子类型 类型子类型 子类型(即任何可以应用于有理数的操作都可以自动应用于整数)。反过来,我们说有理数形成整数的 超类型 类型超类型 超类型。我们这里的特定层次结构是非常简单的,其中每种类型最多只有一个超类型和一个子类型。这种结构称为,在图中有所说明。
类型塔。 类型塔 类型
+ 如果我们有一个塔结构,那么我们可以极大地简化向层次结构中添加新类型的问题,因为我们只需要指定新类型如何嵌入在其上方的下一个超类型中,以及它如何成为其下方类型的超类型。例如,如果我们想将一个整数添加到一个复数中,我们不需要显式定义一个特殊的强制类型转换 过程 函数 integer->complex. integer_to_complex. 相反,我们定义一个整数可以如何转换为有理数,有理数如何转换为实数,以及实数如何转换为复数。然后我们允许系统通过这几个步骤将整数转换为复数并然后将两个复数相加。 + 类型提升 apply_generic带类型塔 我们可以通过以下方式重新设计我们的 apply-generic apply_generic 过程 函数 :对于每种类型,我们需要提供一个 raise 过程, 函数, 该函数将该类型的对象“提升”一个层次到塔中。然后,当系统需要对不同类型的对象进行操作时,它可以逐步提升较低的类型,直到所有对象都位于塔中的相同层次。(练习 涉及实现这种策略的详细信息。) + 另一个塔结构的优点是我们可以轻松实现每种类型都“继承”定义在其超类型上的所有操作的概念。例如,如果我们没有提供一个用于查找整数实部的特殊 过程 函数 ,我们仍然可以期望 real-part real_part 会因为整数是复数的子类型而定义在整数上。在塔中,我们可以通过修改 apply-generic. apply_generic. 来以统一的方式实现这一点。如果所需的操作未直接针对给定对象的类型定义,我们会将对象提升到其超类型并再次尝试。因此,我们沿着塔不断向上爬,随着前进而转换我们的参数,直到我们找到可以执行所需操作的层次,或到达顶层(在这种情况下,我们放弃)。 + 类型降低 底层结构的另一个优点是,它为我们提供了一种简单的方法来将数据对象“降为”最简单的表示。例如,如果我们将 $2+3i$ 加到 $4-3i$,得出结果为整数6而不是复数 $6+0i$会很好。练习讨论了实现这种降低操作的方法。(诀窍在于我们需要一种通用的方法来区分那些可以被降低的对象,比如 $6+0i$,与那些不能被降低的对象,比如 $6+2i$。) + + 层次结构的不足 + + 类型层次结构的不足 + 如果我们系统中的数据类型可以自然地安排成一个塔结构,那么如我们所见,这将极大地简化处理不同类型上的通用操作的问题。不幸的是,通常情况并非如此。图展示了一种更复杂的混合类型排列,显示了不同几何图形类型之间的关系。我们看到,通常来说, 类型多重子类型和超类型 超类型多个 子类型多个 一个类型可以有多个子类型。例如,三角形和四边形都是多边形的子类型。此外,一个类型可以有多个超类型。例如,一个等腰直角三角形可以被视为等腰三角形或直角三角形。这种多重超类型的问题尤其棘手,因为这意味着在层次结构中没有唯一的方法将一个类型“提升”。为对象找到应用操作的“正确”超类型可能需要大量的搜索, 过程 函数 例如 apply-generic. apply_generic. 由于通常一个类型有多个子类型,因此在将一个值“降低”到类型层次结构时也存在类似的问题。处理大量相互关联的类型同时在大型系统的设计中保持模块性是非常困难的,并且这是当前研究的一个热点领域。 这段话出现在该书的第一版中,现在仍然适用,就像我们十二年前编写时一样。开发一个用于表达不同实体类型之间关系的有用且通用的框架(哲学家称之为“本体论”)似乎难以解决。当前与十年前的混乱之间的主要区别在于,现在各种不完善的本体论理论已经被植入相应不完善的编程语言中。例如,面向对象编程语言的复杂性——以及当代面向对象语言之间微妙且混乱的差异——主要集中在处理相互关联类型上的通用操作。我们在第章中对计算对象的讨论完全避免了这些问题。熟悉面向对象编程的读者会注意到我们在第章中对局部状态有很多讨论,但我们甚至没有提到“类”或“继承”。事实上,我们怀疑这些问题不能仅通过计算机语言设计来充分解决,而不借助知识表示和自动推理方面的工作。 +
几何图形类型之间的关系。
+ + + apply_generic带强制转换 Louis Reasoner 注意到 apply-generic apply_generic 可能尝试将参数强制转换为彼此的类型,即使它们已经具有相同的类型。因此,他推测,我们需要在强制转换表中放置 过程 函数 以将每种类型的参数“强制转换”到其自身的类型。例如,除了上面展示的 scheme-number->complex javascript_number_to_complex 强制转换外,他还会这样做: + javascript_number_to_javascript_ number + complex_to_complex + +(define (scheme-number->scheme-number n) n) +(define (complex->complex z) z) +(put-coercion 'scheme-number 'scheme-number + scheme-number->scheme-number) +(put-coercion 'complex 'complex complex->complex) + + +function javascript_number_to_javascript_number(n) { return n; } + +function complex_to_complex(n) { return n; } + +put_coercion("javascript_number", "javascript_number", + javascript_number_to_javascript_number); +put_coercion("complex", "complex", complex_to_complex); + + + +
    +
  1. 随着 Louis 的强制类型转换 过程 函数 安装,如果 apply-generic apply_generic 被调用且带有两个类型为 scheme-number "complex" 或两个参数为类型的 complex "javascript_@number" 且在这些类型的表中找不到操作,会发生什么?例如,假设我们已经定义了一个通用的指数运算: + apply_generic + +(define (exp x y) (apply-generic 'exp x y)) + + +function exp(x, y) { + return apply_generic("exp", list(x, y)); +} + + 并在 Scheme-number JavaScript-number 包中但不在任何其他包中放入一个用于求幂的 过程 函数 + +;; following added to Scheme-number package +(put 'exp '(scheme-number scheme-number) + (lambda (x y) (tag (expt x y)))) ; using primitive expt + + +// following added to JavaScript-number package +put("exp", list("javascript_number", "javascript_number"), + (x, y) => tag(math_exp(x, y))); // using primitive $\texttt{math\char`_exp}$ + + 如果我们调用会发生什么 exp 以两个复数作为参数会怎样?
  2. +
  3. Louis 是否正确地认为针对相同类型参数的强制转换需要进行处理,或者 apply-generic apply_generic 本身是否工作正常?
  4. 修改 apply-generic apply_generic 使得在两个参数具有相同类型时不尝试进行强制转换。
+ +
    +
  1. 如果 Louis 将强制类型转换函数放入操作表中, apply_generic 将进入一个无限循环
  2. Louis 的代码不起作用。 apply_generic 按原样正确工作。但我们可以修改它,使其在尝试任何相同类型的强制转换之前退出并报错。
  3. + + apply_generic_with_unavailable_type_example + base_operation_table + complex_number_package + coercion_support + + +function install_javascript_number_package() { + function tag(x) { + return attach_tag("javascript_number", x); + } + put("add", list("javascript_number", "javascript_number"), + (x, y) => tag(x + y)); + put("sub", list("javascript_number", "javascript_number"), + (x, y) => tag(x - y)); + put("mul", list("javascript_number", "javascript_number"), + (x, y) => tag(x * y)); + put("div", list("javascript_number", "javascript_number"), + (x, y) => tag(x / y)); + put("exp", list("javascript_number", "javascript_number"), + (x, y) => tag(math_exp(x, y))); + put("make", "javascript_number", + x => tag(x)); + return "done"; +} +install_javascript_number_package(); + +function make_javascript_number(n) { + return get("make", "javascript_number")(n); +} + +function javascript_number_to_javascript_number(n) { + return n; +} +function complex_to_complex(n) { + return n; +} +put_coercion("javascript_number", "javascript_number", + javascript_number_to_javascript_number); +put_coercion("complex", "complex", + complex_to_complex); +function exp(x, y) { + return apply_generic("exp", list(x, y)); +} + +const c = make_javascript_number(4); +const d = make_javascript_number(2); +exp(c, d); + + + + + + apply_generic_with_unavailable_type + apply_generic_with_unavailable_type_example + + + +function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + if (! is_undefined(fun)) { + return apply(fun, map(contents, args)); + } else { + if (length(args) === 2) { + const type1 = head(type_tags); + const type2 = head(tail(type_tags)); + const a1 = head(args); + const a2 = head(tail(args)); + const t1_to_t2 = get_coercion(type1, type2); + const t2_to_t1 = get_coercion(type2, type1); + return type1 === type2 + ? error(list(op, type_tags), + "no method for these types") + : ! is_undefined(t1_to_t2) + ? apply_generic(op, list(t1_to_t2(a1), a2)) + : ! is_undefined(t2_to_t1) + ? apply_generic(op, list(a1, t2_to_t1(a2))) + : error(list(op, type_tags), + "no method for these types"); + } else { + return error(list(op, type_tags), + "no method for these types"); + } + } +} + + +
  4. +
+
+ +
+ + + apply_generic对多参数进行强制转换 展示如何将 apply-generic apply_generic 泛化为处理多参数的强制转换。一种策略是尝试将所有参数强制转换为第一个参数的类型,然后是第二个参数的类型,以此类推。举一个此策略(以及上述的双参数版本)不够通用的例子。(提示:考虑在表中存在一些适合的混合类型操作但不会被尝试的情况。) + + + multi_coercion_example + base_operation_table + javascript_number_package + complex_number_package + coercion_support + +function javascript_number_to_complex(n) { + return make_complex_from_real_imag(contents(n), 0); +} + +put_coercion("javascript_number", "complex", + javascript_number_to_complex); + +put("add", list("complex", "complex", "complex"), + (x, y, z) => attach_tag("complex", make_complex_from_real_imag( + real_part(x) + real_part(y) + real_part(z), + imag_part(x) + imag_part(y) + imag_part(z)))); + +function add_three(x, y, z) { + return apply_generic("add", list(x, y, z)); +} + +const c = make_complex_from_real_imag(4, 3); +const n = make_javascript_number(7); +add_three(c, c, n); +// add_three(c, n, n); + + + + + + multi_coercion + multi_coercion_example + +function can_coerce_to(type_tags, target_type) { + return accumulate((type_tag, result) => + result && + (type_tag === target_type || + ! is_undefined(get_coercion(type_tag, target_type))), + true, + type_tags); +} + +function find_coerced_type(type_tags) { + return is_null(type_tags) + ? undefined + : can_coerce_to(type_tags, head(type_tags)) + ? head(type_tags) + : find_coerced_type(tail(type_tags)); +} + +function coerce_all(args, target_type) { + return map(arg => type_tag(arg) === target_type + ? arg + : get_coercion(type_tag(arg), target_type)(arg), + args); +} + +function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + if (! is_undefined(fun)) { + return apply(fun, map(contents, args)); + } else { + const target_type = find_coerced_type(type_tags); + if (! is_undefined(target_type)) { + return apply_generic(op, coerce_all(args, target_type)); + } else { + return error(list(op, type_tags), + "no method for these types"); + } + } +} + + 一种情况下这种方法是不够的:如果你有三种类型,A、B、C,其中A可以强制转换为B并且C可以强制转换为B,并且有一个注册的操作为(A, B, B)。对于(A, B, C)的运算只会尝试(A, B, C)和(B, B, B),但你可以简单地将C强制转换为B,并使用(A, B, B)的注册操作。 + + + + 类型提升 假设你正在设计一个处理图中显示的类型塔的通用算术系统:整数、有理数、实数、复数。对于每种类型(除了复数),设计一个 过程 函数 将该类型的对象提升一个层次到塔中。展示如何安装一个通用【136:3†cn.txt】 raise 操作,该操作将适用于每一种类型(除了复数)。 + + + rational_number_package + +function gcd(a, b) { + return b === 0 ? a : gcd(b, a % b); +} + +function install_rational_package() { + // internal functions + function numer(x) { + return head(x); + } + function denom(x) { + return tail(x); + } + function make_rat(n, d) { + let g = gcd(n, d); + return pair(n / g, d / g); + } + function add_rat(x, y) { + return make_rat(numer(x) * denom(y) + + numer(y) * denom(x), + denom(x) * denom(y)); + } + function sub_rat(x, y) { + return make_rat(numer(x) * denom(y) - + numer(y) * denom(x), + denom(x) * denom(y)); + } + function mul_rat(x, y) { + return make_rat(numer(x) * numer(y), + denom(x) * denom(y)); + } + function div_rat(x, y) { + return make_rat(numer(x) * denom(y), + denom(x) * numer(y)); + } + // interface to rest of the system + function tag(x) { + return attach_tag("rational", x); + } + put("add", list("rational", "rational"), + (x, y) => tag(add_rat(x, y))); + put("sub", list("rational", "rational"), + (x, y) => tag(sub_rat(x, y))); + put("mul", list("rational", "rational"), + (x, y) => tag(mul_rat(x, y))); + put("div", list("rational", "rational"), + (x, y) => tag(div_rat(x, y))); + put("make", "rational", + (n, d) => tag(make_rat(n, d))); +} +install_rational_package(); + +function make_rational(n, d) { + return get("make", "rational")(n, d); +} + + + + + javascript_number_package + rational_number_package + complex_number_package + coercion_support + apply_generic_with_coercion + raise_example + +const a = make_javascript_number(2); +const b = make_rational(2, 3); +raise(a); +// raise(b); + + + + + raise + raise_example + +function raise(x) { + return apply_generic("raise", list(x)); +} + +// add to Javascript-number package +put("raise", list("javascript_number"), + x => make_rational(x, 1)); +//// add to rational package +// put("raise", list("rational"), +// x => make_real(div, numer(x), denom(x))); +// put("raise", list("rational"), +// x => make_complex_from_real_imag(x, 0)); +//// add to real package +// put("raise", list("real"), +// x => make_complex_from_real_imag(x, 0)); + + + + + + apply_generic通过提升进行强制转换 使用练习的raise操作,修改 apply-generic apply_generic 过程 函数 以便通过本节讨论的逐层提升方法,将其参数强制转换为相同类型。您需要设计一种方法来测试两个类型中哪个在塔中更高。按照与系统其他部分“兼容”的方式做到这一点,并不会导致向塔中添加新层级时出现问题。 + + + apply_generic通过下降进行简化 类型降低 本节提到了通过尽可能低地降低数据对象到类型塔中以进行“简化”的方法。设计一个 过程 函数 drop 实现练习中描述的塔的功能。关键是要以某种通用方式判断一个对象是否可以降低。例如,复数 $1.5+0i$ 可以被降低到 real, "real", 复数 $1+0i$ 可以被降低到 integer, "integer", 和复数 $2+3i$ 无法降低。以下是确定对象是否可以降低的计划:首先定义一个通用操作 project 通过降低塔中的对象来“推”它。例如,投影一个复数将涉及丢弃虚部。然后,如果当我们 【158:10†cn.txt】 project 它和 raise 结果返回到我们开始的类型,我们最终得到的等于我们开始的。通过编写一个详细地展示如何实现这个想法的 drop + 过程 函数 将一个对象尽可能地降低。您将需要设计各种投影操作可以使用 math_round (原语函数) math_roundMath.round round math_round 原语将实数投影到整数,该函数返回最接近其参数的整数。 并安装 project 作为系统中的一种通用操作。您还需使用通用等式谓词,例如在练习中描述的。最后,使用 drop 重写练习中的 apply-generic apply_generic ,使其“简化”其答案。 + + + 假设我们希望处理复数,它们的实部、虚部、幅度和角度可以是普通数、有理数或我们可能希望添加到系统中的其他数字。描述并实现为适应此情况系统所需的更改。您将不得不定义一些操作如 sine 和 cosine,这些操作是普通数和有理数的通用操作。 + + 强制类型转换 + 类型层次结构 + 类型层次结构 + +
diff --git a/xml/cn/chapter2/section5/subsection3.xml b/xml/cn/chapter2/section5/subsection3.xml new file mode 100644 index 000000000..17009dd15 --- /dev/null +++ b/xml/cn/chapter2/section5/subsection3.xml @@ -0,0 +1,1914 @@ + + + Example: 符号代数 + + + + 符号代数 + + 符号代数表达式的操控是一个复杂的过程,揭示了在设计大型系统时出现的许多难题。一般来说,代数表达式可视为一个分层结构,即运算符作用于操作数的树形结构。通过从一组原始对象(如常数与变量)出发,并用代数运算符(如加法与乘法)将其组合,我们可以构建代数表达式。如同其他语言,我们形成抽象以便用简单的术语指代复合对象。符号代数中典型的抽象包括线性组合、多项式、有理函数或三角函数等思想。我们可以将这些视为复合类型,它们通常用于引导表达式的处理。例如,我们可以描述表达式 + + + \[ x^{2}\, \sin (y^2+1)+x\, \cos 2y+\cos (y^3 -2y^2) \] + + 为一个关于 $x$ 的多项式,其系数为 $y$ 中多项式的三角函数,而系数为整数。 + + + 我们在此不尝试开发一个完整的代数操控系统。这样的系统是极其复杂的程序,包含深奥的代数知识和优雅的算法。我们将要做的是研究代数操控的一个简单但重要的部分:多项式算术。我们将展示这样的系统设计者所面临的各种决策,以及如何应用抽象数据和通用操作的理念来帮助组织这一工作。 + + + + 多项式算术 + + + polynomial(s) + polynomial arithmetic + + Our first task in designing a system for performing arithmetic on + polynomials is to decide just what a polynomial is. Polynomials are + normally defined relative to certain variables (the + indeterminate of a polynomial + polynomial(s)indeterminate of + indeterminates of the polynomial). For simplicity, we will + restrict ourselves to polynomials having just one indeterminate + univariate polynomial + polynomial(s)univariate + (univariate polynomials).On the other hand, we will + allow polynomials whose coefficients are themselves polynomials in other + variables. This will give us essentially the same representational + power as a full multivariate system, although it does lead to coercion + problems, as discussed below. We will define a polynomial to + be a sum of terms, each of which is either a coefficient, a power of the + indeterminate, or a product of a coefficient and a power of the + indeterminate. A coefficient is defined as an algebraic expression + that is not dependent upon the indeterminate of the polynomial. For + example, + + \[ 5x^2 +3x +7 \] + + is a simple polynomial in $x$, and + + \[ (y^2 +1)x^3 +(2y)x+1 \] + + is a polynomial in $x$ whose coefficients are + polynomials in $y$. + + + Already we are skirting some thorny issues. Is the first of these + polynomials the same as the polynomial + $5y^2 +3y +7$, or not? A reasonable answer + might be yes, if we are considering a polynomial purely as a + mathematical function, but no, if we are considering a polynomial to be a + syntactic form. The second polynomial is algebraically equivalent + to a polynomial in $y$ whose coefficients are + polynomials in $x$. Should our system recognize + this, or not? Furthermore, there are other ways to represent a + polynomialfor example, as a product of factors, or (for a + univariate polynomial) as the set of roots, or as a listing of the values + of the polynomial at a specified set of points.For univariate + polynomials, giving the value of a polynomial at a given set of points can + be a particularly good representation. This makes polynomial arithmetic + extremely simple. To obtain, for example, the sum of two polynomials + represented in this way, we need only add the values of the + polynomials at corresponding points. To transform back to a more + familiar representation, we can use the + Lagrange interpolation formula + Lagrange interpolation formula, which shows how to recover the coefficients + of a polynomial of degree $n$ given the values + of the polynomial at $n+1$ points. + We can finesse these questions by deciding that in our + algebraic-manipulation system a polynomial will be a + particular syntactic form, not its underlying mathematical meaning. + + + Now we must consider how to go about doing arithmetic on polynomials. + In this simple system, we will consider only addition and + multiplication. Moreover, we will insist that two polynomials to be + combined must have the same indeterminate. + + + We will approach the design of our system by following the familiar + discipline of data abstraction. We will represent polynomials using a + data structure called a + poly + poly, which consists of a variable and a + term list of polynomial + collection of terms. We assume that we have selectors + variable and + + term-list + term_list + + that extract those parts from a poly and a constructor + + make-poly + make_poly + + that assembles a poly from a given variable and a term list. + A variable will be just a + + + symbol, + + + string, + + + so we can use the + is_same_variable + + same-variable? + is_same_variable + + + + procedure + function + + of section to compare + variables. + The following + + procedures + functions + + define + polynomial arithmeticaddition + polynomial arithmeticmultiplication + addition and multiplication of polys: + + add_poly + mul_poly + add_mul_poly + install_javascript_number_package_usage + make_polynomial_requires + make_polynomial + make_polynomial_example + +(define (add-poly p1 p2) + (if (same-variable? (variable p1) (variable p2)) + (make-poly (variable p1) + (add-terms (term-list p1) + (term-list p2))) + (error "Polys not in same var - - ADD-POLY" + (list p1 p2)))) + +(define (mul-poly p1 p2) + (if (same-variable? (variable p1) (variable p2)) + (make-poly (variable p1) + (mul-terms (term-list p1) + (term-list p2))) + (error "Polys not in same var - - MUL-POLY" + (list p1 p2)))) + + + + +function add_poly(p1, p2) { + return is_same_variable(variable(p1), variable(p2)) + ? make_poly(variable(p1), + add_terms(term_list(p1), term_list(p2))) + : error(list(p1, p2), "polys not in same var -- add_poly"); +} +function mul_poly(p1, p2) { + return is_same_variable(variable(p1), variable(p2)) + ? make_poly(variable(p1), + mul_terms(term_list(p1), term_list(p2))) + : error(list(p1, p2), "polys not in same var -- mul_poly"); +} + + + + + To incorporate polynomials into our generic arithmetic system, we need + to supply them with type tags. Well use the tag + + polynomial, + "polynomial", + + + and install appropriate operations on tagged polynomials in the operation + table. + + + Well embed all our code in an installation procedure + for the polynomial package, similar to the ones in + section: + + install_polynomial_package_template_scheme + +(define (install-polynomial-package) + ;; internal procedures + ;; representation of poly + (define (make-poly variable term-list) + (cons variable term-list)) + (define (variable p) (car p)) + (define (term-list p) (cdr p)) + $langle$ procedures same-variable? and variable? from section 2.3.2 $\rangle$ + ;; representation of terms and term lists + $langle$ procedures adjoin-term $\ldots$ coeff from text below $langle$ + (define (add-poly p1 p2) $\ldots$) + $langle$ procedures used by add-poly $langle$ + (define (mul-poly p1 p2) $\ldots$) + $langle$procedures used by mul-poly $langle$ + ;; interface to rest of the system + (define (tag p) (attach-tag 'polynomial p)) + (put 'add '(polynomial polynomial) + (lambda (p1 p2) (tag (add-poly p1 p2)))) + (put 'mul '(polynomial polynomial) + (lambda (p1 p2) (tag (mul-poly p1 p2)))) + (put 'make 'polynomial + (lambda (var terms) (tag (make-poly var terms)))) + 'done) + + + + + Well embed all our code in an installation function + for the polynomial package, + similar to the installation functions in + section: + + packagepolynomial + polynomial package + polynomial arithmeticinterfaced to generic arithmetic system + install_polynomial_package + make_poly + variable + term_list + 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"; +} + + + + + + + Polynomial addition is performed termwise. Terms of the same order + (i.e., with the same power of the indeterminate) must be combined. + This is done by forming a new term of the same order whose coefficient + is the sum of the coefficients of the addends. Terms in one addend + for which there are no terms of the same order in the other addend are + simply accumulated into the sum polynomial being constructed. + + + In order to manipulate term lists, we will assume that we have a + constructor + the_empty_termlist + + the-empty-termlist + the_empty_termlist + + + that returns an empty term list and a constructor + adjoin_term + + adjoin-term + adjoin_@term + + that adjoins a new term to a term list. We will also assume that we have + a predicate + is_empty_termlist + + empty-termlist? + is_empty_termlist + + + that tells if a given term list is empty, a selector + first_term + + first-term + first_term + + that extracts the highest-order term from a term list, and a selector + rest_terms + + rest-terms + rest_terms + + that returns all but the highest-order term. To manipulate terms, + we will suppose that we have a constructor + make_term + + make-term + make_term + + that constructs a term with given order and coefficient, and selectors + order + order and + coeff + coeff that return, respectively, the order + and the coefficient of the term. These operations allow us to consider + both terms and term lists as data abstractions, whose concrete + representations we can worry about separately. + + + Here is the + + procedure + function + + that constructs the term list for the sum of two + polynomials:polynomials;This operation is very much like the ordered + + union-set + union_set + + operation we developed in exercise. + In fact, if we think of the terms of the polynomial as a set ordered + according to the power of the indeterminate, then the program that + produces the term list for a sum is almost identical to + + union-set. + union_set. + + + + note that we slightly extend the syntax of + conditional statementconditional instead of alternative block + conditional statements described in + section by admitting another conditional + statement in place of the block following + else: + + + + add_terms + add_terms + install_javascript_number_package_usage + make_polynomial_requires + make_polynomial + make_polynomial_example + + (define (add-terms L1 L2) + (cond ((empty-termlist? L1) L2) + ((empty-termlist? L2) L1) + (else + (let ((t1 (first-term L1)) (t2 (first-term L2))) + (cond ((> (order t1) (order t2)) + (adjoin-term + t1 (add-terms (rest-terms L1) L2))) + ((< (order t1) (order t2)) + (adjoin-term + t2 (add-terms L1 (rest-terms L2)))) + (else + (adjoin-term + (make-term (order t1) + (add (coeff t1) (coeff t2))) + (add-terms (rest-terms L1) + (rest-terms L2))))))))) + + + + +function add_terms(L1, L2) { + if (is_empty_termlist(L1)) { + return L2; + } else if (is_empty_termlist(L2)) { + return L1; + } else { + const t1 = first_term(L1); + const t2 = first_term(L2); + return order(t1) > order(t2) + ? adjoin_term(t1, add_terms(rest_terms(L1), L2)) + : order(t1) < order(t2) + ? adjoin_term(t2, add_terms(L1, rest_terms(L2))) + : adjoin_term(make_term(order(t1), + add(coeff(t1), coeff(t2))), + add_terms(rest_terms(L1), + rest_terms(L2))); + } +} + + + The most important point to note here is that we used the generic addition + + procedure + function + + add (generic)used for polynomial coefficients + add to add together the coefficients of the + terms being combined. This has powerful consequences, as we will see below. + + + + In order to multiply two term lists, we multiply each term of the first + list by all the terms of the other list, repeatedly using + + mul-term-by-all-terms, + mul_term_by_all_terms, + + + which multiplies a given term by all terms in a given term list. The + resulting term lists (one for each term of the first list) are accumulated + into a sum. Multiplying two terms forms a term whose order is the sum of + the orders of the factors and whose coefficient is the product of the + coefficients of the factors: + + mul_terms + mul_terms + install_javascript_number_package_usage + make_polynomial_requires + make_polynomial + make_polynomial_example + + (define (mul-terms L1 L2) + (if (empty-termlist? L1) + (the-empty-termlist) + (add-terms (mul-term-by-all-terms (first-term L1) L2) + (mul-terms (rest-terms L1) L2)))) + + (define (mul-term-by-all-terms t1 L) + (if (empty-termlist? L) + (the-empty-termlist) + (let ((t2 (first-term L))) + (adjoin-term + (make-term (+ (order t1) (order t2)) + (mul (coeff t1) (coeff t2))) + (mul-term-by-all-terms t1 (rest-terms L)))))) + + + + +function mul_terms(L1, L2) { + return is_empty_termlist(L1) + ? the_empty_termlist + : add_terms(mul_term_by_all_terms( + first_term(L1), L2), + mul_terms(rest_terms(L1), L2)); +} +function mul_term_by_all_terms(t1, L) { + if (is_empty_termlist(L)) { + return the_empty_termlist; + } else { + const t2 = first_term(L); + return adjoin_term( + make_term(order(t1) + order(t2), + mul(coeff(t1), coeff(t2))), + mul_term_by_all_terms(t1, rest_terms(L))); + } +} + + + + + This is really all there is to polynomial addition and multiplication. + Notice that, since we operate on terms using the generic + + procedures + functions + + add (generic)used for polynomial coefficients + mul (generic)used for polynomial coefficients + add and mul, + our polynomial package is automatically able to handle any type of + coefficient that is known about by the generic arithmetic package. + If we include a + coercionin polynomial arithmetic + coercion mechanism such as one of those discussed in + section, + then we also are automatically able to handle operations on + polynomials of different coefficient types, such as + + \[ + \begin{array}{l} + {\left[3x^2 +(2+3i)x+7\right] \cdot \left[x^4 +\frac{2}{3}x^2 + +(5+3i)\right]} + \end{array} + \] + + + + Because we installed the polynomial addition and multiplication + + procedures + functions + + + add-poly + add_@poly + + and + + mul-poly + mul_poly + + in the generic arithmetic system as the add + and mul operations for type + polynomial, our system is also automatically + able to handle polynomial operations such as + + \[ + \begin{array}{l} + {\left[ (y+1)x^2 +(y^2 +1)x+(y-1)\right]\cdot \left[(y-2)x+(y^3 +7)\right]} + \end{array} + \] + + The reason is that when the system tries to combine coefficients, it + will dispatch through add and + mul. Since the coefficients are themselves + polynomials (in $y$), these will be combined + using + + add-poly + add_poly + + and + + mul-poly. + mul_poly. + + The result is a kind of + data-directed recursion + recursiondata-directed + data-directed recursion in which, for example, a call to + + mul-poly + mul_poly + + will result in recursive calls to + + mul-poly + mul_poly + + in order to multiply the coefficients. If the coefficients of the + coefficients were themselves polynomials (as might be used to represent + polynomials in three variables), the data direction would ensure that the + system would follow through another level of recursive calls, and so on + through as many levels as the structure of the data dictates.To + make this work completely smoothly, we should also add to our generic + arithmetic system the ability to coerce a number to a + polynomial by regarding it as a polynomial of degree zero whose coefficient + is the number. This is necessary if we are going to perform operations + such as + + \[ + {\left[ x^2 +(y+1)x+5\right]+ \left[ x^2 +2x+1\right]} + \] + + which requires adding the coefficient $y+1$ to + the coefficient 2. + polynomial arithmeticaddition + polynomial arithmeticmultiplication + + + + Representing term lists + + + term list of polynomialrepresenting + + Finally, we must confront the job of implementing a good + representation for term lists. A term list is, in effect, a set of + coefficients keyed by the order of the term. Hence, any of the + methods for representing sets, as discussed in + section, can be applied to this + task. On the other hand, our + + procedures + functions + + + add-terms + add_terms + and + mul-terms + mul_terms + + always access term lists sequentially from highest to lowest order. + Thus, we will use some kind of ordered list representation. + + + How should we structure the list that represents a term list? One + consideration is the density of the polynomials we intend + to manipulate. A polynomial is said to be + dense polynomial + polynomial(s)dense + dense if it has nonzero coefficients in terms of most orders. + If it has many zero terms it is said to be + sparse polynomial + polynomial(s)sparse + sparse. For example, + + \[ A:\quad x^5 +2x^4 +3x^2 -2x -5 \] + + is a dense polynomial, whereas + + \[ B:\quad x^{100} +2x^2 +1 \] + + is sparse. + + + + + The term lists of dense polynomials are most efficiently represented + as lists of the coefficients. + + + The term list of a dense polynomial is most efficiently represented + as a list of the coefficients. + + + For example, + + + the polynomial + + + $A$ above would be nicely represented as + + (1 2 0 3 -2 -5). + list(1, 2, 0, 3, -2, -5). + + + The order of a term in this representation is the length of the sublist + beginning with that terms coefficient, decremented by 1.In + these polynomial examples, we assume that we have implemented the generic + arithmetic system using the type mechanism suggested in + exercise. Thus, coefficients + that are ordinary numbers will be represented as the numbers themselves + rather than as pairs whose + + car + head + + is the + + symbol scheme-number. + string + "javascript_number". + + + This would be a terrible representation for a sparse polynomial such as + $B$: There would be a giant list of zeros + punctuated by a few lonely nonzero terms. A more reasonable representation + of the term list of a sparse polynomial is as a list of the nonzero terms, + where each term is a list containing the order of the term and the + coefficient for that order. In such a scheme, polynomial + $B$ is efficiently represented as + + ((100 1) (2 2) (0 1)). + + list(list(100, 1), list(2, 2), list(0, 1)). + + + As most polynomial manipulations are performed on sparse polynomials, we + will use this method. We will assume that term lists are represented as + lists of terms, arranged from highest-order to lowest-order term. Once we + have made this decision, implementing the selectors and constructors for + terms and term lists is straightforward:Although we are assuming + that term lists are ordered, we have implemented + + adjoin-term + adjoin_term + + to simply + + + cons + the new term onto the existing term list. + + + adjoin the new term to the front of the existing term list. + + + We can get away with this so + long as we guarantee that the + + procedures + functions + + (such as + + + add-terms) + add_terms) + + that use + + adjoin-term + adjoin_term + + always call it with a higher-order term than appears in the list. If we + did not want to make such a guarantee, we could have implemented + + adjoin-term + adjoin_term + + to be similar to the + + adjoin-set + adjoin_set + + constructor for the ordered-list + representation of sets + (exercise). + + + adjoin_term + the_empty_termlist + first_term + rest_terms + is_empty_termlist + make_term + order + coeff + adjoin_term + install_javascript_number_package_usage + make_polynomial_requires + make_polynomial + make_polynomial_example + + (define (adjoin-term term term-list) + (if (=zero? (coeff term)) + term-list + (cons term term-list))) + + (define (the-empty-termlist) '()) + (define (first-term term-list) (car term-list)) + (define (rest-terms term-list) (cdr term-list)) + (define (empty-termlist? term-list) (null? term-list)) + + (define (make-term order coeff) (list order coeff)) + (define (order term) (car term)) + (define (coeff term) (cadr term)) + + + + +function adjoin_term(term, term_list) { + return is_equal_to_zero(coeff(term)) + ? term_list + : pair(term, term_list); +} + +const the_empty_termlist = null; + +function first_term(term_list) { return head(term_list); } + +function rest_terms(term_list) { return tail(term_list); } + +function is_empty_termlist(term_list) { return is_null(term_list); } + +function make_term(order, coeff) { return list(order, coeff); } + +function order(term) { return head(term); } + +function coeff(term) { return head(tail(term)); } + + + where + =zero? + is_equal_to_zero + + + is as defined in exercise. (See also + exercise below.) + + + Users of the polynomial package will create (tagged) polynomials by means + of the + + procedure: + function: + + + make_polynomial_requires + is_same_variable + +function install_javascript_number_is_equal_to_zero() { + put("is_equal_to_zero", list("javascript_number"), + x => x === 0); + return "done"; +} +install_javascript_number_is_equal_to_zero(); + +function is_equal_to_zero(x) { + return apply_generic("is_equal_to_zero", list(x)); +} + +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); } + + // representation of terms and term lists + function adjoin_term(term, term_list) { + return is_equal_to_zero(coeff(term)) + ? term_list + : pair(term, term_list); + } + const the_empty_termlist = null; + function first_term(term_list) { + return head(term_list); + } + function rest_terms(term_list) { + return tail(term_list); + } + function is_empty_termlist(term_list) { + return is_null(term_list); + } + function make_term(order, coeff) { + return list(order, coeff); + } + function order(term) { + return head(term); + } + function coeff(term) { + return head(tail(term)); + } + + function add_poly(p1, p2) { + return is_same_variable(variable(p1), variable(p2)) + ? make_poly(variable(p1), + add_terms(term_list(p1), + term_list(p2))) + : error(list(p1, p2), + "polys not in same var -- add_poly"); + } + + function add_terms(L1, L2) { + if (is_empty_termlist(L1)) { + return L2; + } + else if (is_empty_termlist(L2)) { + return L1; + } + else { + const t1 = first_term(L1); + const t2 = first_term(L2); + return order(t1) > order(t2) + ? adjoin_term(t1, add_terms(rest_terms(L1), L2)) + : order(t1) < order(t2) + ? adjoin_term(t2, add_terms(L1, rest_terms(L2))) + : adjoin_term(make_term(order(t1), + add(coeff(t1), + coeff(t2))), + add_terms(rest_terms(L1), + rest_terms(L2))); + } + } + + function mul_poly(p1, p2) { + return is_same_variable(variable(p1), variable(p2)) + ? make_poly(variable(p1), + mul_terms(term_list(p1), + term_list(p2))) + : error(list(p1, p2), + "polys not in same var -- mul_poly"); + } + + function mul_terms(L1, L2) { + return is_empty_termlist(L1) + ? the_empty_termlist + : add_terms(mul_term_by_all_terms( + first_term(L1), L2), + mul_terms(rest_terms(L1), L2)); + } + function mul_term_by_all_terms(t1, L) { + if (is_empty_termlist(L)) { + return the_empty_termlist; + } else { + const t2 = first_term(L); + return adjoin_term( + make_term(order(t1) + order(t2), + mul(coeff(t1), coeff(t2))), + mul_term_by_all_terms(t1, rest_terms(L))); + } + } + + // 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"; +} +install_polynomial_package(); + + + + make_polynomial + make_polynomial + install_javascript_number_package_usage + make_polynomial_requires + make_polynomial_example + [ 3, [ [ 'javascript_number', 23 ], null ] ] + + (define (make-polynomial var terms) + ((get 'make 'polynomial) var terms)) + + +function make_polynomial(variable, terms) { + return get("make", "polynomial")(variable, terms); +} + + + + make_polynomial_example + +const p1 = make_polynomial("x", + list(make_term(2, make_javascript_number(4)), + make_term(1, make_javascript_number(3)), + make_term(0, make_javascript_number(7)))); +const p2 = make_polynomial("x", + list(make_term(2, make_javascript_number(5)), + make_term(1, make_javascript_number(2)), + make_term(0, make_javascript_number(10)))); + +mul(p1, p2); + + +const p1 = make_polynomial("x", + list(list(2, make_javascript_number(4)), + list(1, make_javascript_number(3)), + list(0, make_javascript_number(7)))); +const p2 = make_polynomial("x", + list(list(2, make_javascript_number(5)), + list(1, make_javascript_number(2)), + list(0, make_javascript_number(10)))); + +head(tail(tail(tail(mul(p1, p2))))); + + + + + Install + is_equal_to_zero (generic)for polynomials + zero test (generic)for polynomials + + =zero? + is_equal_to_zero + + + for polynomials in the generic arithmetic package. This will allow + + adjoin-term + adjoin_term + + to work for polynomials with coefficients that are themselves polynomials. + + + + + Extend the polynomial system to include + polynomial arithmeticsubtraction + subtraction of polynomials. + (Hint: You may find it helpful to define a generic negation operation.) + + + + ex_2_88_solution + +function sub_terms(L1, L2) { + if (is_empty_termlist(L1)) { + return L2; + } else if (is_empty_termlist(L2)) { + return L1; + } else { + const t1 = first_term(L1); + const t2 = first_term(L2); + return order(t1) > order(t2) + ? adjoin_term(t1, sub_terms(rest_terms(L1), L2)) + : order(t1) < order(t2) + ? adjoin_term(t2, sub_terms(L1, rest_terms(L2))) + : adjoin_term(make_term(order(t1), + sub(coeff(t1), coeff(t2))), + sub_terms(rest_terms(L1), + rest_terms(L2))); + } +} +function sub_poly(p1, p2) { + return is_same_variable(variable(p1), variable(p2)) + ? make_poly(variable(p1), + sub_terms(term_list(p1), term_list(p2))) + : error(list(p1, p2), "polys not in same var -- sub_poly"); +} + + + + + + + + + Define procedures + Declare functions + + that implement the term-list representation described above as + appropriate for dense polynomials. + + + + + Suppose we want to have a polynomial system that is efficient for both + sparse and dense polynomials. One way to do this is to allow both + kinds of term-list representations in our system. The situation is + analogous to the complex-number example of + section, where we allowed both + rectangular and polar representations. To do this we must distinguish + different types of term lists and make the operations on term lists + generic. Redesign the polynomial system to implement this generalization. + This is a major effort, not a local change. + + + term list of polynomialrepresenting + + + + A univariate polynomial can be divided by another one to produce a + polynomial arithmeticdivision + polynomial quotient and a polynomial remainder. For example, + + \[ + \begin{array}{lll} + \dfrac{x^5-1}{x^2 -1} & = & x^3 +x,\ \text{remainder }x-1 + \end{array} + \] + + Division can be performed via long division. + That is, divide the highest-order term of the dividend by + the highest-order term of the divisor. The result is the first term of the + quotient. Next, multiply the result by the divisor, subtract that + from the dividend, and produce the rest of the answer by recursively + dividing the difference by the divisor. Stop when the order of the + divisor exceeds the order of the dividend and declare the dividend to + be the remainder. Also, if the dividend ever becomes zero, return + zero as both quotient and remainder. +

+ We can design a + div_poly + + div-poly + div_poly + + + procedure + function + + on the model of + + add-poly + add_poly + + and + + mul-poly. + mul_poly. + + The + + procedure + function + + checks to see if the two polys have the same variable. If so, + + div-poly + div_poly + + strips off the variable and passes the problem to + + div-terms, + div_terms, + + which performs the division operation on term lists. + + Div-poly + + The function + div_poly + + + finally reattaches the variable to the result supplied by + + div-terms. + div_terms. + + It is convenient to design + + div-terms + div_terms + + to compute both the quotient and the remainder of a division. + + div-terms + + The function div_terms + + + can take two term lists as arguments and return a list of the quotient + term list and the remainder term list. +

+ Complete the following definition of + + div-terms + div_terms + + by filling in the missing + + + expressions. + + + parts. + + + Use this to implement + + div-poly, + div_poly, + + which takes two polys as arguments and returns a list of the quotient and + remainder polys. + + div_terms + +(define (div-terms L1 L2) + (if (empty-termlist? L1) + (list (the-empty-termlist) (the-empty-termlist)) + (let ((t1 (first-term L1)) + (t2 (first-term L2))) + (if (> (order t2) (order t1)) + (list (the-empty-termlist) L1) + (let ((new-c (div (coeff t1) (coeff t2))) + (new-o (- (order t1) (order t2)))) + (let ((rest-of-result + compute rest of result recursively + )) + form complete result + )))))) + + +function div_terms(L1, L2) { + if (is_empty_termlist(L1)) { + return list(the_empty_termlist, the_empty_termlist); + } else { + const t1 = first_term(L1); + const t2 = first_term(L2); + if (order(t2) > order(t1)) { + return list(the_empty_termlist, L1); + } else { + const new_c = div(coeff(t1), coeff(t2)); + const new_o = order(t1) - order(t2); + const rest_of_result = compute rest of result recursively; + form and return complete result + } + } +} + + +
+ + + Hierarchies of types in symbolic algebra + + + hierarchy of typessymbolicin symbolic algebra + polynomial(s)hierarchy of types + type(s)hierarchy in symbolic algebra + + + Our polynomial system illustrates how objects of one type + (polynomials) may in fact be complex objects that have objects of many + different types as parts. This poses no real difficulty in defining + generic operations. We need only install appropriate generic operations + for performing the necessary manipulations of the parts of the + compound types. In fact, we saw that polynomials form a kind of + recursive data abstraction, in that parts of a polynomial may + themselves be polynomials. Our generic operations and our + data-directed programming style can handle this complication without + much trouble. + + + On the other hand, polynomial algebra is a system for which the data + types cannot be naturally arranged in a tower. For instance, it is + possible to have polynomials in $x$ whose + coefficients are polynomials in $y$. It is also + possible to have polynomials in $y$ whose + coefficients are polynomials in $x$. Neither of + these types is above the other in any natural way, yet it is + often necessary to add together elements from each set. There are several + ways to do this. One possibility is to convert one polynomial to the type + of the other by expanding and rearranging terms so that both polynomials + have the same principal variable. One can impose a towerlike structure on + this by ordering the variables and thus always converting any polynomial + to a + canonical form, for polynomials + polynomial(s)canonical form + canonical form with the highest-priority variable + dominant and the lower-priority variables buried in the coefficients. + This strategy works fairly well, except that the conversion may expand + a polynomial unnecessarily, making it hard to read and perhaps less + efficient to work with. The tower strategy is certainly not natural + for this domain or for any domain where the user can invent new types + dynamically using old types in various combining forms, such as + trigonometric functions, power series, and integrals. + + + It should not be surprising that controlling + coercionin algebraic manipulation + coercion is a serious problem in the design of large-scale + algebraic-manipulation systems. Much of the complexity of such systems is + concerned with relationships among diverse types. Indeed, it is fair to + say that we do not yet completely understand coercion. In fact, we do not + yet completely understand the concept of a data type. Nevertheless, what + we know provides us with powerful structuring and modularity principles to + support the design of large systems. + + + + By imposing an ordering on variables, extend the polynomial package so + that addition and multiplication of polynomials works for polynomials + in different variables. (This is not easy!) + + + + hierarchy of typessymbolicin symbolic algebra + polynomial(s)hierarchy of types + type(s)hierarchy in symbolic algebra + + + Extended exercise: Rational functions + + + rational function + function (mathematical)rational + polynomial arithmeticrational functions + + + We can extend our generic arithmetic system to include rational + functions. These are fractions whose numerator and + denominator are polynomials, such as + + \[ + \begin{array}{l} + \dfrac{x+1}{x^3 -1} + \end{array} + \] + + The system should be able to add, subtract, multiply, and divide + rational functions, and to perform such computations as + + \[ + \begin{array}{lll} + \dfrac{x+1}{x^3 -1}+\dfrac{x}{x^2 -1} & = & \dfrac{x^3 +2x^2 +3x +1}{x^4 + + x^3 -x-1} + \end{array} + \] + + (Here the sum has been simplified by removing common factors. + Ordinary cross multiplication would have produced a + fourth-degree polynomial over a fifth-degree polynomial.) + + + If 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 fractions to lowest terms. + + + + Modify the rational-arithmetic package to use generic operations, but + change + + make-rat + make_rat + + so that it does not attempt to reduce fractions to lowest terms. Test + your system by calling + + make-rational + make_rational + + + on two polynomials to produce a rational function + + + (define p1 (make-polynomial 'x '((2 1)(0 1)))) + (define p2 (make-polynomial 'x '((3 1)(0 1)))) + (define rf (make-rational p2 p1)) + + +const p1 = make_polynomial("x", list(make_term(2, 1), make_term(0, 1))); +const p2 = make_polynomial("x", list(make_term(3, 1), make_term(0, 1))); +const rf = make_rational(p2, p1); + + + Now add rf to itself, using + add. You will observe that this addition + + procedure + function + + does not reduce fractions to lowest terms. + + + + + We can reduce polynomial fractions to lowest terms using the same idea + we used with integers: modifying + + make-rat + make_rat + + to divide both the numerator and the denominator by their greatest common + divisor. The notion of + greatest common divisorof polynomials + polynomial arithmeticgreatest common divisor + greatest common divisor makes sense for polynomials. In + fact, we can compute the GCD of two polynomials using essentially the + same Euclids Algorithm that works for integers.The fact + that + Euclids Algorithmpolynomialsfor polynomials + polynomial arithmeticEuclids Algorithm + Euclids Algorithm works for polynomials is formalized in algebra + by saying that polynomials form a kind of algebraic domain called a + Euclidean ring + measure in a Euclidean ring + Euclidean ring. A Euclidean ring is a domain that admits + addition, subtraction, and commutative multiplication, together with a + way of assigning to each element $x$ of the + ring a positive integer + measure $m(x)$ with the + properties that $m(xy)\geq m(x)$ for any nonzero + $x$ and $y$ and that, + given any $x$ and $y$, + there exists a $q$ such that + $y=qx+r$ and either + $r=0$ or + $m(r) < m(x)$. From an abstract point of + view, this is what is needed to prove that Euclids Algorithm works. + For the domain of integers, the measure $m$ of an + integer is the absolute value of the integer itself. For the domain of + polynomials, the measure of a polynomial is its degree. The + integer version is + + + + (define (gcd a b) + (if (= b 0) + a + (gcd b (remainder a b)))) + + +function gcd(a, b) { + return b === 0 + ? a + : gcd(b, a % b); +} + + + Using this, we could make the obvious modification to define a GCD + operation that works on term lists: + + gcd_terms + + (define (gcd-terms a b) + (if (empty-termlist? b) + a + (gcd-terms b (remainder-terms a b)))) + + +function gcd_terms(a, b) { + return is_empty_termlist(b) + ? a + : gcd_terms(b, remainder_terms(a, b)); +} + + + where + + remainder-terms + remainder_terms + + + picks out the remainder component of the list returned by the term-list + division operation + + + div-terms + div_terms + + that was implemented in exercise. + + + + Using + + div-terms, + div_terms, + + implement the + + procedure + function + + remainder_terms + + remainder-terms + remainder_terms + + + and use this to define + + gcd-terms + gcd_@terms + + as above. Now write a + + procedure + function + + greatest common divisorgeneric + + gcd-poly + + gcd_poly + + that computes the polynomial GCD of two polys. (The + + procedure + function + + should signal an error if the two polys are not in the same variable.) + Install in the system a generic operation + + greatest-common-divisor + greatest_common_divisor + + + that reduces to + + gcd-poly + gcd_poly + + for polynomials and to ordinary gcd for + ordinary numbers. As a test, try + + + (define p1 (make-polynomial 'x '((4 1) (3 -1) (2 -2) (1 2)))) + (define p2 (make-polynomial 'x '((3 1) (1 -1)))) + (greatest-common-divisor p1 p2) + + +const p1 = make_polynomial("x", list(make_term(4, 1), make_term(3, -1), + make_term(2, -2), make_term(1, 2))); +const p2 = make_polynomial("x", list(make_term(3, 1), make_term(1, -1))); +greatest_common_divisor(p1, p2); + + + and check your result by hand. + + + + + Define $P_{1}$, + $P_{2}$, and + $P_{3}$ to be the polynomials + + + + + + + + + + + + + +
+ $P_{1}$: + + $x^2 - 2x + 1$ +
+ $P_{2}$: + + $11x^2 + 7$ +
+ $P_{3}$: + + $13x + 5$ +
+ Now define $Q_1$ to be the product of + $P_1$ and $P_2$ and + $Q_2$ to be the product of + $P_1$ and $P_3$, and + use + + greatest-common-divisor + greatest_common_divisor + + + (exercise) to compute the GCD of + $Q_1$ and $Q_2$. + Note that the answer is not the same as $P_1$. + This example introduces noninteger operations into the computation, causing + difficulties with the GCD + + + algorithm.In an implementation like MIT Scheme, this produces + a polynomial that is indeed a divisor of + $Q_1$ and $Q_2$, + but with rational coefficients. In many other Scheme systems, in which + division of integers can produce limited-precision decimal numbers, we + may fail to get a valid divisor. + + + algorithm.In JavaScript, division of integers can produce + limited-precision decimal numbers, and thus we + may fail to get a valid divisor. + + + To understand what is happening, try tracing + + gcd-terms + gcd_terms + + while computing the GCD or try performing the division by hand. + +
+ + + + We can solve the problem exhibited in + exercise if + we use the following modification of the GCD algorithm (which really + works only in the case of polynomials with integer coefficients). + Before performing any polynomial division in the GCD computation, we + multiply the dividend by an integer constant factor, chosen to + guarantee that no fractions will arise during the division process. + Our answer will thus differ from the actual GCD by an integer constant + factor, but this does not matter in the case of reducing rational + functions to lowest terms; the GCD will be used to divide both the + numerator and denominator, so the integer constant factor will cancel + out. + + + More precisely, if $P$ and + $Q$ are polynomials, let + $O_1$ be the order of + $P$ (i.e., the order of the largest term of + $P$) and let $O_2$ + be the order of $Q$. Let + $c$ be the leading coefficient of + $Q$. Then it can be shown that, if we multiply + $P$ by the + integerizing factor + integerizing factor + $c^{1+O_{1} -O_{2}}$, the resulting polynomial + can be divided by $Q$ by using the + + div-terms + div_terms + + algorithm without introducing any fractions. The operation of multiplying + the dividend by this constant and then dividing is sometimes called the + pseudodivision of polynomials + pseudodivision of $P$ by + $Q$. The remainder of the division is + called the + pseudoremainder of polynomials + pseudoremainder. + + + + +
    +
  1. + Implement the + procedure + function + + + pseudoremainder-terms, + + pseudoremainder_terms, + + + which is just like + + remainder-terms + remainder_terms + + + except that it multiplies the dividend by the integerizing factor + described above before calling + + div-terms. + div_terms. + + + Modify + + gcd-terms + gcd_terms + + + to use + + pseudoremainder-terms, + + pseudoremainder_terms, + + + and verify that + + greatest-common-divisor + + greatest_common_divisor + + + now produces an answer with integer coefficients on the example in + exercise. +
  2. +
  3. + The GCD now has integer coefficients, but they are larger than those + of $P_1$. Modify + + gcd-terms + gcd_terms + + + so that it removes common factors from the coefficients of the answer + by dividing all the coefficients by their (integer) greatest common + divisor. +
  4. +
+
+ polynomial arithmeticgreatest common divisor + rational functionreducing to lowest terms + reducing to lowest terms + + + Thus, here is how to reduce a rational function to lowest terms: +
    +
  • + Compute the GCD of the numerator and denominator, using + the version of + + gcd-terms + gcd_@terms + + + from exercise. +
  • +
  • + When you obtain the GCD, multiply both numerator and + denominator by the same integerizing factor before dividing through by + the GCD, so that division by the GCD will not introduce any noninteger + coefficients. As the factor you can use the leading coefficient of + the GCD raised to the power + $1+O_{1} -O_{2}$, where + $O_{2}$ is the order of the GCD and + $O_{1}$ is the maximum of the orders of the + numerator and denominator. This will ensure that dividing the + numerator and denominator by the GCD will not introduce any fractions. +
  • +
  • + The result of this operation will be a numerator and denominator + with integer coefficients. The coefficients will normally be very + large because of all of the integerizing factors, so the last step is + to remove the redundant factors by computing the (integer) greatest + common divisor of all the coefficients of the numerator and the + denominator and dividing through by this factor. +
  • +
+
+ + + +
    +
  1. + Implement this algorithm as a + + procedure + function + + + reduce-terms + reduce_terms + + + that takes two term lists n and + d as arguments and returns a list + nn, dd, + which are n and + d reduced to lowest terms via the + algorithm given above. Also write a + + procedure + function + + + reduce-poly, + reduce_poly, + + + analogous to + + add-poly, + + add_poly, + + + that checks to see if the two polys have the same variable. If so, + + reduce-poly + reduce_poly + + + strips off the variable and passes the problem to + + reduce-terms, + reduce_terms, + + + then reattaches the variable to the two term lists supplied by + + + reduce-terms. + reduce_terms. + + +
  2. +
  3. + Define a + + procedure + function + + analogous to + + reduce-terms + reduce_terms + + + that does what the original + + make-rat + make_rat + + did for integers: + + + (define (reduce-integers n d) + (let ((g (gcd n d))) + (list (/ n g) (/ d g)))) + + +function reduce_integers(n, d) { + const g = gcd(n, d); + return list(n / g, d / g); +} + + + and define + reduce as a generic operation that calls + + apply-generic + apply_generic + + + to dispatch + + + to either + reduce-poly + + + either to + reduce_poly + + + (for polynomial arguments) or + + reduce-integers + to reduce_integers + + + (for + + scheme-number + javascript_@number + + + arguments). You can now easily make the rational-arithmetic package + reduce fractions to lowest terms by having + + make-rat + make_rat + + call reduce before combining the given + numerator and denominator to form a rational number. The system now + handles rational expressions in either integers or polynomials. + To test your program, try the example at the beginning of this + extended exercise: + + + (define p1 (make-polynomial 'x '((1 1)(0 1)))) + (define p2 (make-polynomial 'x '((3 1)(0 -1)))) + (define p3 (make-polynomial 'x '((1 1)))) + (define p4 (make-polynomial 'x '((2 1)(0 -1)))) + + (define rf1 (make-rational p1 p2)) + (define rf2 (make-rational p3 p4)) + + (add rf1 rf2) + + +const p1 = make_polynomial("x", list(make_term(1, 1), make_term(0, 1))); +const p2 = make_polynomial("x", list(make_term(3, 1), make_term(0, -1))); +const p3 = make_polynomial("x", list(make_term(1, 1))); +const p4 = make_polynomial("x", list(make_term(2, 1), make_term(0, -1))); + +const rf1 = make_rational(p1, p2); +const rf2 = make_rational(p3, p4); + +add(rf1, rf2); + + + See if you get the correct answer, correctly reduced to lowest terms. +
  4. +
+
+ + + The GCD computation is at the heart of any system that does operations + on rational functions. The algorithm used above, although + mathematically straightforward, is extremely slow. The slowness is + due partly to the large number of division operations and partly to + the enormous size of the intermediate coefficients generated by the + pseudodivisions. + rational functionreducing to lowest terms + reducing to lowest terms + One of the active areas in the development of + algebraic-manipulation systems is the design of better algorithms for + computing polynomial GCDs.One extremely efficient and + elegant method for computing + polynomial arithmeticgreatest common divisor + polynomial arithmeticprobabilistic algorithm for GCD + probabilistic algorithm + algorithmprobabilistic + polynomial GCDs was discovered by + Zippel, Richard E. + Richard Zippel (1979). The method is a probabilistic algorithm, as is the + fast test for primality that we discussed in chapter. + Zippels book (1993) describes this method, together with other ways + to compute polynomial GCDs. + rational function + function (mathematical)rational + polynomial arithmeticrational functions + symbolic algebra + polynomial(s) + polynomial arithmetic + + +
+ diff --git a/xml/cn/chapter3/chapter3.xml b/xml/cn/chapter3/chapter3.xml new file mode 100644 index 000000000..8c36baba9 --- /dev/null +++ b/xml/cn/chapter3/chapter3.xml @@ -0,0 +1,101 @@ + + 模块性, 对象, 和状态 + + \addtocontents{toc}{\protect\enlargethispage{2\baselineskip}} + + + % 1997 年冬 增加 tex '\label' 以供手册参考。 + % 8/28/96 修正第三版打印的 3.5.5 页眉 - - 第353, 355, 357页 + + % 4/13-4/15 索引整理 [在最终章节打印后] + % 4/12 从校对中进行修正和页面调整 + % 4/11 校对中修正 - - 与索引前对比无异 + % 4/8-... Julie 进行索引修复 (以及 4/8 改变 'ns') + % 4/7/96 修正文本错误 & 索引;完成页面编排 + % 4/6/96 根据 Hal 的要求编辑例题 3.73;分页到 3.4.2 + % 4/5/96 Hal: 修正索引 + % 4/3/96 Julie: 修正索引 + % 3/30/96 Julie: 修正校对中发现的错误 + % 3/29/96 Julie: 修正索引 + % 3/18-3/21 Julie 进行的恒定修正 + % 3/17/96 Hal 索引,Julie 修正 + % 3/13/96 新增几个索引条目: 一些 (并非全部) 原语和特殊形式的条目,用于题诗 + % 3/11/96 新增几个索引条目 + % 3/9/96 在两个图注中强制换行 + % 3/6/96 修正错别字;轻微排版调整 + % 3/5/96 修正例题中图的位置 (2/26 遗漏了 rc) + % 3/4/96 重写以"修正"大多数三重和四重连字符 + % 将操作规范 (队列, 导线, 议程, 连接器) 分开行并重写连接器操作 + % 3/3/96 更正标题 3.4、3.4.2、3.5 的大写 + % 3/3/96 拼出 3 位作者 (Morris...) 而不是等。等人。 + % 2/26-3/3/96 修复一些不良换行 + % 2/26/96 移动 ode2, srlcb, srlc 图到练习中以避免额外的练习间空隙 + % 2/24/96 在 {lisp} 后紧接 \noindent 以关闭空白 + % 2/24/96 向 Gordon 参考文献中添加缺少的作者 + % 2/22/96 题诗的新规格 + % 2/19/96 更正错别字 + % 2/19/96 修正参考文献中的 () + % 2/19/96 \ 在 et al. 后面。 + + + + $\def\aal{\alpha{\raise 1pt\hbox{\hskip -4pt{\char'47}}}} + M\epsilon\tau\alpha\beta\acute{\alpha}\lambda\lambda o \nu\ + ἀ\nu\alpha\pi\alpha\acute{\upsilon}\epsilon\tau\alpha\iota$ +

+ (即使在变化中,它仍然保持不变。) + 赫拉克利特 + 赫拉克利特 +
+ + Plus ça change, plus c'est la même chose. + + Karr, Alphonse + Alphonse Karr + + + + + + + + 前几章介绍了构建程序的基本元素。我们看到如何将原语 + + 过程 + 函数 + + 和原语数据结合构建复合实体,并了解到抽象是帮助我们应对大型系统复杂性的重要手段。但这些工具不足以设计程序。有效的程序综合还需要能指导我们制定程序总体设计的组织原则。特别是,我们需要策略来帮助我们构建大型系统,使其模块化,即可以自然地分成可以分别开发和维护的一致部分。 + 模块化 + 模块化, 即,它们可以被自然地分成可以独立开发和维护的多个部分。 + + + + 一个强大的设计策略,特别适用于构建用于物理系统建模的程序,是将我们程序的结构基于被建模系统的结构。对于系统中的每个对象,我们构建一个对应的计算对象。对于每个系统操作,我们在我们的计算模型中定义一个符号操作。我们使用此策略的希望在于,扩展模型以适应新对象或新操作不需要对程序进行战略性更改,只需添加这些对象或操作的新符号类比。如果我们的系统组织成功,那么为添加新功能或调试旧功能,我们将只需在系统的局部部分工作。 + + + + 在很大程度上,我们组织大型程序的方式是由我们对被建模系统的感知决定的。在本章中,我们将研究由两种截然不同的系统结构世界观产生的两种突出的组织策略。第一种组织策略集中于对象对象,将大型系统视为一组不同的对象,其行为随着时间可能会改变。另一种组织策略集中于系统中流动的信息,就像电气工程师看待信号处理系统一样【16:0†cn.txt】。 + + + + 对象化方法和流处理方法在编程中都提出了重要的语言学问题。对于对象,我们必须关注计算对象如何在改变的同时保持其身份。这将迫使我们放弃旧的计算代换模型 (节),转而采用一种更具机械性但在理论上不易处理的求值环境模型 。处理对象、变化和身份带来的困难是我们在计算模型中需要处理时间的基本结果。当我们允许程序的并发执行时,这些困难会更大。流方法可以在我们将模型中的模拟时间与评估期间计算机中发生事件的顺序分离时得到最充分的利用。我们将通过一种称为延迟求值的技术来实现这一点 。 + + + + + + &section3.1; + + + &section3.2; + + + &section3.3; + + + &section3.4; + + + &section3.5; + +
diff --git a/xml/cn/chapter3/section1/section1.xml b/xml/cn/chapter3/section1/section1.xml new file mode 100644 index 000000000..54c39aa20 --- /dev/null +++ b/xml/cn/chapter3/section1/section1.xml @@ -0,0 +1,42 @@ +
+ 赋值与局部状态 + + + + + 赋值 + 局部状态 + + + 我们通常将世界视为由独立对象组成,每个对象都有一个随时间变化的状态。一个对象如果其行为受到历史的影响,则称其具有状态。例如,一个银行账户具有状态,因为对于我可以提现100吗?这个问题的答案取决于存款和取款交易的历史。我们可以通过一个或多个状态变量state variables来表征对象的状态,这些变量之间保持足够的历史信息以确定对象当前的行为。在一个简单的银行系统中,我们可以通过当前余额来表征账户的状态,而不是记住账户交易的整个历史。 + + + + 在由许多对象组成的系统中,这些对象很少完全独立。它们可能通过交互影响其他对象的状态,这些交互用来耦合一个对象的状态变量与其他对象的状态变量。实际上,当一个系统的状态变量可以分组为紧密耦合的子系统,并且这些子系统与其他子系统仅松散耦合时,认为一个系统由独立对象组成的观点才是最有用的。 + + + + 这种系统观可以成为组织系统计算模型的强大框架。为了使这种模型具有模块化,应该将其分解为模拟系统中实际对象的计算对象。每个计算对象都必须有自己的局部状态变量来描述实际对象的状态。由于被建模系统中的对象状态随时间变化,相应计算对象的状态变量也必须变化。如果我们选择用计算机中经过的时间来模拟系统中的时间流动,那么就必须构建计算对象,使得随着程序的运行,其行为会发生变化。特别是,如果我们希望用编程语言中普通的符号名称来建模状态变量,那么语言必须提供一个 + + + 赋值运算符 + assignment operator + + + 赋值赋值操作 + assignment operation + + + 来让我们能够改变与名称相关的值。 + + + + &subsection3.1.1; + + + &subsection3.1.2; + + + &subsection3.1.3; + +
diff --git a/xml/cn/chapter3/section1/subsection1.xml b/xml/cn/chapter3/section1/subsection1.xml new file mode 100644 index 000000000..a5ce7ba2c --- /dev/null +++ b/xml/cn/chapter3/section1/subsection1.xml @@ -0,0 +1,1558 @@ + + + Local State Variables + + + + local state variable + state variablelocal + + + To illustrate what we mean by having a computational object with + object(s)with time-varying state + time-varying state, let us model the situation of withdrawing money + from a + bank account + bank account. We will do this using a + + procedure + function + + withdraw, which takes as argument an + amount to be withdrawn. + If there is enough money in the account to accommodate the withdrawal, + then withdraw should return the balance + remaining after the withdrawal. Otherwise, + withdraw should return the message + Insufficient funds. For example, if we begin with 100 + in the account, we should obtain the following sequence of responses + using + withdraw: + + withdraw_example + 75 + withdraw + +(withdraw 25) + + +75 + + +withdraw(25); + + +75 + + + + withdraw_example2 + withdraw + withdraw_example + 50 + +(withdraw 25) + + +50 + + +withdraw(25); + + +50 + + + + withdraw_example3 + withdraw + withdraw_example + withdraw_example2 + 'Insufficient funds' + +(withdraw 60) + + +"Insufficient funds" + + +withdraw(60); + + +"Insufficient funds" + + + + withdraw_example4 + withdraw + withdraw_example + withdraw_example2 + withdraw_example3 + 35 + +(withdraw 15) + + +35 + + +withdraw(15); + + +35 + + + Observe that the expression + + (withdraw 25), + withdraw(25), + + + evaluated twice, yields different values. This is a new kind of + behavior for a + + procedure. + function. + Until now, all our + + procedures + JavaScript functions + + could be viewed as specifications for computing mathematical functions. + A call to a + + procedure + function + + computed the value of the function applied to the given arguments, + and two calls to the same + + procedure + function + + with the same arguments always produced the same + result.Actually, this is not quite true. One exception was the + math_random (primitive function)assignment needed for + random-number generator + random-number generator + in section. Another exception + involved the + operation-and-type tableassignment needed for + operation/type tables we introduced in + section, where the values of two + calls to get with the same arguments + depended on intervening calls to put. + On the other hand, until we introduce assignment, we have no way to + create such + + procedures + functions + + ourselves. + + + + + + To implement withdraw, we can use a + variable balance to indicate the balance of + money in the account and define withdraw + as a + + procedure + function + + that accesses balance. + + + So far, all our names have been immutable. + When 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. + To implement functions like + withdraw, we introduce + variabledeclaration + syntactic formsvariable declaration + declarationvariableof variable (let) + variable declarations, which use the keyword + let (keyword) + keywordsletlet + let, in addition to constant + declarations, which use the keyword + const. + We can declare a variable + balance + to indicate the balance of money + in the account and define + withdraw as a function that accesses + balance. + + + The withdraw + + procedure + function + + checks to see if balance is at least as large + as the requested amount. If so, + withdraw decrements + balance by amount + and returns the new value of balance. Otherwise, + withdraw returns the Insufficient funds + message. Here are the + + definitions + declarations + + of balance and + withdraw: + + withdraw + withdraw + withdraw_example + +(define balance 100) + +(define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + + +let balance = 100; + +function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } +} + + + Decrementing balance is accomplished by the + + expression + expression statement + + + +(set! balance (- balance amount)) + + +balance = balance - amount; + + + + + This uses the set! special form, whose + syntax is + + +(set! $\langle \textit{name} \rangle$ $\langle \textit{new-value}\rangle$) + + + + + The syntax of + assignment + assignmentassignment expression + variableassignment to + syntactic formsassignment + == + assignment expressions is + + +name = new-value + + + + + Here + + + $\langle \textit{name} \rangle$ + is a symbol + + + name + has been declared with + variableparameter as + let or + as a + parametersvariableas variables + function parameter + + + and + + + $\langle \textit{new-value} \rangle$ + + + new-value + + + is any expression. + + Set! + The assignment + + changes + + + $\langle \textit{name} \rangle$ + + + name + + + so that its value is the + result obtained by evaluating + + + $\langle \textit{new-value}\rangle$. + + + new-value. + + + In the case at hand, we are changing balance so + that its new value will be the result of subtracting + amount from the previous value of + balance. + + + unspecified valuessetset! + The value of a set! expression is + implementation-dependent. Set! should be + used only for its effect, not for its value. + naming conventions!! for assignment and mutation + exclamation point in names + !in names + The name set! reflects a naming convention + used in Scheme: Operations that change the values of variables (or that + change data structures, as we will see in + section) are given names that end + with an exclamation point. This is similar to the convention of + designating predicates by names that end with a question mark. + + + The + assignmentvalue of + value of an assignment is the value being assigned to the name. + Assignment expression statements + assignmentconstant/variable declaration vs. + look similar to and should not be + confused with constant and variable declarations of the form + + +const $name$ = $value$; + + + and + + +let $name$ = $value$; + + + in which a newly declared name + is associated with a value. + Assignment expressions look similar to and should not be confused with + assignmentequality test vs. + expressions of the form + + +$expression_1$ === $expression_2$ + + + which evaluate to true + if expression$_1$ evaluates to the + same value as expression$_2$ and to + false otherwise. + + + + + + + + special forms (those marked ns are not in the IEEE Scheme standard)begin + Withdraw also uses the + begin + special form to cause two expressions to be evaluated in the case where + the if test is true: first decrementing + balance and then returning the value of + balance. In general, evaluating the + expression + + +(begin $\textit{exp}_{1}$ $\textit{exp}_{2}$ $\ldots$ $\textit{exp}_{k}$) + + + causes the expressions $\textit{exp}_{1}$ + through $\textit{exp}_{k}$ to be evaluated in + sequence and the value of the final expression + $\textit{exp}_{k}$ to be returned as the + value of the entire begin + form.We have already used + beginimplicit in consequent of conditional + cond and in + begin implicitly in our programs, because in + Scheme the body of a procedure can be a sequence of expressions. Also, + the consequent part of each clause in a + condimplicit beginimplicit begin in consequent + cond expression can be a sequence of + expressions rather than a single expression. + + + The function withdraw also uses a + sequence of statements + 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. + In general, executing a sequence + + +stmt$_{1}$ stmt$_{2} \ldots$stmt$_{n}$ + + + causes the statements stmt$_{1}$ + through + stmt$_{n}$ to be evaluated in + sequence.We have already used + sequence of statementsblockin block + sequences implicitly in our programs, because in + JavaScript the body block + of a function can contain a sequence of function declarations + followed by a return statement, not + just a single return statement, + as discussed in + section. + + In JavaScript, the return value of functions is determined by + return statements, which + can appear in the first statement of a sequence. To make matters + even more complex, at JavaScript top level, the value of a sequence is + the value of the first component, if the second component is not + value-producing. Thus in JavaScript, + + +1; const x = 2; + + + evaluates to the value 1. We decide to ignore the subtleties of the + JavaScript top level here. The return value of non-top-level sequences + are determined by the placement of return statements, which we will + explain later. + + + + + + + Although withdraw works as desired, the + variable balance presents a problem. As + specified above, balance is a name defined + in the + + global + program + environment and is freely accessible to be examined or + modified by any + + procedure. + function. + + It would be much better if we could somehow make + balance internal to + withdraw, so that + withdraw would be the only + + procedure + function + + that could access balance directly and + any other + + procedure + function + + could access balance only indirectly + (through calls to withdraw). This would + more accurately model the notion that + balance is a local state variable used by + withdraw to keep track of the state of the + account. + + + + + We can make balance internal to + withdraw by rewriting the definition as + follows: + + new_withdraw_example + +new_withdraw(60); +new_withdraw(60); + + + + new_withdraw + new_withdraw + new_withdraw_example + +(define new-withdraw + (let ((balance 100)) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")))) + v + +function make_withdraw_balance_100() { + let balance = 100; + return amount => { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + }; +} +const new_withdraw = make_withdraw_balance_100(); + + + + + What we have done here is use let to + establish an environment with a local variable + balance, bound to the initial value 100. + Within this local environment, we use + lambda to create a procedure that takes + amount as an argument and behaves like our + previous withdraw procedure. This + procedurereturned as the result of evaluating the + let expressionis + new-withdraw, + which behaves in precisely the same way as + withdraw but whose variable + balance is not accessible by any other + procedure.In programming-language jargon, the variable + balance is said to be + encapsulated name + nameencapsulated + encapsulated within the + new-withdraw + procedure. Encapsulation reflects the general system-design principle + known as the + hiding principle + modularityhiding principle + hiding principle: One can make a system more modular and robust + by protecting parts of the system from each other; that is, by providing + information access only to those parts of the system that have a + need to know. + + + What we have done here is use let + to establish an environment with a local variable + balance, bound to the initial + value 100. Within this local environment, we use a lambda + expressionBlocks as bodies of lambda expressions were + introduced in section. to + create a function that takes amount + as an argument and behaves like our previous + withdraw function. This + 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.In programming-language jargon, the variable + balance is said to be + encapsulated name + nameencapsulated + encapsulated within the + new_withdraw function. + Encapsulation reflects the general system-design principle known as the + hiding principle + modularityhiding principle + hiding principle: One can + make a system more modular and robust by protecting parts of the + system from each other; that is, by providing information access only + to those parts of the system that have a need to + know. + + + + + + + Combining + + + set! + with local variables + + + assignments with variable declarations + + + is the general programming + technique we will use for constructing computational objects with + local state. Unfortunately, using this technique raises a serious + problem: When we first introduced + + procedures, + functions, + + we also introduced the substitution model of evaluation + (section) to provide an + interpretation of what + + procedure + function + + application means. We said that applying a + + procedure + function whose body is a return statement + + should be interpreted as evaluating the + + body of the procedure + return expression of the function + + with the + + formal + + parameters replaced by their values. + + + For functions with more complex + bodies, we need to evaluate the whole body with the + parameters replaced by their values. + + + The trouble is that, + as soon as we introduce assignment into our language, substitution is no + longer an adequate model of + + procedure + function + + application. (We will see why this is so in + section.) As a consequence, we + technically have at this point no way to understand why the + + new-withdraw + + new_withdraw + + + + procedure + function + + behaves as claimed above. In order to really understand a + + procedure + function + + such as + + new-withdraw, + + new_withdraw, + + + we will need to develop a new model of + + procedure + function + + application. In section we will + introduce such a model, together with an explanation of + + set! and local variables. + assignments and variable declarations. + + First, however, we examine some variations on the theme established by + + new-withdraw. + new_withdraw. + + + + + + + + Parameters of functions as well as names declared with + let are + parametersvariableas variables + variables. + + + The following + + + procedure, make-withdraw, + + + function, make_withdraw, + + + creates withdrawal processors. + The formal parameter + balance in + + make-withdraw + make_withdraw + + + specifies the initial amount of money in the + account. + + In contrast with + + + new-withdraw + + + make_withdraw_balance_100 + + + above, we do not have to use + let + to make balance a local variable, since + + formal + + parameters are already + local. This will be clearer after the discussion of the environment + model of evaluation in + section. + (See also + exercise.) + + make_withdraw + make_withdraw + make_withdraw_define + +(define (make-withdraw balance) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds"))) + + +function make_withdraw(balance) { + return amount => { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + }; +} + + + + Make-withdraw + + The function make_withdraw + + + can be used as follows to create two objects W1 + andW2: + + make_withdraw_define + make_withdraw + +(define W1 (make-withdraw 100)) +(define W2 (make-withdraw 100)) + + +const W1 = make_withdraw(100); +const W2 = make_withdraw(100); + + + + make_withdraw_example1 + make_withdraw_define + 50 + +(W1 50) + + +50 + + +W1(50); + + +50 + + + + make_withdraw_example1 + make_withdraw_example2 + 30 + +(W2 70) + + +30 + + +W2(70); + + +30 + + + + make_withdraw_example3 + make_withdraw_example2 + 'Insufficient funds' + +(W2 40) + + +"Insufficient funds" + + +W2(40); + + +"Insufficient funds" + + + + make_withdraw_example4 + make_withdraw_example3 + 10 + +(W1 40) + + +10 + + +W1(40); + + +10 + + + Observe that W1 and + W2 are completely independent objects, each + with its own local state variable balance. + Withdrawals from one do not affect the other. + + + + We can also create objects that handle + deposit message for bank account + deposits as well as + withdrawals, and thus we can represent simple bank accounts. Here is + a + + procedure + function + + that returns a bank-account object with a specified initial + balance: + + make_account + make_account + make_account_example_my + +(define (make-account balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (define (dispatch m) + (cond ((eq? m 'withdraw) withdraw) + ((eq? m 'deposit) deposit) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch) + + +function make_account(balance) { + function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + function dispatch(m) { + return m === "withdraw" + ? withdraw + : m === "deposit" + ? deposit + : error(m, "unknown request -- make_account"); + } + return dispatch; +} + + + + make_account_example_my + +(define acc (make-account 100)) +((acc 'withdraw) 50) + + +const acc = make_account(100); + +acc("withdraw")(50); + + + Each call to make_account sets up an + environment with a local state variable balance. + Within this environment, make_account defines + + procedures + functions + + deposit and + withdraw that access + balance and an additional + + procedure + function + + dispatch + that takes a message as input and returns one of the two local + + procedures. + functions. + + The dispatch + + procedure + function + + itself is returned as the value that represents the bank-account object. + This is precisely the + message passingin bank account + message-passing style of programming that we saw in + section, although here we are using + it in conjunction with the ability to modify local variables. + + + + + + Make-account + + + The function + make_account + + + can be used as follows: + + make_account_example + make_account + + (define acc (make-account 100)) + + +const acc = make_account(100); + + + + make_account_example1 + make_account_example + 50 + +((acc 'withdraw) 50) + + +50 + + +acc("withdraw")(50); + + +50 + + + + make_account_example1 + make_account_example2 + 'Insufficient funds' + +((acc 'withdraw) 60) + + +"Insufficient funds" + + +acc("withdraw")(60); + + +"Insufficient funds" + + + + make_account_example2 + make_account_example3 + 90 + +((acc 'deposit) 40) + + +90 + + +acc("deposit")(40); + + +90 + + + + make_account_example3 + make_account_example4 + 30 + +((acc 'withdraw) 60) + + +30 + + +acc("withdraw")(60); + + +30 + + + Each call to acc returns the locally defined + deposit or withdraw + + procedure, + function, + + which is then applied to the specified amount. + As was the case with + + + make-withdraw, another + call to make-account + + + make_withdraw, another + call to make_account + + + + make_account + +(define acc2 (make-account 100)) + + +const acc2 = make_account(100); + + + will produce a completely separate account object, which maintains its + own local balance. + + + + + An + accumulator + accumulator is a + + procedure + function + + that is called repeatedly with a single numeric argument and accumulates its + arguments into a sum. Each time it is called, it returns the currently + accumulated sum. Write a + + procedure + function + + make_accumulator + + make-accumulator + make_accumulator + + + that generates accumulators, each maintaining an independent sum. The + input to + + make-accumulator + make_accumulator + + + should specify the initial value of the sum; for example + + make_accumulator_example + +(define A (make-accumulator 5)) + + +const a = make_accumulator(5); + + +// make_accumulator to be written by students +const a = make_accumulator(5); + + + + make_accumulator_example1 + make_accumulator_example + +(A 10) + + +15 + + +a(10); + + +15 + + + + make_accumulator_example2 + make_accumulator_example1 + +(A 10) + + +25 + + +a(10); + + +25 + + + + make_accumulator_example_solution + +(A 10) + + +25 + + +const a = make_accumulator(5); +a(10); +a(10); + + +25 + + + + + make_accumulator_solution + make_accumulator_example_solution + 25 + +function make_accumulator(current) { + function add(arg) { + current = current + arg; + return current; + } + return add; +} + + + + + + + + In software-testing applications, it is useful to be able to count the + number of times a given + + procedure + function + + is called during the course of a computation. Write a + + procedure + function + + make_monitored + monitored procedurefunction + monitored + + make-monitored + make_monitored + + + that takes as input a + + procedure, + function, + + f, that itself takes one input. The result + returned by + + make-monitored + make_monitored + + + is a third + + procedure, + function, + + say mf, that keeps track of the number of times + it has been called by maintaining an internal counter. If the input to + mf is the + + special symbol how-many-calls, + + string "how many calls", + + + then mf returns the value of the counter. If + the input is the + + special symbol reset-count, + string "reset count", + + + then mf resets the counter to zero. For any + other input, mf returns the result of calling + f on that input and increments the counter. + For instance, we could make a monitored version of the + sqrt + + procedure: + function: + + + make_monitored_example + + (define s (make-monitored sqrt)) + + +const s = make_monitored(math_sqrt); + + +// make_monitored function to be written by students +const s = make_monitored(math_sqrt); + + + + make_monitored_example + make_monitored_example1 + +(s 100) + + +10 + + +s(100); + + +10 + + + + make_monitored_example1 + make_monitored_example2 + +(s 'how-many-calls?) + + +1 + + +s("how many calls"); + + +1 + + + + + make_monitored_example3 + +const s = make_monitored(math_sqrt); +s(100); +display(s("how many calls")); +s(5); +display(s("how many calls")); + + +const s = make_monitored(math_sqrt); +s(100); +s("how many calls"); +s(5); +s("how many calls"); + + + + make_monitored_example3 + 2 + +function make_monitored(f) { + let counter = 0; //initialized to 0 + function mf(cmd) { + if (cmd === "how many calls") { + return counter; + } else if (cmd === "reset count") { + counter = 0; + return counter; + } else { + counter = counter + 1; + return f(cmd); + } + } + return mf; +} + + + + + + + + + Modify the + + make-account + make_account + + + procedure + function + + so that it creates + bank accountpassword-protected + password-protected bank account + password-protected accounts. That is, + + make-account + make_account + + should take a + + symbol + string + + as an additional argument, as in + + make_account_exercise + + (define acc (make-account 100 'secret-password)) + + +const acc = make_account(100, "secret password"); + + +// make_account function to be written by students +const acc = make_account(100, "secret password"); + + + The resulting account object should process a request only if it is + accompanied by the password with which the account was created, and + should otherwise return a complaint: + + make_account_exercise + make_account_exercise_example1 + +((acc 'secret-password 'withdraw) 40) + + +60 + + +acc("secret password", "withdraw")(40); + + +60 + + + + make_account_exercise_example2 + make_account_exercise_example1 + +((acc 'some-other-password 'deposit) 50) + + +"Incorrect password" + + +acc("some other password", "deposit")(40); + + +"Incorrect password" + + + + + make_account_password_protected + 'Incorrect Password' + +function make_account(balance, p) { + function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + function dispatch(m, q) { + if (p === q) { + return m === "withdraw" + ? withdraw + : m === "deposit" + ? deposit + : "Unknown request: make_account"; + } else { + return q => "Incorrect Password"; + } + } + return dispatch; +} + +const a = make_account(100, "eva"); +a("withdraw", "eva")(50); //withdraws 50 +a("withdraw", "ben")(40); //incorrect password + + + + + + + + Modify the + + make-account + make_account + + + procedure + function + + of exercise by adding another + local state variable so that, if an account is accessed more than seven + consecutive times with an incorrect password, it invokes the + + procedure + function + + + call-the-cops. + call_the_cops. + + + + + mka + mka_example + 'calling the cops because you have exceeded the max no of failed attempts' + +function call_the_cops(reason) { + return "calling the cops because " + reason; +} +function make_account(balance, p) { + + let invalid_attempts = 0; //initializes to 0 + + function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + + function deposit(amount) { + balance = balance + amount; + return balance; + } + + function calling_the_cops(_) { + return call_the_cops("you have exceeded " + + "the max no of failed attempts"); + } + + function dispatch(m, q) { + if (invalid_attempts < 7) { + if (p === q) { + return m === "withdraw" + ? withdraw + : m === "deposit" + ? deposit + : "Unknown request: make_account"; + } else { + invalid_attempts = invalid_attempts + 1; + return x => "Incorrect Password"; + } + } else { + return calling_the_cops; + } + } + + return dispatch; + +} + + + + mka_example + +const a = make_account(100, "rosebud"); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); +a("withdraw", "rosebad")(50); + + + + + local state variable + state variablelocal + + diff --git a/xml/cn/chapter3/section1/subsection2.xml b/xml/cn/chapter3/section1/subsection2.xml new file mode 100644 index 000000000..e57432697 --- /dev/null +++ b/xml/cn/chapter3/section1/subsection2.xml @@ -0,0 +1,678 @@ + + + 引入赋值的好处 + + + + 赋值好处 + + + 正如我们将看到的,引入赋值到我们的编程语言中将我们带入了一系列复杂的概念问题。然而,视系统为 + 对象用对象建模的好处 + 具有局部状态的对象集合是一种 + 模块性通过对象建模 + 的强大技术。作为一个简单的例子,考虑设计一个 + + 过程 + 函数 + + rand,每次调用时返回一个随机整数。 + + + + 完全不清楚“随机选择”的含义是什么。 + 我们想要的可能是连续调用 + 随机数生成器 +rand 生成具有 + 均匀分布的统计特性的数列。我们在此不讨论生成合适序列的方法。相反,我们假设有一个 + + 过程 + 函数 + + + rand-update + rand_update + + ,它具有这样的性质:如果我们从一个给定的数字开始 +$x_{1}$ 并形成 + + + + +$x_{2}$ = (rand-update $x_{1}$) +$x_{3}$ = (rand-update $x_{2}$) + + + + + + +$x_2$ = rand_update($x_1$); +$x_3$ = rand_update($x_2$); + + + + + 那么值的序列 + $x_1, x_2, x_3, \ldots$ ,将具有所需的统计特性。 一种常见的实现方式是使用 + + rand-update + rand_update + + 的规则是 $x$ 被更新为 + $ax+b$$m$ ,其中 $a$, $b$ ,并且 + $m$ 是适当选择的整数。 + 第章 + 高德纳,丹纳德 E. + Knuth 1997b 包含关于生成随机数序列和 + 确立其统计特性的技术的广泛讨论。注意 + + rand-update + rand_update + + + 过程 + 函数 + + 计算一个数学函数:给定相同输入两次,它 + 产生相同的输出。因此,由 + + rand-update + rand_update + + 生成的数列肯定不是“随机的”,如果按“随机”我们 + 坚持认为序列中的每个数字与前一个 + 数字无关。 “真实随机性”与所谓的 + 伪随机序列 + 伪随机序列之间的关系,后者是通过确定的 + 计算生成的,但具有合适的统计特性,是一个复杂的问题,涉及数学和 + 哲学上的困难问题。 + 柯尔莫哥洛夫,A. N. + 柯尔莫哥洛夫, + 索洛莫诺夫,雷 + 索洛莫诺夫,以及 + 蔡廷,格里高利 + 蔡廷在澄清这些问题上取得了很大进展;讨论可在 + Chaitin 1975中找到。 + + + + 我们可以实现 +rand 作为一个 + + 过程 + 函数 + + 带有一个局部状态变量 +x 被初始化为某个固定值 + + random-init. + random_init. + + 每次调用 rand 计算 + + rand-update + rand_update + + 当前值的 +x ,将其作为随机数返回,并将其存储为的新值 +x. + + rand_update + +// A very simple rand_update function computes a number +// from 0 (inclusive) to 200560490131 (a large prime) +// from a value x by multiplying it with a constant a, +// adding a constant b, and then taking the remainder +// of dividing it by the large prime. We used it here +// for illustration only, and do not claim any +// statistical properties. +const m = 200560490131; +const a = 1103515245; +const b = 12345; + +function rand_update(x) { + return (a * x + b) % m; +} + + + + rand + rand_definition + rand_update + random_init + rand_example + 40083849805 + +(define rand + (let ((x random-init)) + (lambda () + (set! x (rand-update x)) + x))) + + +function make_rand() { + let x = random_init; + return () => { + x = rand_update(x); + return x; + }; +} +const rand = make_rand(); + + + + + random_init + +const random_init = 123456789; + + + + + rand_example + +display(rand()); +display(rand()); +display(rand()); + + +rand(); +rand(); +rand(); + + + + + + 当然,我们可以通过简单地直接调用 + + rand-update + rand_update + + 来生成相同的随机数序列而不使用赋值。然而,这将意味着我们程序的任何使用 + 随机数的部分都必须明确记住当前的 + x值以作为参数传递给 + + rand-update. + rand_update. + + 为了意识到这将是多么烦人,考虑使用随机数来实现一种名为 + 蒙特卡洛模拟 + 随机数生成器在蒙特卡洛模拟中 + 蒙特卡洛模拟的技术。 + + + + 蒙特卡洛方法包括从一个大集合中随机选择样本实验, + 然后根据通过统计这些实验结果估算出的概率进行推断。 + 例如,我们可以利用 + 圆周率$\pi$ (pi)狄利克雷估计 + $\pi$ 来近似,因为 + $6/\pi^2$ 是两个随机选择的整数没有公因子的概率;也就是说,它们的 + 最大公约数为1。此定理由G. + 狄利克雷,彼得 古斯塔夫 勒热讷 + 勒热讷·狄利克雷提出。见第4.5.2节 + 高德纳,丹纳德 E. + Knuth 1997b 对该讨论及证明。 + 为了获得对$\pi$的近似,我们需要执行大量实验。在每次实验中,我们随机选择两个整数 + 并进行测试 + 最大公约数用于估计用于估计 $\pi$ + 以查看它们的最大公约数是否为1。测试通过的次数比例 + 给出了我们估算的 $6/\pi^2$,从而我们获得对$\pi$的近似。 + + + + 我们程序的核心是一个 + + 过程 + 函数 + + + monte-carlo, + monte_carlo, + + 它将实验的次数作为参数, + 以及实验,表示为一个不带参数的 + + 过程 + 函数 + + ,每次运行时将返回 true 或 false。 + + + Monte-carlo + + + 该函数 + monte_carlo + + + 运行指定次数的实验,并返回一个数, + 指示实验结果为真的试验比例。 + + + + + estimate_pi + dirichlet_test + monte_carlo + monte_carlo + rand_definition + gcd_definition + estimate_pi_example + 3.1408877612819492 + +(define (estimate-pi trials) + (sqrt (/ 6 (monte-carlo trials cesaro-test)))) + +(define (cesaro-test) + (= (gcd (rand) (rand)) 1)) + +(define (monte-carlo trials experiment) + (define (iter trials-remaining trials-passed) + (cond ((= trials-remaining 0) + (/ trials-passed trials)) + ((experiment) + (iter (- trials-remaining 1) (+ trials-passed 1))) + (else + (iter (- trials-remaining 1) trials-passed)))) + (iter trials 0)) + + +function estimate_pi(trials) { + return math_sqrt(6 / monte_carlo(trials, dirichlet_test)); +} +function dirichlet_test() { + return gcd(rand(), rand()) === 1; +} +function monte_carlo(trials, experiment) { + function iter(trials_remaining, trials_passed) { + return trials_remaining === 0 + ? trials_passed / trials + : experiment() + ? iter(trials_remaining - 1, trials_passed + 1) + : iter(trials_remaining - 1, trials_passed); + } + return iter(trials, 0); +} + + + + estimate_pi_example + +estimate_pi(10000); + + + + + + 现在让我们尝试使用 + + rand-update + rand_update + + 而不是 rand 进行相同的计算,这种方式如果我们不使用赋值来模拟局部状态就将被迫采用: + + estimate_pi + estimate_pi_alternative + rand_definition + gcd_definition + estimate_pi_example + 3.1408877612819492 + +(define (estimate-pi trials) + (sqrt (/ 6 (random-gcd-test trials random-init)))) + +(define (random-gcd-test trials initial-x) + (define (iter trials-remaining trials-passed x) + (let ((x1 (rand-update x))) + (let ((x2 (rand-update x1))) + (cond ((= trials-remaining 0) + (/ trials-passed trials)) + ((= (gcd x1 x2) 1) + (iter (- trials-remaining 1) + (+ trials-passed 1) + x2)) + (else + (iter (- trials-remaining 1) + trials-passed + x2)))))) + (iter trials 0 initial-x)) + + +function estimate_pi(trials) { + return math_sqrt(6 / random_gcd_test(trials, random_init)); +} +function random_gcd_test(trials, initial_x) { + function iter(trials_remaining, trials_passed, x) { + const x1 = rand_update(x); + const x2 = rand_update(x1); + return trials_remaining === 0 + ? trials_passed / trials + : gcd(x1, x2) === 1 + ? iter(trials_remaining - 1, trials_passed + 1, x2) + : iter(trials_remaining - 1, trials_passed, x2); + } + return iter(trials, 0, initial_x); +} + + + + + + 虽然程序仍然简单,但它暴露了一些痛苦的模块化破坏。在我们程序的第一个版本中,使用 +rand ,我们可以直接将蒙特卡洛方法表达为一个通用 + + monte-carlo + monte_carlo + + + 过程 + 函数 + + ,将一个任意参数作为参数 +experiment + + 过程。 + 函数。 + + 在我们程序的第二个版本中,由于随机数生成器没有局部状态, + + random-gcd-test + random_gcd_test + + + 必须显式操作随机数 +x1x2 并且 + 回收 +x2 通过迭代循环作为 + 的新输入 + + rand-update. + rand_update. + + 随机数的这种显式处理将累积测试结果的结构与我们特定实验使用两个随机数这一事实交织在一起,而其他蒙特卡洛实验可能使用一个或三个随机数。即使是顶层 + + 过程 + 函数 + + + estimate-pi + estimate_pi + + 也必须关注提供一个初始随机数。随机数生成器的内部泄漏到程序的其他部分一这一事实使得我们很难将蒙特卡洛的思想隔离开来,以便它可以应用于其他任务。在程序的第一个版本中, + 赋值将随机数生成器的状态封装在 +rand + + 过程, + 函数, + 这样随机数生成的细节就可以保持与程序的其余部分独立。 + + + + 蒙特卡洛示例所展示的一般现象是:从复杂过程的一个部分来看,其他部分似乎会随时间而变化。它们有隐藏的时变局部状态。如果我们希望编写其结构反映这种分解的计算机程序,我们创建计算对象(如银行账户和随机数生成器),其行为随时间而变化。我们用局部状态变量来建模状态,并通过赋值给这些变量来建模状态的变化。 + + + + 我们很容易通过引入赋值和隐藏状态在局部变量中的技术来结束这段讨论,这样我们可以比必须通过传递额外参数来显式操作所有状态时,以更模块化的方式进行系统结构。不幸的是,正如我们将看到的,情况并不那么简单。 + + + + + + 蒙特卡洛积分 + 蒙特卡洛积分 + 圆周率$\pi$ (pi)用蒙特卡洛积分近似 + 定积分用蒙特卡洛模拟估计 + 是一种通过蒙特卡洛模拟估算定积分的方法。考虑计算由一个谓词描述的空间区域的面积 +$P(x, y)$ 对于这些点为真的 + $(x, y)$ 在该区域内为真,而不在该区域内为假。例如,包含在半径 + $3$ 以为中心 + $(5, 7)$ 的圆内的区域由测试 + 是否 +$(x-5)^2 + (y-7)^2\leq 3^2$ 。要估计由此类谓词描述的区域的面积,首先选择一个包含该区域的矩形。例如,一个对角相对的角在 +$(2, 4)$ 和 + $(8, 10)$ 包含上述圆。所需的积分是矩形中位于该区域内的部分面积。我们可以通过在矩形中随机挑选点来估算该积分 +$(x, y)$ 查看是否位于矩形内,并测试 + $P(x, y)$ 对每个点确定该点是否位于区域内。如果我们用许多点尝试这个方法,那么落在区域内的点的比例应该给出矩形中位于区域内部分的比例。因此,将这个比例乘以整个矩形的面积就应得到一个积分的估计值。 +

+ 将蒙特卡洛积分实现为一个 + + 过程 + 函数 + + estimate_integral + + estimate-integral + estimate_integral + + + ,它将一个谓词作为参数 +P ,上限 + 和下限 +x1, + x2, y1 ,和 + y2 用于矩形,以及要执行的试验次数以生成估计。 您的 + + 过程 + 函数 + + 应使用相同的 + + monte-carlo + monte_carlo + + + 过程 + 函数 + + 用于估算 +$\pi$ 。使用您的 + + estimate-integral + estimate_integral + + + 来生成一个估计 +$\pi$ 通过测量单位圆的面积。 +

+ 你会发现 + + 过程 + 函数 + + 很有用,它返回一个从给定范围内随机选择的数字。以下 + + random-in-range + random_in_range + + + + 过程 + 函数 + + 根据章节中使用的 + + + random + 过程 + + + math_random + 函数 + + + section,返回一个小于其输入的非负数。 + + + + MIT Scheme + MIT Schemerandomrandom + 提供了这样一个 + + 过程。 + 函数。 + + 如果给定一个精确整数(如按章节中所示),则返回一个精确整数,但如果给定一个小数值(如在本练习中),则返回一个小数值。 + + + than1. + + + + random_in_range + random_in_range + random_definition + random_in_range_example + +(define (random-in-range low high) + (let ((range (- high low))) + (+ low (random range)))) + + +function random_in_range(low, high) { + const range = high - low; + return low + math_random() * range; +} + + + + random_in_range_example + +random_in_range(80000, 81000); + + + + + exercise_3_5_solution_example + +function inside_unit_circle(x, y) { + return square(x) + square(y) <= 1; +} +estimate_integral(inside_unit_circle, -1, 1, -1, 1, 50000); + + + + exercise_3_5_solution + square_definition + monte_carlo + exercise_3_5_solution_example + +function random_in_range(low, high) { + const range = high - low; + return low + math_random() * range; +} +function estimate_integral(pred, x1, x2, y1, y2, trials) { + const area_rect = (x2 - x1) * (y2 - y1); + return monte_carlo(trials, + () => pred(random_in_range(x1, x2), + random_in_range(y1, y2))) * area_rect; +} + + + +
+ + + + 能够 + 随机数生成器带重置 + rand带重置 + 重置随机数生成器以生成从给定值开始的序列是很有用的。设计一个新的 +rand + + 过程 + 函数 + + ,它的参数可以是 + + + 符号 generate 或符号 + reset + + + 字符串 "generate" 或字符串 + "reset" + + + ,并具有如下行为: + + (rand 'generate) + rand("generate") + + + 生成一个新的随机数; + + + ((rand 'reset) + $\langle \textit{new-value} \rangle$ + ) + + + rand("reset")(new-value) + + 将内部状态变量重置为指定的 新值。这样,通过重置状态,可以生成可重复的序列。这在测试和调试使用随机数的程序时非常方便。 + + + prng_example + 127 + +let state = 2; + +function rand(symbol) { + if (symbol === "reset") { + return new_state => { + state = new_state; + }; + } else { + // symbol is "generate" + state = (state * 1010) % 1101; + return state; + } +} + + + + + prng_example + +display(rand("generate")); +display(rand("generate")); +display(rand("generate")); + +// This should display 919, 47, 127 + +rand("reset")(2); + +// State is reset to 2 again + +display(rand("generate")); +display(rand("generate")); +display(rand("generate")); + +// Because initial state is the same, 919, 47, 127 is displayed again + + +rand("generate"); +rand("generate"); +rand("generate"); + +// This generates 919, 47, 127 + +rand("reset")(2); + +// State is reset to 2 again + +rand("generate"); +rand("generate"); +rand("generate"); + +// Because initial state is the same, 919, 47, 127 is generated + + + + + 赋值优点 +
+ diff --git a/xml/cn/chapter3/section1/subsection3.xml b/xml/cn/chapter3/section1/subsection3.xml new file mode 100644 index 000000000..e3996b772 --- /dev/null +++ b/xml/cn/chapter3/section1/subsection3.xml @@ -0,0 +1,1015 @@ + + + 引入赋值的代价 + + + + 赋值代价 + + + + 正如我们所见, + + + set! 操作 + + + 赋值 + + + 使我们能够建模具有局部状态的对象。然而,这种优势是有代价的。我们的编程语言不再可以根据我们在 + + 过程 + 函数 + + 应用的替换模型中进行解释,详情参见节。此外,没有一个具有“良好”数学性质的简单模型可以成为处理编程语言中的对象和赋值的充分框架。 + + + + 只要我们不使用赋值,两次评估相同 + + 过程 + 函数 + + 使用相同的参数将产生相同的结果,这样 + + 过程 + 函数 + + 可以被视为计算数学函数。因此,不使用任何赋值进行编程,就像我们在本书的前两章所做的那样,被称为 + 函数式编程 + 函数式编程。 + + + + 函数应用的代换模型不足之处 + 为了理解赋值如何使事情变得复杂,考虑一个简化版本的 +make_withdraw + + 过程 + 函数 + + 在节中,它不检查是否有不足的情况: + + make_simplified_withdraw + make_simplified_withdraw + make_simplified_withdraw_example + +(define (make-simplified-withdraw balance) + (lambda (amount) + (set! balance (- balance amount)) + balance)) + + +function make_simplified_withdraw(balance) { + return amount => { + balance = balance - amount; + return balance; + }; +} + + + + make_simplified_withdraw_example + make_simplified_withdraw + +(define W (make-simplified-withdraw 25)) + + +const W = make_simplified_withdraw(25); + + + + make_simplified_withdraw_example1 + make_simplified_withdraw + make_simplified_withdraw_example + 5 + +(W 20) + + +5 + + +W(20); + + +5 + + + + make_simplified_withdraw_example2 + make_simplified_withdraw + make_simplified_withdraw_example + make_simplified_withdraw_example1 + -5 + +(W 10) + + +-5 + + +W(10); + + +-5 + + + 比较此 + + 过程 + 函数 + + 与下列 +make_decrementer + + 过程, + 函数, + + 不使用 + + set!: + 赋值: + + + make_decrementer + make_decrementer + + (define (make-decrementer balance) + (lambda (amount) + (- balance amount))) + + +function make_decrementer(balance) { + return amount => balance - amount; +} + + + + make-decrementer + + + The function + make_decrementer + + 返回一个 + + 过程 + 函数 + + 用于从指定数量中减去其输入 + balance ,但与连续调用没有累积效果,如同 make_simplified_withdraw : + + make_decrementer + make_decrementer_example + +(define D (make-decrementer 25)) + + +const D = make_decrementer(25); + + + + make_decrementer_example + make_decrementer_example1 + 5 + +(D 20) + + +5 + + +D(20); + + +5 + + + + make_decrementer_example1 + make_decrementer_example2 + 15 + +(D 10) + + +15 + + +D(10); + + +15 + + + 我们可以使用代换模型来解释如何 +make_decrementer 工作。例如,让我们分析表达式的求值 + make_decrementer + 5 + +((make-decrementer 25) 20) + + +make_decrementer(25)(20) + + +make_decrementer(25)(20); + + + 首先,我们通过替换简化 + + + 组合的运算符 + + + 应用的函数表达式 + + + $25$balance 在 + + + make-decrementer 的 + + + make_decrementer 的 + + + 函数体中。这将表达式简化为 + + +((lambda (amount) (- 25 amount)) 20) + + +(amount => 25 - amount)(20) + + + 现在我们通过用 20 替换来应用 + + + 运算符 + + + 函数 + + + amount + 现在我们在 + + + lambda + + + lambda + + + 表达式的函数体内进行替换: + + 5 + +(- 25 20) + + +25 - 20 + + +25 - 20; + + + 最终答案是 5。 + + + + 然而,请观察如果我们尝试进行类似的替换分析 + with +make_simplified_withdraw : + + make_simplified_withdraw + +((make-simplified-withdraw 25) 20) + + +make_simplified_withdraw(25)(20) + + + 首先,我们通过用 25 替换来简化 + + + 运算符 + + + 函数表达式 + + + balance 在 + 函数体中 + 函数体中 + make_simplified_withdraw 。这将表达式简化为 + + + 表达式 + + + 表达式 + + + 我们不为 + balance 在 + + set! 表达式 + 赋值 + + 中的出现进行替换,因为在 + + a set! + 赋值 + + 中的name不会被评估。如果我们对其进行替换,将得到 + + (set! 25 (- 25 amount)), + 25 = 25 - amount;, + + + 这是没有意义的。 + + +((lambda (amount) (set! balance (- 25 amount)) 25) 20) + + +(amount => { + balance = 25 - amount; + return 25; + })(20) + + + 现在我们通过用 20 替换来应用 + + 运算符 + 函数 + + amount + 在 + + + lambda 表达式的函数体中: + + + lambda 表达式的函数体中: + + + + +(set! balance (- 25 20)) 25 + + +balance = 25 - 20; +return 25; + + + 如果我们坚持代换模型,我们就不得不说 + + 过程 + 函数 + + 应用的意义是先设置 +balance 设为 5 然后返回 25 作为表达式的值。 这得出了错误的答案。 为了得到正确的答案,我们必须以某种方式区分出第一次出现的 +balance (在 + + set! 之前的效果 + + 赋值之前的效果) + + 中的首次出现与第二次出现相区别 +balance + (在 + set! 之后的效果), + 赋值之后的效果), + + 而代换模型无法做到这一点。 + + + + 这里的问题在于替换最终是基于这样一种观念: + + + 我们语言中的符号本质上是 + 值的名称。 + + + 我们语言中的名称本质上是 + 值的符号。 + + + + + 但一旦我们引入 set! 和 + 变量值可以改变的观念,变量就不能再仅仅是一个名称。现在,变量以某种方式指向一个可以存储值的地方,并且存储在这个地方的值可以改变。 + 函数应用的代换模型不足之处 + + + 这对常量来说很好用。 + 但一个值可以通过赋值改变的变量,不能仅仅是一个值的名称。变量以某种方式指向一个可以存储值的地方,并且存储在这个地方的值可以改变。 + 函数应用的代换模型不足之处 + + + 在节中,我们将看到环境在我们的计算模型中扮演“地方”的角色。 + + + + 同一性与变化 + + + 同一性与变化含义 + 变化与同一性含义 + + + 这里出现的问题不仅仅是某种特定计算模型的崩溃。当我们将变化引入到我们的计算模型中时,许多以前简单明了的概念变得复杂起来。考虑两个事物相同的概念。 + + + + 假设我们调用 + + make-decrementer + make_decrementer + + + 两次,使用相同的参数来创建两个 + + 过程: + 函数: + + + make_decrementer + +(define D1 (make-decrementer 25)) + +(define D2 (make-decrementer 25)) + + +const D1 = make_decrementer(25); + +const D2 = make_decrementer(25); + + + D1 + 和 + D2 + 是相同的吗?一个可以接受的答案是是,因为 + D1 + 和 + D2 + 具有相同的计算行为——每个都是一个 + + 过程 + 函数 + + ,用于从 25 中减去其输入。事实上, + D1 + 可以替换 + D2 + 于任何计算中而不改变结果。 + + + + 将此与两次调用 + + make-simplified-withdraw: + make_simplified_withdraw: + + + + make_simplified_withdraw + make_simplified_withdraw_example3 + +(define W1 (make-simplified-withdraw 25)) + +(define W2 (make-simplified-withdraw 25)) + + +const W1 = make_simplified_withdraw(25); + +const W2 = make_simplified_withdraw(25); + + + 是 +W1 和 + W2 相同吗?当然不是,因为调用 + W1W2 + 具有不同的效果,如以下交互序列所示: + + make_simplified_withdraw_example3 + make_simplified_withdraw_example4 + 5 + +(W1 20) + + +5 + + +W1(20); + + +5 + + + + make_simplified_withdraw_example4 + make_simplified_withdraw_example5 + -15 + +(W1 20) + + +-15 + + +W1(20); + + +-15 + + + + make_simplified_withdraw_example5 + make_simplified_withdraw_example6 + 5 + +(W2 20) + + +5 + + +W2(20); + + +5 + + + 即使 +W1 和 + W2 和 + make_simplified_withdraw(25) + 从某种意义上说是“相等”的,因为它们都是通过求值相同的表达式创建的, + + (make-simplified-withdraw 25), + + + make_simplified_withdraw(25), + + + 但这并不表示 +W1 可以被替换为 + W2 替代于任何表达式中而不改变该表达式的求值结果。 + + + + 一种支持在表达式中“相等可以替换为相等”而不改变表达式值的语言,被称为具有 + 引用透明性 + 透明性,引用 + 相等性引用透明性和 + 引用透明性。当我们在计算机语言中加入 + + set! + 赋值 + + 时,引用透明性被破坏。这使得通过替换等价表达式来简化表达式变得棘手。因此,关于使用赋值的程序的推理变得极其困难。 + + + + 一旦我们放弃引用透明性,计算对象“相同”的概念在形式上就难以捉摸。事实上,我们的程序所模拟的现实世界中“相同”的含义本身就不明确。通常,我们只能通过修改一个对象,然后观察另一个对象是否以同样的方式发生了变化,来判断两个看似相同的对象是否确实是“同一个”。但是,除了通过两次观察“相同”对象并查看对象的一些属性是否在两次观察之间有所不同之外,我们如何能够判断对象是否“改变”了?因此,没有某种先验的“相同性”概念,我们无法确定“改变”,而不观察改变的影响,我们无法确定相同性。 + + + + 作为编程中出现此问题的一个例子,考虑彼得和保罗有一个 + 银行账户联名 + 银行账户,上面有100。将其建模为 + + make_account + 50 + +(define peter-acc (make-account 100)) +(define paul-acc (make-account 100)) + + +const peter_acc = make_account(100); +const paul_acc = make_account(100); + + +const peter_acc = make_account(100); +const paul_acc = make_account(100); + +peter_acc("withdraw")(10); +peter_acc("withdraw")(20); +paul_acc("withdraw")(50); + + + 和将其建模为 + + make_account + 20 + +(define peter-acc (make-account 100)) +(define paul-acc peter-acc) + + +const peter_acc = make_account(100); +const paul_acc = peter_acc; + + +const peter_acc = make_account(100); +const paul_acc = peter_acc; +peter_acc("withdraw")(10); +peter_acc("withdraw")(20); +paul_acc("withdraw")(50); + + + 在第一个情况下,两个银行账户是不同的。彼得的交易不会影响保罗的账户,反之亦然。然而,在第二种情况下,我们将 + + paul-acc + paul_acc + + 定义为与 + + peter-acc + peter_acc + + 相同的事物。实际上,彼得和保罗现在有一个联名账户,如果彼得从 + + peter-acc + peter_acc + + 提款,保罗将在 + + paul-acc + paul_acc + + 中观察到较少的钱。这两个相似但不同的情况在构建计算模型时可能会引起混淆。特别是对于共享账户来说,一个对象(即银行账户)有两个不同的名称 + + + (peter-acc 和 + paul-acc); + + + (peter_acc 和 + paul_acc); + + + 如果我们正在寻找程序中可以改变的所有地方 + + paul-acc + paul_acc + + ,我们必须记住还要查看可能更改的内容【178:0†cn.txt】。 + peter-accpeter_acc单个计算对象被多个名称访问的现象称为 + 别名 + 别名。联名账户情况展示了别名的一个非常简单的例子。在节 + 我们将看到更复杂的例子,例如共享部分的“不同”复合数据结构。如果我们忘记对象的更改也可能因为 + 副作用错误 + 错误与别名的副作用 + 赋值相关的错误 + “副作用”而更改“不同”对象,因为这两个“不同”对象实际上是一个在不同别名下出现的单一对象,那么我们的程序中可能会出现错误。这些所谓的副作用错误是如此难以定位和分析,以至于有人提出编程语言应该设计成不允许副作用或别名 + Lampson, Butler + Morris, J. H. + Schmidt, Eric + Wadler, Philip + (Lampson et al.1981; + Morris, Schmidt, and Wadler 1980)。 + + + + + 参考上面关于“同一性”和“变化”的评论,注意,如果彼得和保罗只能查看他们的银行余额,而不能进行改变余额的操作,那么这两个账户是否不同就无关紧要了。一般来说,只要我们不修改数据对象,就可以将复合数据对象视为它各个部分的总和。例如,一个有理数是通过给定其分子和分母来确定的。但是,在存在变化的情况下,这种观点就不再有效,因为复合数据对象具有与其组成部分不同的“身份”。即使我们通过提款改变了余额,一个银行账户仍然是“同一个”银行账户;反过来,我们可能有两个不同的银行账户却具有相同的状态信息。这种复杂性不是由于我们的编程语言,而是因为我们将银行账户视为一个对象。例如,我们通常不把有理数视为具有身份且可更改的对象,好像我们能够改变分子但仍得到“相同”的有理数。 + 同一性与变化含义 + 变化与同一性含义 + + + + 命令式编程的陷阱 + + + + + 与函数式编程相对,广泛使用赋值的编程被称为 + 命令式编程 + 编程命令式 + 命令式编程。除了对计算模型引发复杂问题外,用命令式风格编写的程序还容易出现函数式程序中不会发生的错误。例如,回忆一下 + + + 节中的迭代阶乘程序: + + + 节中的迭代阶乘程序(这里使用条件语句而不是条件表达式): + + + + factorial_iterative + factorial_example + 120 + +(define (factorial n) + (define (iter product counter) + (if (> counter n) + product + (iter (* counter product) + (+ counter 1)))) + (iter 1 1)) + + +function factorial(n) { + function iter(product, counter) { + if (counter > n) { + return product; + } else { + return iter(counter * product, + counter + 1); + } + } + return iter(1, 1); +} + + + 与在内部迭代循环中传递参数不同,我们可以采用更具命令式风格的方式,通过使用显式赋值来更新变量的值 +product + 和 +counter : + + factorialwith assignment + factorial_imperative + factorial_example + 120 + +(define (factorial n) + (let ((product 1) + (counter 1)) + (define (iter) + (if (> counter n) + product + (begin (set! product (* counter product)) + (set! counter (+ counter 1)) + (iter)))) + (iter))) + + +function factorial(n) { + let product = 1; + let counter = 1; + function iter() { + if (counter > n) { + return product; + } else { + product = counter * product; + counter = counter + 1; + return iter(); + } + } + return iter(); +} + + + 这不会改变程序产生的结果,但确实引入了一个微妙的陷阱。我们如何确定赋值的顺序?碰巧,这个程序按照书写的顺序是正确的。但是,如果赋值顺序相反 + + +(set! counter (+ counter 1)) +(set! product (* counter product)) + + +counter = counter + 1; +product = counter * product; + + + 将会产生不同的, + 错误赋值顺序 + 赋值相关的错误 + 不正确的结果。通常,使用赋值进行编程迫使我们必须仔细考虑赋值的相对顺序,以确保每个语句使用的是已改变变量的正确版本。在函数式程序中,这个问题根本不会出现。考虑到这一点,具有讽刺意味的是,初学者编程通常是在一个高度命令式的风格下教授的。这可能是 20 世纪 60 年代和 70 年代普遍存在的信念的遗留,即调用 + + 过程 + 函数 + + 的程序必然会比执行赋值的程序效率低。 + (Steele (1977) + Steele, Guy Lewis Jr. + 驳斥了这种说法。)或者这可能反映了一种观点,即逐步赋值对于初学者而言比 + + 过程 + 函数 + + 调用更易于可视化。无论出于何种原因,这通常使初学者程序员陷入“我应该在这一个之前还是之后设置这个变量”的困扰,这可能会使编程变得复杂,并掩盖重要的思想。 + + + + 命令式程序的复杂性在我们考虑多个进程同时执行的应用程序时变得更加糟糕。我们将在节回到这个问题。然而,首先,我们将处理为涉及赋值的表达式提供计算模型的问题,并探讨具有局部状态的对象在设计模拟中的用途。 + + + + + + 考虑由 + + make-account, + make_account, + + + 使用习题中描述的密码修改后的 + + 想象银行系统需要能够进行 + 银行账户联名 + 联名账户的功能。定义一个 + + procedure + + 函数 + + 创建联名账户函数 + + make-joint + + 创建联名账户函数 + + 来完成此操作。 + + Make-joint + + 创建联名账户函数 make_joint + + + 应该接收三个参数。第一个是密码保护的账户。 + 第二个参数必须与定义账户时的密码匹配,以便 + + make-joint + + 创建联名账户函数 make_joint应 + + + 操作可以进行。第三个参数是新密码。 + + Make-joint + + 创建联名账户函数 make_joint + + + 是为了使用新密码为原始账户创建额外的访问权限。例如,如果 + + peter-acc + + 是为了使用新密码为原始账户创建额外的访问权限。例如,如果 + peter_acc + + 是一个拥有密码保护的银行账户,密码为 + + 是一个拥有密码保护的银行账户,密码为 + + open-sesame, + "open sesame", + + + ,那么 + + make_joint_example + +(define paul-acc + (make-joint peter-acc 'open-sesame 'rosebud)) + + +const paul_acc = make_joint(peter_acc, "open sesame", "rosebud"); + + +// make_joint to be written by students +const paul_acc = make_joint(peter_acc, "open sesame", "rosebud"); + + + 将允许在 + + peter-acc + peter_acc + + 上使用名称 + + + paul-acc + paul_acc + + 和密码 + + + rosebud。 + + + "rosebud"。 + + + 您可能希望修改您的解决方案以适应这一新功能 + 习题。 + + + 创建联名账户函数 + joint_acc_example + 40 + +function 创建联名账户函数(linked_acc, linked_pw, joint_pw) { + return (message, input_pw) => { + + // 检查联名账户的身份验证 + if (input_pw !== joint_pw) { + return x => "错误的联名账户密码"; + } else { + const access_linked = linked_acc(message, linked_pw); + + // 检查关联账户的身份验证 + if (access_linked(0) === "密码不正确") { + // access_linked(0) 执行存款 / 取款为 0 + // 以检测 "密码不正确" 信息。 + return x => "错误的关联账户密码"; + } else { + // 所有身份验证都通过,返回访问的账户给用户 + return access_linked; + } + } + }; +} + + + + joint_acc_example + make_account_password_protected + +const peter_acc = make_account(100, "open sesame"); +peter_acc("withdraw", "open sesame")(10); // 提款 10 +peter_acc("withdraw", "ben")(40); // 密码错误 + +// 创建联名账户 +const paul_acc = 创建联名账户函数(peter_acc, "open sesame", "rosebud"); + +paul_acc("withdraw", "rosebud")(50); // 提款 50,应返回 40 + + + + + + + + 当我们在节中定义求值模型时,我们说 + 求值顺序JavaScript在 JavaScript 中 + 求值顺序与赋值 + 求值表达式的第一步是求值其子表达式。但是,我们从未指明子表达式应该按照何种顺序求值(例如,从左到右或从右到左)。 + + + 当我们引入 + 赋值时,过程的参数求值顺序会对结果产生影响。 + + + 当我们引入赋值时,运算符组合的操作数求值顺序会对结果产生影响。 + + + 定义一个简单的 + + 过程 + 函数 + + f 使得对 + + (+ (f 0) (f 1)) + f(0) + f(1) + + 的求值会返回 0 如果 + + + 的参数 + + 的运算对象 + + 是从左到右求值的,但如果 + + 参数 + 运算对象 + + 是从右到左求值则会返回 1。 + + + + + exercise_3_8_solution_example + +let v = -0.5; +function f(x) { + v = x + v; + return v; +} + + + + + exercise_3_8_solution_example + +// 分别尝试这些 +display(f(0) + f(1)); // 返回 0 +display(f(1) + f(0)); // 返回 1 + + + + + + 赋值 + 局部状态 + 赋值代价 + diff --git a/xml/cn/chapter3/section2/section2.xml b/xml/cn/chapter3/section2/section2.xml new file mode 100644 index 000000000..35ce5a5d6 --- /dev/null +++ b/xml/cn/chapter3/section2/section2.xml @@ -0,0 +1,195 @@ +
+ 求值的环境模型 + + + + + 求值的环境模型 + + + 当我们在一章中介绍复合 + 过程 + 函数 + 时,我们使用求值的代换模型 + (在节)来定义应用过程函数到参数的意义: +
    +
  • 要应用一个复合 + 过程 + 函数 + 到参数,需将 + 过程体 + 函数的返回表达式(更广泛地说,函数体) + 中的每个 + 形式化 + + 参数替换为对应的实参。 +
  • +
+
+ + + 一旦我们将赋值引入我们的编程语言,这样的定义就不再足够。特别是,节指出,在存在赋值的情况下, + + + 变量不能仅仅被视为一个值的名字。相反,变量必须以某种方式指示一个可以存储值的位置。 + + + 名字不能仅仅被视为表示一个值。相反,名字必须以某种方式指示一个可以存储值的位置。 + + + 在我们新的求值模型中,这些位置将维持在称为环境 + 环境的结构中。 + + + + 环境是帧(环境模型)的序列。每个帧是绑定绑定的表(可能为空),它将 + + 变量名 + + + 名称 + + 与其对应的值关联起来。 + + + (单个帧中最多只能包含对任何变量的一个绑定。) + + + (单个帧中最多只能包含对任何名称的一个绑定。) + + 每个帧还有一个指针指向其封闭环境环境封闭封闭环境,除非为了讨论的目的,帧被认为是全局帧帧(环境模型)全局全局。相对于一个环境的 + + 变量的值 + + + 名称名称的值 + + 是由环境中第一个包含该 + + 变量 + + + 名称 + + 绑定的帧所给出的。如果序列中的没有任何帧为该 + + 变量, + + + 名称, + + 指定绑定,那么就说该 + + 变量 + + + 名称 + + 是未绑定的。 未绑定名称 + 名称未绑定 + 未绑定 在环境中。 + + +
+
+ + 一个简单的环境结构。 +
+ + +
+
+ + + 一个简单的 + 求值的环境模型环境结构 + 环境结构。 + +
+
+
+
+ + + + + 图 + + + 图 + + + 展示了一个简单的环境结构,由三个帧组成,标记为 I、II 和 III。在图中,A、B、C 和 D 是指向环境的指针。C和 D 指向相同的环境。 + + 变量 + + + 名称 + + z 和 x 被绑定在帧 II,而 y 和 x 被绑定在帧 I。在环境 D 中,x 的值是 3。相对于环境 B,x 的值也是 3。其确定方式如下:检查序列中的第一个帧(帧 III),未找到x的绑定,因此继续到封闭环境 D,并在帧 I 找到绑定。另一方面,在环境 A 中,x 的值是 7,因为序列中的第一个帧(帧 II)包含将x绑定到 7 的绑定。相对于环境 A,将x绑定到帧 II 的 7 被认为遮蔽一个绑定遮蔽了在帧 I 中将x绑定到 3 的绑定。 + + + + 环境对求值过程至关重要,因为它决定了表达式应该在何种上下文中进行求值。实际上,可以说编程语言中的表达式本身没有任何意义。相反,表达式仅在其被求值的某个环境中才能获得意义。 + + + 即使是像(+11)这样简单的表达式的解释也依赖于理解+是加法的符号这一上下文。 + + + 即使是像display(1)这样简单的表达式的解释也依赖于理解名称display指的是显示一个值的原语函数的上下文。 + + + 因此,在我们的求值模型中,我们总是会提到在某个环境中求值表达式。为了描述与解释器的交互,我们假设存在一个 + 全局环境 + 全局环境,由一个包含原语 + + 过程 + + + 函数 + + 符号值的单一帧(无封闭环境)组成。 + 例如,理念是 + + + + + 是加法符号是通过说符号+ + + + display 是指代显示的原语函数的名称是通过说名称display + + + 在全局环境中绑定到原语 + + 加法过程。 + 显示函数。 + + + + + + + 在我们对程序进行求值之前,我们通过一个新的帧程序帧扩展全局环境,从而形成 + 程序环境 + 程序环境。我们将在程序顶层声明的名称,即在任何块之外的名称,添加到这个帧中。然后,给定程序相对于程序环境进行求值。 + + + + + + + &subsection3.2.1; + + + &subsection3.2.2; + + + &subsection3.2.3; + + + &subsection3.2.4; + + + &subsection3.2.5; + +
diff --git a/xml/cn/chapter3/section2/subsection1.xml b/xml/cn/chapter3/section2/subsection1.xml new file mode 100644 index 000000000..deddaf6a0 --- /dev/null +++ b/xml/cn/chapter3/section2/subsection1.xml @@ -0,0 +1,568 @@ + + + 求值规则 + + + + 求值的环境模型求值规则 + + + + 关于解释器如何 + 函数应用环境模型的 + 求值的环境模型函数应用 + 对 + + + 组合式 + + + 函数应用 + + + 的求值的总体规范保持不变,如我们在
中首次介绍的那样: +
    +
  • + 对 + + + 组合式进行求值: + + + 函数应用进行求值: + + +
      +
    1. + 对 + + + 组合式的子表达式求值。赋值在求值规则的第1步中引入了微妙之处。如练习所示,赋值的存在使我们能够编写根据子表达式在组合式中求值的求值顺序与实现相关不同而产生不同值的表达式。因此,为了精确起见,我们应该在第1步中指定一个求值顺序(例如,从左到右或从右到左)。然而,这种顺序应该始终被视为一种实现细节,绝不应编写依赖某种特定顺序的程序。例如,一个复杂的编译器可能通过改变子表达式的求值顺序来优化程序。ECMAScript标准指定从左到右对子表达式进行求值。 + + + 应用的子表达式求值。赋值在求值规则的第1步中引入了一种微妙之处。如练习所示,赋值的存在使我们能够编写根据组合式中子表达式的求值顺序产生不同值的表达式。为了消除这种歧义,求值顺序JavaScript在JavaScript中 JavaScript指定组合式的子表达式和应用的参数表达式的从左到右求值。 + + +
    2. +
    3. + 将 + + + 运算符 + + + 函数 + + + 子表达式的值应用到 + + + 操作数 + + + 参数 + + + 子表达式的值上。 +
    4. +
    +
  • +
+ 求值的环境模型替代了代换模型,用于指定应用复合函数的含义 + + 过程 + 函数 + + 到参数。 +
+ + + 在求值的环境模型中, + + 过程 + 函数 + + 始终是由一些代码和一个指向环境的指针组成的组合体。 + + 过程 + 函数 + + 的创建只有一种方式:通过对 + + lambda + lambda + + 表达式求值。 + lambdalambda 表达式一起创建 + 这产生了一个 + + 过程 + 函数 + + 其代码是从 + + lambda + lambda + + 表达式的文本中获得的,其环境是对 + + lambda + lambda + + 表达式进行求值以生成 + + 过程。 + 函数。 + + 例如,考虑以下 + + 过程定义。 + 函数声明。 + + square环境在环境模型中 + + square_example + 196 + +(define (square x) + (* x x)) + + +function square(x) { + return x * x; +} + + + 在 + + + 全局 + + + 程序 + + + 环境中求值。 + + 过程定义 + 函数声明 + + 语法是 + + 只是语法糖 + + 等同于 + + + 底层隐式 + + lambda + lambda + + 表达式的语法形式。 + 这将等同于使用 + 脚注在第 1 章中 + 提到在完整的 JavaScript 中二者之间的微妙差别,我们将在本书中忽略这些差别。 + + square_example + 196 + +(define square + (lambda (x) (* x x))) + + +const square = x => x * x; + + + 对 + + (lambda (x) (* x x)) + + + x => x * x + + + 求值并绑定 + square 绑定到结果值,全部在 + + + 全局 + + + 程序 + + + 环境中。 + + + + + + 图 + + + 图 + + + 显示了评估此 + 声明环境模型的 + + define 表达式。 + 声明语句。 + + + + + + 全局环境包围程序环境。为了减少混乱,在这幅图之后我们将不再显示全局环境(因为它始终相同),但我们通过程序环境向上的指针提醒其存在。 + + + 这个 + + 过程 + 函数 + + 对象是一个组合体,其代码指定 + + 过程 + 函数 + + 具有一个 + 形式 + 参数,即 +x , 和 + + 过程 + 函数 + + 体 + + (* x x). + return x * x;. + + + 这个 + + 过程 + 函数 + + 的环境部分是指向程序环境的指针,因为这是求值 + + lambda + lambda + + 表达式以产生 + + 过程 + 函数 + + 的环境。一个新的绑定,它将 + + 过程 + 函数 + + 对象与一个 + + + 符号 + + + 名称 + + + square ,已添加到程序帧中。 + + + 一般而言,define 通过 + 将绑定添加到帧来创建定义。 + + + + + + + +
+
+ + + 在全局环境中评估 + (define (square x) (* x x)) 产生的 + 环境结构。 + +
+ + +
+
+ + + 在程序环境中评估 + function square(x) { return x * x; } + 产生的环境结构。 + +
+
+
+
+ + + + + + + 通常,const, + function和 + let + 会将绑定添加到帧中。 + 对常量的赋值是被禁止的,因此我们的环境模型需要将指向常量的名称与指向变量的名称区分开来。我们通过在冒号后面添加一个等号来表示名称是一个常量。 + 我们将函数声明视为与常量声明等效;我们在第 1 章的脚注中提到,在完整的 JavaScript 语言中允许对函数声明的名称进行赋值。请注意图中冒号后的等号。 + + + + + 现在我们已经看到了 + + 过程 + 函数 + + 是如何创建的,我们可以描述 + + 过程 + 函数 + + 是如何应用的。环境模型规定:要将 + + 过程 + 函数 + + 应用于参数,创建一个新的环境,其中包含一个绑定参数到参数值的帧。这个帧的外围环境是由 + + 过程。 + 函数。 + 指定的环境。现在,在这个新环境中,评估 + + 过程 + 函数 + + 体。 + + + +要显示如何遵循这一规则, + + + 图 + + + 图 + + + 说明了通过求值表达式 + + (square 5) + square(5) + + + 在 + + 全局 + 程序 + + 环境中创建的环境结构,在哪里 +square 是在 + + 过程 + 函数 + + 中生成的 + + + 图. + + + 图. + + + 应用 + + 过程 + 函数 + + 结果是产生一个新环境,图中标记为 E1,其始于一个框架,其中 +x , + + 形式 + + 参数在 + + 过程中, + 函数中, + + 被绑定到参数5。 + + + 请注意,环境 E1 中的名称 x 后面跟有一个冒号,但没有等号,指示参数 x 被视为变量。该例子并未利用该参数是变量这一事实,但请回忆在第节中的函数 make_withdraw 依赖于其参数是变量。 + + + + 从该框架向上的指针表明该框架的外围环境是 + + 全局 + 程序 + + 环境。选择 + + 全局 + 程序 + + 环境是因为这是指示为一部分的环境 +square + + 过程 + 函数 + + 对象。在 E1 中,我们求值 + + 过程, + 函数, + + + (* x x). + return x * x;. + + + 体。由于 +x 在 E1 中是 5,结果是 + + (*55), + 5 * 5, + + 或25。 + square环境在环境模型中 + + +
+
+ + + 在全局环境中求值 + (square 5)所创建的环境。 + +
+ + +
+
+ + + 在程序环境中求值 + square(5) + 所创建的环境。 + +
+
+
+
+ + + 求值的环境模型 + + 过程 + 函数 + + 应用可归纳为两个规则: +
    +
  • + A + + 过程 + 函数 + + 对象是通过构建一个帧来应用于一组参数的, + + 将该过程的形式参数绑定到调用的参数 + 将函数的参数绑定到调用的参数 + + ,然后在新构造的环境中评估 + + 过程 + 函数 + + 体。新框架的外围环境是所应用的 + + 过程 + 函数 + + 对象的环境部分。 + 应用的结果是在评估函数体时遇到的第一个返回语句的返回表达式的评估结果。 +
  • +
  • + A + + 过程 + 函数 + + 是通过在给定环境中评估 + lambdalambda 表达式一起创建 + + lambda + lambda + + 表达式创建的。结果的 + lambda 表达式 + + 过程 + 函数 + + 对象是由 + + lambda + lambda + + 表达式的文本和一个指向创建该 + + 过程 + 函数 + + 环境的指针组成的组合体。 +
  • +
+
+ + + + + define环境模型的 + 我们也指定使用define定义一个符号在当前环境帧中创建一个绑定,并将指示的值赋给该符号。 + + + + 我们在 JavaScript 版本中省略这一点,因为在第节中有更详细的讨论。 + + + + + + define环境模型的 + 对表达式(set! + variable value) + 在某个环境中进行求值会定位到环境中变量的绑定。为此,需要找到环境中包含该变量绑定的第一个帧并对其进行修改。如果变量在环境中没有绑定,则set!会发出错误信号。 + + + 赋值求值的 + 最后,我们规定了赋值的行为,这个操作最初迫使我们引入了环境模型。对表达式 + name=value + 在某个环境中的求值会定位到环境中名称的绑定。也就是说,需要找到环境中包含该名称绑定的第一个帧。 + 如果绑定是变量绑定——在帧中用名称后仅有:表示——该绑定会被改变以反映变量的新值。 + 否则,如果帧中的绑定是常量绑定——在帧中用名称后紧跟:=表示——赋值会发出"assignment to constant"错误信号。 + 如果名称在环境中没有绑定,则赋值会发出"variable undeclared"错误信号。 + + + + + + 尽管这些求值规则比代换模型复杂得多,但仍然相当简单。此外,尽管求值模型是抽象的,但它正确描述了解释器如何对表达式求值。在第章中,我们将看到这个模型如何可以作为实现有效解释器的蓝图。以下各节通过分析一些示例程序来详细说明该模型的细节。 + 求值的环境模型求值规则 + +
diff --git a/xml/cn/chapter3/section2/subsection2.xml b/xml/cn/chapter3/section2/subsection2.xml new file mode 100644 index 000000000..ab933ed26 --- /dev/null +++ b/xml/cn/chapter3/section2/subsection2.xml @@ -0,0 +1,103 @@ + + 应用简单 过程 函数 + + + 求值的环境模型过程函数-应用示例 + 函数应用环境模型 + + + sum_of_squares环境在环境模型 当我们在部分引入代换模型时,我们展示了 组合 (f 5) 函数应用 f(5) 在给定以下 过程定义: 函数声明: 的情况下计算结果为136【16:0†source】 + f_example + 136 + +(define (square x) + (* x x)) + +(define (sum-of-squares x y) + (+ (square x) (square y))) + +(define (f a) + (sum-of-squares (+ a 1) (* a 2))) + + +function square(x) { + return x * x; +} +function sum_of_squares(x, y) { + return square(x) + square(y); +} +function f(a) { + return sum_of_squares(a + 1, a * 2); +} + + 我们可以使用环境模型分析相同的示例。 图显示了通过求值定义创建的三个 过程 函数 对象【16:2†source】f, square ,和 sum-of-squares sum_of_squares 全局 程序 环境中。每个 过程 函数 对象由一些代码以及指向 全局 程序 环境的指针组成。
全局框架中的过程对象。
程序框架中的函数对象。
+ + + <!-- 图像稍后为 SICP JS 分页而更改 --> <!-- 图像代码在本文件的 PDF_ONLY 中重复 -->
通过图中的过程计算 (f 5) 所创建的环境。
通过图中的函数计算 f(5) 所创建的环境。
中,我们看到通过求值表达式 (f 5). f(5). 所创建的环境结构。对 f 创建了一个新环境,E1,始于一个帧 a形式参数 f ,被绑定到参数 5。在 E1 中,我们 评估的主体【40:1†source】 f + +(sum-of-squares (+ a 1) (* a 2)) + + +return sum_of_squares(a + 1, a * 2); + + + 为了求值 这个组合,我们首先对子表达式进行求值。 返回语句,我们首先对返回表达式的子表达式进行求值。 第一个子表达式, sum-of-squares, sum_of_squares, 的值是一个 过程 函数 对象。(注意如何找到这个值:我们先在 E1 的第一个帧中查找,其中不包含 sum-of-squares 的绑定。 sum_of_squares的绑定。 然后我们转到封闭环境,即 全局 程序 环境,并找到在 中显示的绑定。) 中的绑定。) 另外两个子表达式通过应用原始运算进行求值【16:0†source】 +* 为了对两个组合进行求值 (+ a 1) a + 1 (* a 2) a * 2 分别得到6和10。
+ + 现在我们应用 过程 函数 对象 sum-of-squares sum_of_squares 到参数6和10。这会产生一个新环境,E2,其中 形式参数 xy 被绑定到参数。在 E2 中我们求值 组合式 (+ (square x) (square y))。 语句 return square(x) + square(y); 这导致我们求值 (square x), square(x), 其中 【16:5†source】 square 全局 程序 帧中找到和 x 是 6。再一次,我们设置一个新环境,E3,其中 【70:0†source】 x 被绑定到 6,并在其中我们评估的函数体 【74:12†cn.txt】 square ,它是 (* x x). return x * x;. 同时在应用 sum-of-squares, sum_of_squares, 时,我们必须对子表达式求值 (square y), square(y), 其中 【16:0†source】 y 是 10。第二次调用 【16:1†source】square 创建了另一个环境,E4,其中 x形式参数 被绑定到 参数 6,并在我们评估的函数体中 【90:15†source】 square ,被绑定到 10。并且在 E4 中,我们必须 求值 (* x x). return x * x;. 【74:12†cn.txt】 + + 需要注意的重要一点是,每次调用 square 都会创建一个包含 x 绑定的新环境。我们可以在这里看到,不同的帧如何用来分开所有命名为 x 的不同局部变量。注意,由 square 创建的每个帧都指向 全局 程序 环境,因为这是由 square 过程 函数 对象指示的环境。 + + 在子表达式求值后,结果会被返回。通过两次调用square生成的值被 sum-of-squares, sum_of_squares, 相加并返回给 f。因为我们这里的重点是环境结构,所以我们不会详细讨论这些返回值是如何从一次调用传递到另一次调用的;然而,这也是求值过程中的一个重要方面,我们将在章中详细讨论。 sum_of_squares环境在环境模型 + + + 在章节中,我们使用代换模型来分析两个 递归过程与迭代过程比较 迭代过程与递归过程比较 过程 函数 用于计算 factorial评估中的环境结构 阶乘,一个递归版本 【106:3†cn.txt】 + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* n (factorial (- n 1))))) + + +function factorial(n) { + return n === 1 + ? 1 + : n * factorial(n - 1); +} + + 和一个迭代版本【110:6†cn.txt】 + factorial_example + 120 + +(define (factorial n) + (fact-iter 1 1 n)) + +(define (fact-iter product counter max-count) + (if (> counter max-count) + product + (fact-iter (* counter product) + (+ counter 1) + max-count))) + + +function factorial(n) { + return fact_iter(1, 1, n); +} +function fact_iter(product, counter, max_count) { + return counter > max_count + ? product + : fact_iter(counter * product, + counter + 1, + max_count); +} + + 显示由评估 (factorial 6) factorial(6) 使用每个版本 【114:0†cn.txt】 factorial + 过程函数 使用环境模型无法明确我们在章节中关于解释器可以在有限空间内通过尾递归执行类似 过程 函数 fact-iter fact_iter 的说法。我们将在章节中处理解释器的控制结构时讨论 求值的环境模型尾递归与 尾递归求值的环境模型与 尾递归。 + + 求值的环境模型过程函数-应用示例 + 函数应用环境模型 + + <!-- 图像从之前的位置移到这里以便于 SICP JS 排版 --> <!-- 图像代码是本文件中 WEB_ONLY 之前代码的复制 -->
通过求值 (f 5) 以及图中的过程创建的环境。
通过求值 f(5) 以及图中的函数创建的环境。
+ +
diff --git a/xml/cn/chapter3/section2/subsection3.xml b/xml/cn/chapter3/section2/subsection3.xml new file mode 100644 index 000000000..8bcd66011 --- /dev/null +++ b/xml/cn/chapter3/section2/subsection3.xml @@ -0,0 +1,655 @@ + + 帧(作为局部状态的存储库) + + + 帧 (环境模型)作为局部状态的存储库 + 局部状态在帧中维护 + 求值的环境模型局部状态 + + + 我们可以通过环境模型来查看 + + 过程 + 函数 + + 和赋值如何用于表示具有局部状态的对象。例如,考虑通过调用make_withdrawenvironment在环境模型中创建的section中的取款处理器 + + 过程 + 函数 + + + make_withdraw2 + +(define (make-withdraw balance) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds"))) + + +function make_withdraw(balance) { + return amount => { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "insufficient funds"; + } + }; +} + + + 让我们描述一下求值 + + make_withdraw2 + make_withdraw2_w1_declare + +(define W1 (make-withdraw 100)) + + +const W1 = make_withdraw(100); + + + 接着是 + + make_withdraw2_w1_example + make_withdraw2_w1_declare + 50 + +(W1 50) + + + 50 + + +W1(50); + + +50 + + + + + 图 + + + 图 + + + 显示了 + + 定义 + make-withdraw + 声明 + make_withdraw + + + + 过程 + 函数 + + 在 + + 全局 + 程序 + + 环境中的结果。这产生了一个包含对 + + 全局 + 程序 + + 环境的指针的 + + 过程 + 函数 + + 对象。到目前为止,这与我们已经看到的例子没有什么不同,除了 + + + 过程体本身是一个 + lambda + 表达式。 + + + 函数体中的返回表达式本身是一个 + lambda 表达式。 + + + + +
+
+ + 在全局环境中定义make-withdraw的结果。 + + +
+ + +
+
+ + 在程序环境中定义make_withdraw的结果。 + + +
+
+
+
+ + + 计算的有趣部分发生在我们应用 + 过程 + 函数 + + make-withdraw + make_withdraw + + 到一个参数: + + +(define W1 (make-withdraw 100)) + + +const W1 = make_withdraw(100); + + + 我们像往常一样开始,通过设置一个环境 E1,其中的 + 形式 参数 +balance + 绑定到参数 100。在这个环境中,我们对 + + make-withdraw, + + make_withdraw, + + + 的函数体进行求值,主要是对 + + lambda 表达式。这 + 返回语句,其中返回表达式是 + 一个 lambda 表达式。对这个 lambda 表达式的求值 + + 构造一个新的 + + 过程 + 函数 + + 对象,其代码如 + + lambda + lambda 表达式 + + 中所指定,其环境是 E1,也是在这个环境中 + + lambda + lambda 表达式 + + 被求值以生成 + + 过程。 + 函数。 + + 由调用 + make-withdraw + + make_withdraw + + 返回的值就是这个结果 +W1 + 在 + 全局 + 程序 + + 环境中,因为 + define + 常量声明 + + 本身正在 + 全局 + 程序 + + 环境中被求值。 + + + 图 + + + 图 + + + 显示了最终的环境结构。 + + +
+
+ + 在 + (define W1 (make-withdraw 100)) + 上求值的结果。 + + +
+ + +
+
+ + 在 + const W1 = make_withdraw(100); + 上求值的结果。 + + +
+
+
+
+ + + 现在我们可以分析当 +W1 + 被应用于一个参数: + + +(W1 50) + + + 50 + + +W1(50); + + +50 + + + 我们开始构造一个帧,其中 +amount , + 形式参数 +W1 ,被绑定到参数 50。需要注意的关键点是,这个帧的外围环境不是 + 全局 + 程序 + + 环境,而是环境 E1,因为这是由 +W1 + + + 过程 + 函数 + + 对象。在这个新环境中,我们对 + + 过程: + 函数: + + 的主体进行求值。 + + +(if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds") + + +if (balance >= amount) { + balance = balance - amount; + return balance; +} else { + return "insufficient funds"; +} + + + 生成的环境结构如 + + + 图. + + + 图. + + + 所示。正在求值的表达式引用了两个 +amount + 和 +balance. + + + Amount + 变量 amount + + + 将在该环境中的第一个帧中找到,并且 +balance +将在 E1 中通过跟随外层环境指针找到。 + + +
+
+ + 通过应用过程对象 W1 创建的环境。 + + +
+ + +
+
+ + 通过应用函数对象 W1 创建的环境。 + + +
+
+
+
+ + + 当 + + set! + 赋值 + + 被执行时,绑定 +balance 在 E1 中被更改。 在调用结束时 +W1, balance 在 E1 中,50,包含的帧 balance +仍然由 + + 过程 + 函数 + +对象指向 +W1 +仍然由包含的帧绑定。 +amount ,绑定的帧(我们执行更改的代码) +balance +) 不再相关,因为构造它的 + 过程 + 函数 + +调用已经结束,并且没有来自环境其他部分的指向该帧的指针。下一次 +W1 +被调用时,这将构建一个新的帧,该帧绑定 +amount +并且其外层环境是 E1。我们看到 E1 作为持有 + + 过程 + 函数 + +对象的局部状态变量的位置 +W1. + + + + 图 + + + 图 + + +显示了调用后的情况 +W1. + + + + 图 + + + 图 + + +显示了调用后的情况 + + +
+
+ + 调用 + W1 + W1 + + 后的环境。 + + +
+ + +
+
+ + 调用后产生的环境。 + + +
+
+
+
+ + \pagebreak + + +观察创建第二个取款对象时会发生什么,通过再次调用 + + make_withdraw: + make_withdraw: + + + + make_withdraw2 + make_withdraw2_w2_declare + 20 + +(define W2 (make-withdraw 100)) + + +const W2 = make_withdraw(100); + + +const W1 = make_withdraw(100); +W1(50); +const W2 = make_withdraw(100); +W2(80); + + +这产生了 + + + 图, + + + 图, + + +的环境结构,显示了 +W2 是一个过程函数对象,即,一个包含一些代码和一个环境的对。环境 E2【152:8†cn.txt】W2 是由调用 make-withdraw. make_withdraw. 创建的。它包含一个帧,其中具有自己的局部绑定 【156:0†cn.txt】。balance +这产生了 + + + 图, + + + 图, + + +的环境结构,显示了它是一个包含一些代码和环境的 + 过程 + 函数 +对象。环境 E2 是由调用 make-withdraw. make_withdraw. 创建的。它包含一个帧,其中具有自己的局部绑定。另一方面, 【152:8†cn.txt】 【156:0†cn.txt】 【160:0†cn.txt】W1W2 拥有相同代码:由 lambda lambda make-withdraw</SCHEMEINLINE>make_withdraw 的主体中指定的代码。这是一个实现细节,无论 W1 和 W2 是否共享计算机中相同的物理代码, 还是每个保留一份代码的副本。在我们在章节 中实现的解释器中,代码实际上是共享的。在这里我们看到为什么【168:0†cn.txt】。W1 具有相同的代码:即 lambda lambda make-withdraw</SCHEMEINLINE>make_withdraw 主体中指定的代码。在章节 中实现说明性知识通过共享方式来实现。这是一个实现细节,无论 W1 和 W2 是否共享代码 【172:0†cn.txt】。W2 +作为独立对象行为。调用 +W1 引用状态变量balance 存储在 E1,而引用 【184:0†cn.txt】。W2 引用 balance 存储在 E2。因此,一个对象的局部状态的变化不会影响另一个对象。 + + +
+
+ + 使用(define W2 (make-withdraw 100))来创建第二个对象。 + + +
+ + +
+
+ + 使用const W2 = make_withdraw(100);来创建第二个对象。 + + +
+
+
+
+ + + + 存储在 E2。因此,一个对象的局部状态的变化不会影响另一个对象。 + + +
+
+ + 使用(define W2 (make-withdraw 100))来创建第二个对象。 + + +
+ + +
+
+ + 使用const W2 = make_withdraw(100);来创建第二个对象。 + + +
+
+
+balance +作为 + make-withdraw的参数创建。 + make_withdraw的参数创建。 + + +我们也可以使用 + 显式地 + 分别地, + +使用 + let, + 我们称为lambda 表达式立即调用立即调用 lambda 表达式立即调用 lambda 表达式 + +创建局部状态变量,如下所示: + + make_withdrawusing immediately invoked lambda expression + make_withdraw3 + make_withdraw3_example + 20 + +(define (make-withdraw initial-amount) + (let ((balance initial-amount)) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")))) + + +function make_withdraw(initial_amount) { + return (balance => + amount => { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "insufficient funds"; + } + })(initial_amount); +} + + + + make_withdraw3_example + +const W1 = make_withdraw(100); +W1(80); + + + + + + 回忆在章节中,let语法糖作为语法糖let求值模型let只是语法糖,类似于一个 过程函数 的调用: + + +(let ((var exp)) body) + + + 被诠释为 + +((lambda (var) body) exp) + + 的替代语法。 + + + 外部 lambda 表达式在评估后立即调用。其唯一目的是创建一个局部变量balance并将其初始化为initial_amount。 + + +使用环境模型来分析这个替代版本 【204:0†source】【204:5†source】【204:6†source】。make_withdraw ,绘制如上图像以说明交互 + make_withdraw3_example_2 + make_withdraw3 + 20 + +(define W1 (make-withdraw 100)) + +(W1 50) + +(define W2 (make-withdraw 100)) + + +const W1 = make_withdraw(100); + +W1(50); + +const W2 = make_withdraw(100); + + +const W1 = make_withdraw(100); +W1(50); +const W2 = make_withdraw(100); +W2(80); + + +显示两个版本的 + + make-withdraw + make_withdraw + +创建具有相同行为的对象。这两个版本的环境结构有何不同? + +
    +
  • 黑色显示练习 3.10 中函数的环境结构
  • +
  • 绿色显示原始环境结构的差异(其中make_withdraw被替换为图 3.9 版本)
  • +
+
+ +
+
+
+ + 帧 (环境模型)作为局部状态的存储库 + 作为 + make-withdraw + make_withdraw + +的参数创建。我们也可以创建局部状态变量为 + 显式地 + 分别地 + +使用 + let + + ,即立即调用 lambda 表达式 + + +如下: + + 求值的环境模型局部状态 + make_withdrawenvironment在环境模型中 +
diff --git a/xml/cn/chapter3/section2/subsection4.xml b/xml/cn/chapter3/section2/subsection4.xml new file mode 100644 index 000000000..4de37a099 --- /dev/null +++ b/xml/cn/chapter3/section2/subsection4.xml @@ -0,0 +1,619 @@ + + + + 内部 + + 定义 + 声明 + + + + + 块结构环境在环境模型中 + 求值的环境模型内部声明 + 内部声明环境在环境模型中 + + + + + 在本节中,我们处理包含声明的函数体或其他块(例如条件语句的分支)的求值。每个块为块中声明的名称开启一个新的作用域。为了在给定的环境中求值一个块,我们通过一个新的框架扩展该环境,该框架包含在块体中直接声明的所有名称(即,不在嵌套块内),然后在新构建的环境中求值该块体。 + + + + + 第 节介绍了 + + 过程 + 函数 + + 可以有内部 + + + 定义 + + + 声明 + + + ,从而形成一个块结构,如在 + sqrt环境在环境模型中 + 计算平方根的下面的 + + 过程 + 函数 + + : + + another_sqrt + abs_definition + square_definition + average_definition + sqrt_example7 + 2.2360688956433634 + +(define (sqrt x) + (define (good-enough? guess) + (< (abs (- (square guess) x)) 0.001)) + (define (improve guess) + (average guess (/ x guess))) + (define (sqrt-iter guess) + (if (good-enough? guess) + guess + (sqrt-iter (improve guess)))) + (sqrt-iter 1.0)) + + +function sqrt(x) { + function is_good_enough(guess) { + return abs(square(guess) - x) < 0.001; + } + function improve(guess) { + return average(guess, x / guess); + } + function sqrt_iter(guess){ + return is_good_enough(guess) + ? guess + : sqrt_iter(improve(guess)); + } + return sqrt_iter(1); +} + + + 现在我们可以使用环境模型来理解这些内部 + + + 定义 + + + 声明 + + + 是如何按预期行为的。 + + + 图 + + + 图 + + + 显示在求值表达式 + + (sqrt 2) + sqrt(2) + + 时,内部 + + 过程 + 函数 + + + good-enough? + is_good_enough + + 首次被调用的时刻。 +guess 等于1. + + +
+
+ + sqrt 过程含有内部定义。 + + +
+ + +
+
+ The + sqrt 函数含有内部声明。 + + +
+
+
+
+ + + 观察环境的结构。 + + + Sqrt 是全局环境中的一个符号,被绑定为 + + + 名称 + sqrt 已在程序环境中绑定 + + + 到一个 + + 过程 + 函数 + + 对象,其关联的环境是 + + 全局 + 程序 + + 环境。当 +sqrt 被调用时,一个新的环境 E1 被形成,隶属于 + + 全局 + 程序 + + 环境,其中参数 x 被绑定到2。身体的 sqrt 然后在E1 中求值。 + + + 由于sqrt体中的第一个表达式是 + + + 该段是一个包含局部函数声明的块,因此 E1 被扩展为一个新的帧用于这些声明,结果形成了一个新环境 E2。块体随后在 E2 中求值。因为体中的第一条语句是 + + + + abs_definition + square_definition + +(define (good-enough? guess) + (< (abs (- (square guess) x)) 0.001)) + + +function is_good_enough(guess) { + return abs(square(guess) - x) < 0.001; +} + + + + + 求值这个表达式定义了在环境E1 中的过程 + good-enough?。 + + + 求值这个声明在环境E2 中创建了函数 + is_good_enough。 + + + + + 更精确地说,符号 + good-enough? 被添加到 E1 的第一个帧, + 与一个过程对象绑定,其关联的环境是 E1。 + + + 更精确地说, + 名称 is_good_enough + 在 E2 的第一个帧中被绑定到一个函数 + 对象,其关联的环境是 E2。 + + + 类似地, + improve and + + sqrt-iter + sqrt_iter + + were defined as + + procedures in E1. + functions in E2. + + For conciseness, + + + figure + + + figure + + + shows only the + + procedure + function + + object for + + good-enough?. + is_good_enough. + + + + + 在本地 + + 过程 + 函数 + + 被定义之后,表达式 + + (sqrt-iter 1.0) + sqrt_@iter(1) + + + 仍在环境 + + E1 + E2 + + 中求值。因此在 + + E1 中绑定到的过程对象 + E2 中绑定到的函数对象 + + + sqrt-iter + 以 1 作为参数被调用。 这创建了一个新的环境 E2,其中 + + sqrt_@iter + 以 1 作为参数被调用。这创建了一个新的环境 E3,其中 + + + guesssqrt-iter,sqrt_@iter,的参数被绑定到 1。sqrt-iter函数sqrt_@iter反过来调用了good-enough?is_@good_@enough,并使用这个值。 guess + + + (来自 E2) 作为 good-enough? 的参数。 + + + (来自 E3) 作为 is_@good_@enough 的参数。 + + + 这设置了另一个环境, + + + E3,其中guess (good-enough? 的参数) + + + E4,其中guess (is_@good_@enough 的参数) + + + 被绑定到 1。虽然 + + sqrt-iter + sqrt_@iter + + 和 + + good-enough? + is_@good_@enough + + 都有一个名称为 +guess ,这些是位于不同帧中的两个独立局部变量。 + + + 此外,E2 和 E3 都以 E1 作为它们的外层环境,因为 + sqrt-iter 和 + good-enough? 过程 + 都以 E1 作为它们的环境部分。 + + + 此外,E3 和 E4 都以 E2 作为它们的外层环境,因为 + sqrt_@iter + 和 + is_@good_@enough 函数 + 都以 E2 作为它们的环境部分。 + + + 这一结果的一个后果是, + + + 符号 + + + 名称 + + + x ,出现在 + + good-enough? + is_@good_@enough + + 函数体中的符号将引用 +x ,出现在 E1 中,即值为 x ,其原始的 sqrt + + 过程 + 函数 + + 被调用。 + sqrt环境在环境模型中 + + + + 因此,环境模型解释了使得本地 + + 过程定义 + 函数声明 + + 成为模块化程序的一种有用技术的两个关键属性: +
    +
  • + 本地 + + 过程 + 函数 + + 的名称不会干扰 + 外部于封闭 + + 过程, + 函数, + + 的名称,因为本地 + + 过程 + 函数 + + 的名称将被绑定在 + + 过程运行时创建的帧中, + 块求值时创建的帧中, + + 而不是在 + + 全局 + 程序 + + 环境中。 +
  • +
  • + 本地 + + 过程 + 函数 + + 可以访问封闭 + + 过程, + 函数, + + 的参数,只需使用参数名称作为自由 + + 变量。 + 名称。 + + 这是因为本地 + + 过程 + 函数 + + 的体是在一个从属于封闭 + + 过程 + 函数。 + + 的求值环境中进行求值的。 +
  • +
+
+ + 在第节中,我们看到环境模型如何描述具有局部状态的 + + 过程 + 函数 + + 的行为。现在我们了解了内部 + + + 定义 + + + 声明 + + + 的工作原理。 + 求值的环境模型消息传递 + 消息传递环境模型与 + 一个典型的消息传递 + + 过程 + 函数 + + 包含这两个方面。考虑第节中的 + 银行账户 + 银行账户 + + 过程 + 函数 + + : + + + make_accountenvironmentin environment model + another_make_account + another_make_account_example + 30 + +(define (make-account balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (define (dispatch m) + (cond ((eq? m 'withdraw) withdraw) + ((eq? m 'deposit) deposit) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch) + + +function make_account(balance) { + function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + function dispatch(m) { + return m === "withdraw" + ? withdraw + : m === "deposit" + ? deposit + : error(m, "Unknown request: make_account"); + } + return dispatch; +} + + + 显示由一系列互动生成的环境结构 + + another_make_account_example + + (define acc (make-account 50)) + + +const acc = make_account(50); + + +const acc = make_account(50); +acc("withdraw")(20); + + + + another_make_account_example_2 + + ((acc 'deposit) 40) + + + 90 + + +acc("deposit")(40); + + +90 + + + + another_make_account_example_3 + + ((acc 'withdraw) 60) + + + 30 + + +acc("withdraw")(60); + + +30 + + + 局部状态在哪里 +acc 保留?假设我们定义了另一个账户 + + (define acc2 (make-account 100)) + + +const acc2 = make_account(100); + + + 两个账户的局部状态如何保持独立?环境结构中哪些部分是共享的? +accacc2 ? + + + 更多关于块 + + + 如我们所见,在 + sqrt 中声明的名称的作用域是整个 + sqrt 的主体。这解释了为什么 + 相互递归 + 递归相互 + 相互递归能够工作,如这种(相当浪费的)检查非负整数是否为偶数的方法。 + + f_is_even_is_odd + +function f(x) { + function is_even(n) { + return n === 0 + ? true + : is_odd(n - 1); + } + function is_odd(n) { + return n === 0 + ? false + : is_even(n - 1); + } + return is_even(x); +} + + + 当 + is_even 在调用 + f 时被调用,环境图看起来像 + sqrt_iter 被调用时图中的那样。函数 + is_even 和 + is_odd 在 E2 中被绑定到指向 E2 的函数对象作为评估这些函数调用的环境。因此 + is_odd 在 + is_even 的体内指的是正确的函数。虽然 + is_odd + 是在 + is_even 之后定义的, + 这与 + sqrt_iter 的体内的名称 + improve + 和名字 + sqrt_iter + 本身指的是正确的函数没有区别。 + + + 有了一种处理块内声明的方法,我们可以 + 重新审视顶层的名称声明。在第节中,我们看到 + 顶层声明的名称被添加到程序 + 帧中。一个更好的解释是整个程序被放置在一个 + 隐含块中,该块在全局环境中求值。 + 上述块的处理方式然后处理了顶层: + 全局环境通过一个包含所有在隐含块中声明的名称绑定的帧来扩展。该帧是 + 程序帧,产生的 + 环境是 + 程序环境 + 程序环境。 + + + 我们说过,一个块的主体是在包含块体内直接声明的所有名称的环境中求值的。 + 在进入块时,局部声明的名称被放入环境中,但没有关联的值。在块主体求值期间对其声明的求值,然后将表达式右侧的求值结果赋值给该名称,就像声明是一个赋值一样。由于名称添加到环境中与声明的求值是分开的,并且整个块都在名称的作用域中,一个错误的程序可能会尝试 + 声明在使用名称之前 + 在其声明求值之前访问名称的值; + 对未赋值名称的求值会引发错误。 + + 这解释了为什么在第 1 章中的脚注中程序会出现问题。 + 从为名称创建绑定到对名称的声明进行求值之间的时间称为 + 时间死区(TDZ) + TDZ(时间死区) + 时间死区(TDZ)。 + + 求值的环境模型 + 块结构环境在环境模型中 + 求值的环境模型内部声明 + 内部声明环境在环境模型中 +
diff --git a/xml/cn/chapter3/section2/subsection5.xml b/xml/cn/chapter3/section2/subsection5.xml new file mode 100644 index 000000000..4e3d6f847 --- /dev/null +++ b/xml/cn/chapter3/section2/subsection5.xml @@ -0,0 +1,35 @@ + + + 寄存器机 + + + + 寄存器机 + + + + + 环境模型如上所述,侧重于函数如何引用其参数、局部声明的名称以及函数外部声明的名称。我们通过相对于当前环境求值语句和表达式来实现这一点。它没有指定随着计算的进行,我们如何跟踪环境。例如,当我们求值表达式f(x) + y时,我们需要在当前环境中求值x,建立一个新当前环境,该环境是f的环境通过绑定其参数到x的值来扩展,并在此扩展环境中求值f的主体。但是在f返回后,我们应该使用哪种环境来求值y? +在本节中,我们扩展了 + + + 求值算术表达式 + + + 练习表明,赋值的存在会使程序的结果依赖于运算符组合的操作数的求值顺序。为了消除由此引起的歧义,JavaScript标准指定操作数从左到右进行求值。 + + 例如,考虑算术表达式语句的求值 + + +1 + (2 * 3); + + + 该表达式分解为其操作数 + 1和 + 2 * 3,然后是 + 指令将它们的结果相加。 + + 寄存器机 + + + diff --git a/xml/cn/chapter3/section3/section3.xml b/xml/cn/chapter3/section3/section3.xml new file mode 100644 index 000000000..f01ad9639 --- /dev/null +++ b/xml/cn/chapter3/section3/section3.xml @@ -0,0 +1,43 @@ +
+ 用可变数据建模 + + 可变数据对象 + + + + + 第章讨论了复合数据,作为构造具有多个部分的计算对象的一种手段,以便对具有多个方面的现实世界对象进行建模。在那一章中,我们引入了数据抽象的原则,根据该原则,数据结构是通过构造函数和选择器来指定的,构造函数创建数据对象,选择器访问复合数据对象的组成部分。但我们现在知道,还有数据的另一个方面是第章没有涉及的。为了对由状态变化的对象组成的系统进行建模,我们需要修改复合数据对象,以及构造和选择它们。为了对具有变化状态的复合对象进行建模,我们将设计数据抽象,除了选择器和构造器,还包括称为 + mutator + 修改器的操作,这些操作会修改数据对象。例如,建模银行系统要求我们改变账户余额。因此,用于表示银行账户的数据结构可能会允许一个操作 + + + +(set-balance! $account$ $new$-$value$) + + +set_balance(account, new-value) + + + 将指定账户的余额更改为指定的新值。为其定义了修改器的数据对象被称为可变数据对象。 + + + + 第章介绍了序对,作为合成复合数据的通用粘合剂。我们在本节开始时定义了序对的基本修改器,以便序对可以作为构建可变数据对象的构建块。这些修改器大大增强了序对的表示能力,使我们能够构建除节中处理的序列和树之外的数据结构。我们还展示了一些模拟的例子,其中复杂系统被建模为具有局部状态的对象集合。 + + + + &subsection3.3.1; + + + &subsection3.3.2; + + + &subsection3.3.3; + + + &subsection3.3.4; + + + &subsection3.3.5; + +
diff --git a/xml/cn/chapter3/section3/subsection1.xml b/xml/cn/chapter3/section3/subsection1.xml new file mode 100644 index 000000000..d081d557c --- /dev/null +++ b/xml/cn/chapter3/section3/subsection1.xml @@ -0,0 +1,1917 @@ + + + 可变数据对象 (列表结构) + + + + 可变数据对象列表结构 + 列表结构可变 + 可变数据对象序对流生成函数 + 序对可变 + + + 基本操作在 + + + 序对cons, + + + 序对pair, + + + + car, + head, + + 和 + + cdr可以 + tail可以 + + + 用于构建列表结构并从列表结构中选择部分,但它们无法修改列表结构。同样地,我们迄今所用的列表操作,例如append和 + list,因为这些可以用 + + cons, + pair, + + + car, + head, + + 和 + + cdr. + tail. + + 要修改列表结构,我们需要新的操作。 + + + + 序对的基本修改器是 + set_head (原语函数) + set_head (\textit{ns}) + + set-car! + set_head + + 和 + set_tail (原语函数) + set_tail (\textit{ns}) + + set-cdr!. + set_tail. + + + Set-car! + 函数 set_head + + + 接受两个参数,第一个必须是序对。它修改这个序对,将 + + car + head + + 指针替换为指向 + set-car!set_head的第二个参数的指针。 + + + Set-car! 和 + set-cdr! 返回的是 + 特定于实现的 + set-car! (原语函数)的值 + 未指定值set-carset-car! + set-cdr! (原语函数)的值 + 未指定值set-cdrset-cdr! + 的值。像set!一样,它们应该仅用于其效果。 + + + 函数 set_head 和 + set_tail 返回值 + undefined。 + set_head (原语函数)的值 + set_tail (原语函数)的值 + 它们应该仅用于其效果。 + + + + + + 举例来说,假设 +x 被绑定为 + + + 列表为 + ((a b) c d) + + + list(list("a", "b"), "c", "d") + + + 和 y 绑定到 + + + 列表 + (e f) + + list("e", "f") + + + 如图所示 + + + 图. + + + 图. + + + 计算表达式 + + (set-car! x y) + + set_head(x, y) + + 修改绑定到的序对 x 被绑定, + 将其 + + car + head + + 替换为 y 。操作的结果显示在 + + + 图. + + + 图. + + + 结构 x 已被修改,现在 + + + 会被打印为 + ((ef)cd)。 + + + 现在等同于 + list(list("e", "f"), "c", "d")。 + + 表示列表的序对 + + (a b), + list("a", "b"), + + + 由被替换的指针表示,现在已从原始结构中分离出来。从中我们可以看到,对列表的修改操作可能会产生不属于任何可访问结构的垃圾。我们将在部分看到, + + Lisp + JavaScript + + 内存管理系统包含一个 + 垃圾回收突变与 + 垃圾回收器,用于识别并回收不需要的序对所占用的内存空间。 + + + + +
+
+ + 列表 x: + ((a b) c d) 和 + y: (e f). + + +
+ + +
+
+ + 列表 x: + list(list("a", "b"), "c", "d") + 和 y: + list("e", "f"). + + +
+
+
+ + +
+
+ + (set-car! x y) 对图中列表的影响。 + + +
+ + +
+
+ + set_head(x, y) 对图中列表的影响。 + + +
+
+
+ + +
+
+ + (define z (cons y (cdr x))) 对图中列表的影响。 + + +
+ + +
+
+ + const z = pair(y, tail(x)); 对图中列表的影响。 + + +
+
+
+ + +
+
+ + (set-cdr! x y) 对图中列表的影响。 + + +
+ + +
+
+ + set_tail(x, y) 对图中列表的影响。 + + +
+
+
+ + + + 比较 + + + 图 + + + 图 + + + 和 + + + 图, + + + 图, + + + 哪个说明了执行的结果 + + mutable_list_example + +(define x '((a b) c d)) +(define y '(e f)) + + +const x = list(list("a", "b"), "c"); +const y = list("e", "f"); + + + + mutable_list_example + +(define z (cons y (cdr x))) + + +const z = pair(y, tail(x)); + + + 其中x和y绑定到了原始列表 + + + 图。 + + + 图。 + + + 这个 + + + 变量 + + + 名称 + + + z 现在绑定到了一个 + 由 + cons + pair + + 操作创建的新序对;x绑定的列表未被改变。 + + + + + set-cdr! + set_tail + + 操作与 + + set-car!. + set_head. + + 类似。唯一的区别是 + + cdr + tail + + 指针而不是 + + car + head + + 指针被替换。执行 + + (set-cdr! x y) + set_tail(x, y) + + + 在 + + + 图 + + + 图 + + + 列表上的效果显示在 + + + 图。 + + + 图。 + + + 这里 + + cdr + tail + + 指针 + x 被替换为指向 + + (e f)。 + list("e", "f")。 + + + 同时,列表 + + (c d), + list("c", "d"), + + + 曾是 + + cdr + tail + + 的 x ,现在从结构中分离出来。 + + + + + Cons + 函数 pair + + + 通过创建新的序对来构建新的列表结构, + + 而 set-car! + set_@head + + 和 + + set-cdr! + set_tail + + 修改现有的序对。 + 实际上,我们可以 + pair (原语函数)用修改器实现 + 用两个修改器和一个 + 过程函数 + + get-new-pair, + get_new_pair, + + 实现 + + cons + pair + + ,它返回一个不属于任何现有列表结构的新序对。 + 我们获得这个新序对,设置其 + + car + head + + 和 + + cdr + tail + + 指针指向指定的对象,并返回作为 + conspair + 的结果的新序对。 + + + Get-new-pair + 是Lisp实现所需内存管理的一部分操作。 + 我们将在部分讨论这一点。 + + + 部分 + 将展示内存管理系统如何实现get_new_pair。 + + + + + + get_new_pair + + + +// The book proposes a primitive function get_new_pair. +// Since JavaScript does not provide such a function, let's +// define it as follows, for the sake of the example. + +function get_new_pair() { + return pair(undefined, undefined); +} +{ + + + + pair (primitive function)implemented with mutators + get_new_pair + mutable_pair_example + [ [ 1, 2 ], 4 ] + +(define (cons x y) + (let ((new (get-new-pair))) + (set-car! new x) + (set-cdr! new y) + new)) + + +function pair(x, y) { + const fresh = get_new_pair(); + set_head(fresh, x); + set_tail(fresh, y); + return fresh; +} + + + + + mutable_pair_example + + + +pair(pair(1, 2), 4); +} + + + + + + + 以下是追加列表的 + + 过程 + 函数 + + 在节中介绍: + + append_example3 + 9 + +append(list(1, 3), list(5, 7, 9)); + + +list_ref(append(list(1, 3), list(5, 7, 9)), 4); + + + + 以下 + + 过程 + 函数 + + 用于追加列表在部分中介绍: + + append2 + append_example3 + 9 + +(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)); +} + + + + Append + 函数 append + + + 通过连续地 + + + cons将 + x的元素加入到 + y中。 + + + 将x的元素添加到 + y的前面。 + + + 这个 + + 过程 + 函数 + + appendappend 变异器append_mutator vs. + + append! + append_mutator + + + 类似于 append ,但是它是一个修改器 + 而不是构造器。它通过将列表拼接在一起来附加列表, + 修改 x ,现在变为 + + cdr + tail + + 。 y 。(调用 + + append! + append_mutator + + + 带空是错误 x。) + + append_mutator + append_mutator + last_pair + +(define (append! x y) + (set-cdr! (last-pair x) y) + x) + + +function append_mutator(x, y) { + set_tail(last_pair(x), y); + return x; +} + + + 这里 + + last-pair + last_pair + + 是一个 + + 过程 + 函数 + + ,返回其参数中的最后一个序对: + + last_pair_example_2 + +last_pair(list(1, 2, 3, 4, 5)); + + + + last_pair + last_pair + last_pair_example_2 + [ 5, null ] + +(define (last-pair x) + (if (null? (cdr x)) + x + (last-pair (cdr x)))) + + +function last_pair(x) { + return is_null(tail(x)) + ? x + : last_pair(tail(x)); +} + + + 考虑交互 + + append_interaction_1 + append2 + append_mutator + +(define x (list 'a 'b)) + + +const x = list("a", "b"); + + + + append_interaction_2 + append_interaction_1 + +(define y (list 'c 'd)) + + +const y = list("c", "d"); + + + + append_interaction_3 + append_interaction_2 + +(define z (append x y)) + + +const z = append(x, y); + + + + append_interaction_4 + append_interaction_3 + +z + + +(a b c d) + + +z; + + +["a", ["b", ["c", ["d, null]]]] + + + + append_interaction_5 + append_interaction_4 + +(cdr x) + + +<response> + + +tail(x); + + +response + + + + append_interaction_6 + append_interaction_5 + +(define w (append! x y)) + + +const w = append_mutator(x, y); + + + + append_interaction_7 + append_interaction_6 + +w + + +(a b c d) + + +w; + + +["a", ["b", ["c", ["d", null]]]] + + + + append_interaction_8 + append_interaction_7 + +(cdr x) + + +<response> + + +tail(x); + + +response + + + 缺少的response是什么? + 画出方框与指针图来解释你的答案。 + + + + 考虑以下 + 列表中的循环 + + make-cycle + make_cycle + + + 过程, + 函数, + + 它使用 + + + last-pair + + + last_pair + + + + 过程 + 函数 + + 在练习中定义: + + make_cycle + make_cycle + last_pair + make_cycle_example + +(define (make-cycle x) + (set-cdr! (last-pair x) x) + x) + + +function make_cycle(x) { + set_tail(last_pair(x), x); + return x; +} + + + 画出一个方框与指针图显示结构 +z + 由...创建的 + + make_cycle_example + make_cycle + 'b' + +(define z (make-cycle (list 'a 'b 'c))) + + +const z = make_cycle(list("a", "b", "c")); + + +const z = make_cycle(list("a", "b", "c")); +list_ref(z, 100); + + + 如果我们尝试计算 + + (last-pair z)? + last_pair(z)? + + + + + (由GitHub用户jonathantorres提供) + 如果我们尝试计算last_pair(z),程序将进入一个无限循环,因为列表的末尾指向开头。 + +
+ +
+ +
+
+ + + 以下 + + 过程 + 函数 + + 是非常有用的,尽管比较晦涩: + + mystery + mystery_loop + mystery_loop_example_1 + mystery_loop_example_2 + +(define (mystery x) + (define (loop x y) + (if (null? x) + y + (let ((temp (cdr x))) + (set-cdr! x y) + (loop temp x)))) + (loop x '())) + + +function mystery(x) { + function loop(x, y) { + if (is_null(x)) { + return y; + } else { + const temp = tail(x); + set_tail(x, y); + return loop(temp, x); + } + } + return loop(x, null); +} + + + + Loop + 函数 loop + + + 使用临时 + + + 变量 + + + 名称 + + +temp + 用于保存 + + cdr + tail + + 的旧值 +x,因为下一行的 + + set-cdr! + set_tail + + 破坏了 + + cdr。 + tail + + 解释什么 +mystery + 通常是做什么的。假设 +v + 由...定义为 + + mystery_loop_example_1 + +(define v (list 'a 'b 'c 'd)) + + +const v = list("a", "b", "c", "d"); + + + 画出一个方框与指针图来表示该列表 +v + 绑定到。假设我们现在计算 + + mystery_loop_example_2 + mystery_loop + +(define w (mystery v)) + + +const w = mystery(v); + + + 画出方框与指针图来展示结构 +vw + 在评估此 + + 表达式 + 程序 + + 后,展示结构的方框与指针图。什么将被打印为的值 +vw ? + + + (由GitHub用户jonathantorres提供) + 应用mystery(x)将原地反转列表x。 + 最初 + v看起来像这样: + +
+ +
+ + 计算 + const w = mystery(v); + 后 + v 和 + w 的值变为: + +
+ +
+ + 函数display + 打印["a", null]为 + v 和 + ["d", ["c", ["b", ["a", null]]]]为 + w。 +
+
+ + 可变数据对象列表结构 + list structuremutable + 可变数据对象序对 + 序对可变 + + + 共享和同一性 + + + 数据共享 + 共享数据 + + 共享数据 + pair_example1 + +(define x (list 'a 'b)) +(define z1 (cons x x)) + + +const x = list("a", "b"); +const z1 = pair(x, x); + + + 如图所示 + + + 图, + + + 图, + + +z1 可变数据对象序对流生成函数 x 。这种共享 +x 由 + + car + head + + 和 + + cdr + tail + + 指向 +z1 这是以 + + cons + pair + + 被简单实现的结果。通常,使用 + + cons + pair + + 构建列表会导致许多单独的序对在多个不同的结构中共享的序对的连接结构。 + + +
+
+ + 列表z1由 + (cons x x)形成。 + + +
+ + +
+
+ + 列表z1由 + pair(x, x)形成。 + + +
+
+
+ + +
+
+ + 列表z2由 + (cons (list 'a 'b) (list 'a 'b))形成。 + + +
+ + +
+
+ + 列表z2由 + pair(list("a", "b"), list("a", "b"))形成。 + + +
+
+
+
+ + + 与 + + + 图相比, + 图 + + + 图相比, + 图 + + + 显示的是由...创建的结构 + + pair_example2 + +(define z2 (cons (list 'a 'b) (list 'a 'b))) + + +const z2 = pair(list("a", "b"), list("a", "b")); + + + In this structure, the pairs in the two + + (a b) + list("a", "b") + + + lists are distinct, although + + + the actual symbols are shared. + The two pairs are distinct because each call to + cons returns a new pair. The + symbols are shared; in Scheme there is a + symbol(s)uniqueness of + unique symbol with any given + name. Since Scheme provides no way to mutate a symbol, this sharing is + undetectable. Note also that the sharing is what enables us to + compare symbols using eq?, which simply checks equality of + pointers. + + + they contain the same strings.The two pairs are distinct + because each call to pair + returns a new pair. The strings are + string(s)uniqueness of + the same in the sense + that they are primitive data (just like numbers) that are composed of + the same characters in the same order. Since JavaScript provides no way + to mutate a string, any sharing that the designers of a JavaScript + interpreter might decide to implement for strings is undetectable. + We consider primitive data such as numbers, booleans, and strings + to be identical if and only if they are + indistinguishable. + + + + + + + When thought of as a list, z1 and + z2 both represent the same list: + + abab + [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +((a b) a b) + + +list(list("a", "b"), "a", "b") + + +list(list("a", "b"), "a", "b"); + + + In general, sharing is completely undetectable if we operate on lists using + only + + cons, + pair, + + + car, + head, + + and + + cdr. + tail. + + However, if we allow mutators on list structure, sharing becomes + significant. As an example of the difference that sharing can make, + consider the following + + procedure, + function, + which modifies the + + car + head + + of the structure to which it is applied: + + set_to_wow + +(define (set-to-wow! x) + (set-car! (car x) 'wow) + x) + + +function set_to_wow(x) { + set_head(head(x), "wow"); + return x; +} + + + Even though z1 and + z2 are the same structure, + applying + + set-to-wow! + set_to_wow + + to them yields different results. With z1, + altering the + + car + head + + also changes the + + cdr, + tail, + + because in z1 the + + car + head + + and the + + cdr + tail + + are the same pair. With z2, the + + car + head + + and + + cdr + tail + + are distinct, so + + set-to-wow! + set_to_wow + + modifies only the + + car: + head: + + + set_to_wow_example_1 + pair_example1 + [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +z1 + + + ((a b) a b) + + +z1; + + +[["a", ["b", null]], ["a", ["b", null]]] + + + + set_to_wow_example_2 + set_to_wow + pair_example1 + [ [ 'wow', [ 'b', null ] ], [ 'wow', [ 'b', null ] ] ] + +(set-to-wow! z1) + + +((wow b) wow b) + + +set_to_wow(z1); + + +[["wow", ["b", null]], ["wow", ["b", null]]] + + + + set_to_wow_example_3 + pair_example2 + [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +z2 + + +((a b) a b) + + +z2; + + > +[["a", ["b", null]], ["a", ["b", null]]] + + + + set_to_wow_example_4 + set_to_wow + pair_example2 + [ [ 'wow', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +(set-to-wow! z2) + + +((wow b) a b) + + +set_to_wow(z2); + + +[["wow", ["b", null]], ["a", ["b", null]]] + + + + + + + + One way to detect sharing in list structures is to use the predicate + eq?, which we introduced in + section as a way to test whether two + symbols are equal. More generally, (eq? x y) + tests whether x and + y are the same object (that is, whether + x and y + are equal as pointers). + + + One way to detect sharing in list structures is to use the + ===equalityas equality of pointers + ===generalas general comparison operator + === (for nonprimitive values) + primitive predicate ===, + which we introduced in + section to test whether two numbers + are equal + and extended in section to test whether + two strings are equal. When applied to two nonprimitive values, + x === y + tests whether x and + y are the same object (that is, whether + x and y + are equal as pointers). + + + Thus, with z1 and + z2 as defined in + + + figure + and, + + + figure + and, + + + + (eq? (carz1) (cdrz1)) + + head(z1) === tail(z1) + + + is true and + + + (eq? (car z2) (cdr z2)) + head(z2) === tail(z2) + + + is false. + + + + As will be seen in the following sections, we can exploit sharing to + greatly extend the repertoire of data structures that can be + represented by pairs. On the other hand, sharing can also be + mutable data objectsshared data + dangerous, since modifications made to structures will also affect + other structures that happen to share the modified parts. The mutation + operations + + set-car! + set_head + + and + + set-cdr! + set_tail + + should be used with care; unless we have a good understanding of how our + data objects are shared, mutation can have unanticipated + results.The subtleties of dealing with sharing of mutable data + objects reflect the underlying issues of sameness and + change that were raised in + section. We mentioned there + that admitting change to our language requires that a compound object must + have an identity that is something different from the pieces + from which it is composed. In + + Lisp, + JavaScript, + + we consider this identity to be the quality that is tested by + eq?, + ===, + + i.e., by equality of pointers. Since in most + + Lisp + JavaScript + + implementations a pointer is essentially a memory address, we are + solving the problem of defining the identity of objects by + stipulating that a data object itself is the information + stored in some particular set of memory locations in the computer. This + suffices for simple + + Lisp + JavaScript + + programs, but is hardly a general way to resolve the issue of + sameness in computational models. + + + + + Draw box-and-pointer diagrams to explain the effect of + + set-to-wow! + set_to_wow + + on the structures z1 and + z2 above. + + + + + Ben Bitdiddle decides to write a + + procedure + function + + to count the number of pairs in any list structure. + Its easy, he reasons. The number of pairs in + any structure is the number in the + + car + head + + plus the number in the + + cdr + tail + + plus one more to count the current pair. So Ben writes the following + + procedure: + function + + + count_pairs + count_pairs + +(define (count-pairs x) + (if (not (pair? x)) + 0 + (+ (count-pairs (car x)) + (count-pairs (cdr x)) + 1))) + + +function count_pairs(x) { + return ! is_pair(x) + ? 0 + : count_pairs(head(x)) + + count_pairs(tail(x)) + + 1; +} + + + Show that this + + procedure + function + + is not correct. In particular, draw box-and-pointer diagrams representing + list structures made up of exactly three pairs for which Bens + + procedure + function + + would return3; return 4; return 7; never return at all. + + + + exercise_3_16_solution + count_pairs + exercise_3_16_solution_example + + + +const three_list = list("a", "b", "c"); +const one = pair("d", "e"); +const two = pair(one, one); +const four_list = pair(two, "f"); +const seven_list = pair(two, two); +const cycle = list("g", "h", "i"); +set_tail(tail(tail(cycle)), cycle); + + + + + exercise_3_16_solution_example + +// return 3; return 4; return 7; +display(count_pairs(three_list)); +display(count_pairs(four_list)); +display(count_pairs(seven_list)); + +// never return at all +display(count_pairs(cycle)); + + + + + + + + Devise a correct version of the + + count-pairs + count_pairs + + + procedure + function + + of exercise that returns the number of + distinct pairs in any structure. (Hint: Traverse the structure, maintaining + an auxiliary data structure that is used to keep track of which pairs have + already been counted.) + + + exercise_3_17_solution_example + +// solution provided by GitHub user clean99 + +function count_pairs(x) { + let counted_pairs = null; + function is_counted_pair(current_counted_pairs, x) { + return is_null(current_counted_pairs) + ? false + : head(current_counted_pairs) === x + ? true + : is_counted_pair(tail(current_counted_pairs), x); + } + function count(x) { + if(! is_pair(x) || is_counted_pair(counted_pairs, x)) { + return 0; + } else { + counted_pairs = pair(x, counted_pairs); + return count(head(x)) + + count(tail(x)) + + 1; + } + } + return count(x); +} + + + + exercise_3_17_solution_example + +const three_list = list("a", "b", "c"); +const one = pair("d", "e"); +const two = pair(one, one); +const four_list = pair(two, "f"); +const seven_list = pair(two, two); +const cycle = list("g", "h", "i"); +set_tail(tail(tail(cycle)), cycle); + +// return 3; return 3; return 3; +display(count_pairs(three_list)); +display(count_pairs(four_list)); +display(count_pairs(seven_list)); + +// return 3 +display(count_pairs(cycle)); + + + + + + + Write a + + procedure + function + + that examines a list and + cycle in listdetecting + determines whether it contains a cycle, that is, + whether a program that tried to find the end of the list by taking + successive + + cdrs + tails + + would go into an infinite loop. Exercise + constructed such lists. + + + + exercise_3_18_solution_example + +// solution provided by GitHub user clean99 + +function contains_cycle(x) { + let counted_pairs = null; + function is_counted_pair(counted_pairs, x) { + return is_null(counted_pairs) + ? false + : head(counted_pairs) === x + ? true + : is_counted_pair(tail(counted_pairs), x); + } + function detect_cycle(x) { + if (is_null(x)) { + return false; + } else if (is_counted_pair(counted_pairs, x)) { + return true; + } else { + counted_pairs = pair(x, counted_pairs); + return detect_cycle(tail(x)); + } + } + return detect_cycle(x); +} + + + + exercise_3_18_solution_example + +const three_list = list("a", "b", "c"); +const cycle = list("g", "h", "i"); +set_tail(tail(tail(cycle)), cycle); + +// displays false +display(contains_cycle(three_list)); + +// displays true +display(contains_cycle(cycle)); + + + + + + + + Redo exercise using an algorithm that + takes only a constant amount of space. (This requires a very clever idea.) + + pair2_example + +const x = pair(1, 2); +set_head(x, 3); +head(x); + + + + Define a fast pointer and a slow pointer. The fast pointer goes forward + 2 steps every time, while the slow pointer goes forward 1 step every time. + If there is a cycle in the list, the fast pointer will eventually catch + up with the slow pointer. + + contains_cycle_example + make_cycle + +const c = make_cycle(list("c", "d", "e")); +const c1 = append(list("a", "b"), c); + +contains_cycle(c1); + + + + contains_cycle + contains_cycle_example + +function contains_cycle(x) { + function detect_cycle(fast, slow) { + return is_null(fast) || is_null(tail(fast)) + ? false + : fast === slow + ? true + : detect_cycle(tail(tail(fast)), tail(slow)); + } + return detect_cycle(tail(x), x); +} + + + + + + datashared + shared data + + + Mutation is just assignment + + + mutable data objectsfunctional representation of + mutable data objectsimplemented with assignment + pair(s)functional representation of + functional representation of datamutable data + + + When we introduced compound data, we observed in + section that pairs can be represented purely + in terms of + + procedures: + functions: + + + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + cons_1_2_run + 1 + +(define (cons x y) + (define (dispatch m) + (cond ((eq? m 'car) x) + ((eq? m 'cdr) y) + (else (error "Undefined operation - - CONS" m)))) + dispatch) + +(define (car z) (z 'car)) + +(define (cdr z) (z 'cdr)) + + +function pair(x, y) { + function dispatch(m) { + return m === "head" + ? x + : m === "tail" + ? y + : error(m, "undefined operation -- pair"); + } + return dispatch; +} + +function head(z) { return z("head"); } + +function tail(z) { return z("tail"); } + + + The same observation is true for mutable data. We can implement + mutable data objects as + + procedures + functions + + using assignment and local state. For instance, we can extend the above + pair implementation to handle + + set-car! + set_head + + and + + set-cdr! + set_tail + + in a manner analogous to the way we implemented bank accounts using + + make-account + make_account + + in section: + + cons_1_2_run_3 + +const x = pair(1, 2); +set_head(x, 3); +head(x); + + + + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + set_head (primitive function)functional implementation of + set_tail (primitive function)functional implementation of + pair2 + cons_1_2_run_3 + 3 + +(define (cons x y) + (define (set-x! v) (set! x v)) + (define (set-y! v) (set! y v)) + (define (dispatch m) + (cond ((eq? m 'car) x) + ((eq? m 'cdr) y) + ((eq? m 'set-car!) set-x!) + ((eq? m 'set-cdr!) set-y!) + (else (error "Undefined operation - - CONS" m)))) + dispatch) + +(define (car z) (z 'car)) + +(define (cdr z) (z 'cdr)) + +(define (set-car! z new-value) + ((z 'set-car!) new-value) + z) + +(define (set-cdr! z new-value) + ((z 'set-cdr!) new-value) + z) + + +function pair(x, y) { + function set_x(v) { x = v; } + function set_y(v) { y = v; } + return m => m === "head" + ? x + : m === "tail" + ? y + : m === "set_head" + ? set_x + : m === "set_tail" + ? set_y + : error(m, "undefined operation -- pair"); +} + +function head(z) { return z("head"); } + +function tail(z) { return z("tail"); } + +function set_head(z, new_value) { + z("set_head")(new_value); + return z; +} +function set_tail(z, new_value) { + z("set_tail")(new_value); + return z; +} + + + + + + Assignment is all that is needed, theoretically, to account for the + behavior of mutable data. As soon as we admit + + set! + assignment + + to our language, we raise all the issues, not only of assignment, but of + mutable data in general.On the other hand, from the viewpoint of + implementation, assignment requires us to modify the environment, which is + itself a mutable data structure. Thus, assignment and mutation are + equipotent: Each can be implemented in terms of the other. + + + + Draw environment diagrams to illustrate the evaluation of the sequence + of + + + expressions + + + statements + + + + pair2_example1 + pair2 + +(define x (cons 1 2)) +(define z (cons x x)) +(set-car! (cdr z) 17) + +(car x) + + +const x = pair(1, 2); +const z = pair(x, x); +set_head(tail(z), 17); + + + + pair2_example2 + pair2_example1 + 17 + +(car x) + + + 17 + + +head(x); + + +17 + + + using the + + + procedural + + + functional + + + implementation of pairs given above. (Compare + exercise.) + + + + mutable data objectsfunctional representation of + mutable data objectsimplemented with assignment + pair(s)functional representation of + functional representation of datamutable data + mutable data objects +
+ diff --git a/xml/cn/chapter3/section3/subsection2.xml b/xml/cn/chapter3/section3/subsection2.xml new file mode 100644 index 000000000..35b17b1c8 --- /dev/null +++ b/xml/cn/chapter3/section3/subsection2.xml @@ -0,0 +1,961 @@ + + + 队列表示 + + + + 队列 + + + 修改器 + + set-car! + set_head + + 和 + + set-cdr! + set_tail + + 使我们能够使用序对来构建无法使用 + + cons, + pair, + + + car, + head, + + 和 + + cdr + tail + + 单独构建的数据结构。本节展示了如何使用序对来表示一个称为队列的数据结构。第节将展示如何表示称为表的数据结构。 + + + + A 队列 is a sequence in which items are inserted at one end + (called the + 队列 + rear of the queue) and deleted from the other end (the + 队列 + front). + + 图 + + + shows an initially empty queue in which the items + ab 被插入。然后 a 被移除, cd 被插入,并且 b 被移除。因为项目总是按插入的顺序被移除,所以队列有时被称为 + 先入先出缓冲器 + FIFO(先入先出)缓冲器。 + + +
+
+ 队列操作。 + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Operation + + 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 +
+ 队列操作。 + +
+
+
+
+ + + + 在 + 数据抽象用于队列 + 队列上的操作 + 数据抽象方面,我们可以将队列视为由以下一组操作定义的: +
    +
  • + 一个构造函数: +

    + make_queue + + (make-queue) + make_queue() + + +

    + 返回一个空队列(一个不包含任何项目的队列)。 + \vspace{6pt} +
  • +
  • + 一个谓词: +

    + is_empty_queue + + (empty-queue? queue) + + is_empty_queue(queue) + + +

    + 用来测试队列是否为空。 + \vspace{6pt} +
  • +
  • + 一个选择器: +

    + front_queue + + (front-queue queue) + + front_queue(queue) + + +

    + 返回队列前端的对象,如果队列为空则提示错误;它不修改队列。 + \vspace{6pt} +
  • +
  • + 两个修改器: +

    + + (insert-queue! queue item) + + + insert_queue(queue, item) + + +

    + 插入 + insert_queue + 在队列的尾部插入项目,并以修改后的队列作为其值返回。\\[4pt]

    + + (delete-queue! queue) + delete_queue(queue) + + +

    + 移除 + delete_queue + 在队列的头部移除项目,并以修改后的队列作为其值返回,如果队列在删除前为空,则提示错误。 +
  • +
+
+ + + 因为队列是一个项目序列,我们当然可以将其表示为一个普通的列表;队列的前部将是列表的 + + car + head + + ,在队列中插入一个项目相当于将一个新元素附加到列表的末尾,而从队列中删除一个项目则相当于获取列表的 + + cdr + tail + + 。然而,这种表示是低效的,因为为了插入一个项目,我们必须扫描列表直到到达末尾。由于我们扫描列表的唯一方法是通过连续的 + + cdr + tail + + 操作,这种扫描对于一个包含 + $n$ + 项的列表需要 + $\Theta(n)$ + 步骤。对列表表示的一种简单修改可以克服这种缺点,使得队列操作可以实现为只需 + $\Theta(1)$ + 步骤;也就是说,所需步骤的数量不依赖于队列的长度。 + + + + 列表表示法的困难在于需要扫描以找到列表的末尾。我们需要扫描的原因是,虽然将列表表示为序对链的标准方法为我们提供了一个指向列表开头的指针,但它没有提供一个易于访问的指向末尾的指针。避免这一缺点的方法是将队列表示为一个列表,外加一个指示列表最后一对的额外指针。这样,当我们要插入一个项目时,我们可以查看尾部指针,从而避免扫描列表。 + + + + 因此,队列被表示为一对指针, + + front-ptr + front_ptr + + 和 + + rear-ptr, + rear_ptr, + + ,它们分别指示普通列表中的第一个和最后一个对。由于我们希望队列是一个可识别的对象,我们可以使用 + + cons + pair + + 来组合这两个指针。因此,队列本身将是这两个指针的 + + cons + pair + + 。 + + 图 + + + 说明了这种表示法。 + + +
+
+ + 实现一个具有前端和尾部指针的列表作为队列。 + + +
+ + +
+
+ + 实现一个具有前端和尾部指针的列表作为队列。 + + +
+
+
+
+ + + 为了定义队列操作,我们使用以下 + + 过程,函数, + + 它们使我们能够选择和修改队列的前端和尾部指针: + + modify_pointers_example + +const q = pair(1, 2); +set_front_ptr(q, 42); +front_ptr(q); + + + + front_ptr + rear_ptr + set_front_ptr + set_rear_ptr + modify_pointers + modify_pointers_example + 42 + +(define (front-ptr queue) (car queue)) + +(define (rear-ptr queue) (cdr queue)) + +(define (set-front-ptr! queue item) (set-car! queue item)) + +(define (set-rear-ptr! queue item) (set-cdr! queue item)) + + +function front_ptr(queue) { return head(queue); } + +function rear_ptr(queue) { return tail(queue); } + +function set_front_ptr(queue, item) { set_head(queue, item); } + +function set_rear_ptr(queue, item) { set_tail(queue, item); } + + + + + + 现在我们可以实现实际的队列操作。如果队列的前端指针是空列表,我们将认为队列为空。 + + is_empty_queue_example + +const q = pair(null, 2); +is_empty_queue(q); + + + + is_empty_queue + modify_pointers + is_empty_queue + is_empty_queue_example + true + +(define (empty-queue? queue) (null? (front-ptr queue))) + + +function is_empty_queue(queue) { return is_null(front_ptr(queue)); } + + + + make-queue + make_queue + + 构造函数返回一个最初为空的队列,一个其 + + car + head + + 和 + + cdr + tail + + 均为空列表的序对: + + make_queue_example + modify_pointers + +const q = make_queue(); +front_ptr(q); + + + + make_queue + make_queue + make_queue_example + null + +(define (make-queue) (cons '() '())) + + +function make_queue() { return pair(null, null); } + + + 要选择队列前面的项目,我们返回由前端指针指示的序对的 + + car + head + + : + + front_queue_example + +const q = pair(pair(1, 2), 3); +front_queue(q); + + + + front_queue + front_queue + is_empty_queue + front_queue_example + 1 + +(define (front-queue queue) + (if (empty-queue? queue) + (error "FRONT called with an empty queue" queue) + (car (front-ptr queue)))) + + +function front_queue(queue) { + return is_empty_queue(queue) + ? error(queue, "front_queue called with an empty queue") + : head(front_ptr(queue)); +} + + + + + + 要在队列中插入一个项目,我们遵循在 + + 图中指示的方法。 + 中指示的方法。 + + 我们首先创建一个新序对,其 + + car + head + + 是要插入的项,其 + + cdr + tail + + 是空列表。如果队列最初为空,我们将队列的前端和尾部指针设置为这个新序对。否则,我们将队列中的最后一个序对修改为指向新序对,并且也将尾部指针设置为新序对。 + + +
+
+ + 使用(insert-queue! q 'd)在图的队列上的结果。 + + +
+ + +
+
+ + 使用 + insert_queue(q, "d")在图的队列上的结果。 + + +
+
+
+ + print_queue_example + make_queue + insert_queue + delete_queue + + + +const q1 = make_queue(); +insert_queue(q1, "a"); +insert_queue(q1, "b"); +delete_queue(q1); +delete_queue(q1); + + + + insert_queue + insert_queue + modify_pointers + is_empty_queue + print_queue_example + [ null, [ 'b', null ] ] + +(define (insert-queue! queue item) + (let ((new-pair (cons item '()))) + (cond ((empty-queue? queue) + (set-front-ptr! queue new-pair) + (set-rear-ptr! queue new-pair) + queue) + (else + (set-cdr! (rear-ptr queue) new-pair) + (set-rear-ptr! queue new-pair) + queue)))) + + +function insert_queue(queue, item) { + const new_pair = pair(item, null); + if (is_empty_queue(queue)) { + set_front_ptr(queue, new_pair); + set_rear_ptr(queue, new_pair); + } else { + set_tail(rear_ptr(queue), new_pair); + set_rear_ptr(queue, new_pair); + } + return queue; +} + + +
+ + + 要删除队列前面的项目,我们只需修改前端指针,使其现在指向队列中的第二个项目,可以通过跟随第一个项目的 + + cdr + tail + + 指针来找到(见 + ):如果第一个项目是队列中的最后一个项目,删除后前端指针将为空列表,这将标记队列为空;我们不需要担心更新尾部指针,它仍将指向被删除的项目,因为 + + + empty-queue? + + + is_empty_queue + + + 只查看前端指针。 + + +
+
+ + 在图的队列上使用(delete-queue! q)的结果。 + + +
+ + +
+
+ + 在图的队列上使用delete_queue(q)的结果。 + + +
+
+
+ + delete_queue + delete_queue + is_empty_queue + modify_pointers + print_queue_example + [ null, [ 'b', null ] ] + +(define (delete-queue! queue) + (cond ((empty-queue? queue) + (error "DELETE! called with an empty queue" queue)) + (else + (set-front-ptr! queue (cdr (front-ptr queue))) + queue))) + + +function delete_queue(queue) { + if (is_empty_queue(queue)) { + error(queue, "delete_queue called with an empty queue"); + } else { + set_front_ptr(queue, tail(front_ptr(queue))); + return queue; + } +} + + +
+ + + Ben Bitdiddle 决定测试上面描述的队列实现。他在 + + Lisp + JavaScript + + 解释器中输入这些 + + 过程 + 函数 + + ,并继续尝试它们: + + make_queue_example1 + make_queue + +(define q1 (make-queue)) + + +const q1 = make_queue(); + + + + insert_queue_example1 + insert_queue + make_queue_example1 + +(insert-queue! q1 'a) + + +insert_queue(q1, "a"); + + + ((a) a) + + +[["a", null], ["a", null]] + + + + insert_queue_example2 + insert_queue_example1 + +(insert-queue! q1 'b) + + +insert_queue(q1, "b"); + + +[["a", ["b", null]], ["b", null]] + + + ((a b) b) + + + + insert_queue_example2 + delete_queue + delete_queue_example1 + +(delete-queue! q1) + + +delete_queue(q1); + + +[["b", null], ["b", null]] + + + ((b) b) + + + + delete_queue_example2 + delete_queue_example1 + +(delete-queue! q1) + + +delete_queue(q1); + + +[null, ["b", null]] + + + (() b) + + + 这全错了! 他抱怨道。 解释器的响应显示最后一个项目被插入了两次。当我删除两个项目后,第二个 b 仍然在那,所以即使它应该是空的,队列并没有空。 Eva Lu Ator 建议 Ben 误解了正在发生的事情。 并不是说项目被插入队列两次, 她解释道。 只是标准的 Lisp JavaScript 打印程序不知道如何识别队列的表示。如果你想要正确打印队列,你需要为队列定义自己的打印 过程 函数 解释 Eva Lu 所讨论的内容。具体地,展示为什么 Ben 的例子产生了他们所做的打印结果。定义一个 过程 函数 print_queue print-queue print_queue ,它以队列为输入并打印队列中的项目序列。 + + + + make_queue + modify_pointers + insert_queue + is_empty_queue + delete_queue + ex_3_21_solution_example + +function print_queue(q) { + return display(head(q)); +} + + + + ex_3_21_solution_example + +const q1 = make_queue(); +print_queue(q1); // prints: null +insert_queue(q1, "a"); +print_queue(q1); // prints: ["a", null] +insert_queue(q1, "b"); +print_queue(q1); // prints: ["a", ["b", null]] +delete_queue(q1); +print_queue(q1); // prints: ["b", null] + + + + + + + 我们可以将队列构建为具有局部状态的 + + 过程 + 函数 + + 队列函数式实现 + ,而不是表示为一对指针。局部状态将由指向普通列表开头和结尾的指针组成。因此, + + make-queue + make_queue + + + 过程 + 函数 + + 将具有以下形式 + + +(define (make-queue) + (let ((front-ptr $\ldots$ ) + (rear-ptr $\ldots$ )) + definitions of internal procedures + (define (dispatch m) $\ldots$) + dispatch)) + + +function make_queue() { + let front_ptr = $\ldots$; + let rear_ptr = $\ldots$; + declarations of internal functions + function dispatch(m) {$\ldots$} + return dispatch; +} + + + 完成 + + make-queue + make_queue + + 的定义并使用这种表示提供队列操作的实现。 + + + + ex_3_22_example + +// 由GitHub用户devinryu提供 + +function make_queue() { + let front_ptr = null; + let rear_ptr = null; + function is_empty_queue() { + return is_null(front_ptr); + } + function insert_queue(item) { + let new_pair = pair(item, null); + if (is_empty_queue()) { + front_ptr = new_pair; + rear_ptr = new_pair; + } else { + set_tail(rear_ptr, new_pair); + rear_ptr = new_pair; + } + } + function delete_queue() { + if (is_empty_queue()) { + error("FRONT called with an empty queue"); + } else { + front_ptr = tail(front_ptr); + } + } + function print_queue() { + display(front_ptr); + } + function dispatch(m) { + return m === "insert_queue" + ? insert_queue + : m === "delete_queue" + ? delete_queue + : m === "is_empty_queue" + ? is_empty_queue + : m === "print_queue" + ? print_queue + : error(m, "Unknow operation -- DISPATCH"); + } + return dispatch; +} +function insert_queue(queue, item) { + return queue("insert_queue")(item); +} +function delete_queue(queue) { + return queue("delete_queue")(); +} +function print_queue(queue) { + return queue("print_queue")(); +} + + + + ex_3_22_example + +const q = make_queue(); +print_queue(q); // prints: null +insert_queue(q, "a"); +print_queue(q); // prints: ["a", null] +insert_queue(q, "b"); +print_queue(q); // prints: ["a", ["b", null]] +delete_queue(q); +print_queue(q); // prints: ["b", null] + + + + + + + 双端队列 + 队列双端 + 双端队列 + (double-ended queue) 是一个序列,其中项目可以在 + + 前端或 + 前端或 + + 尾部插入和删除。 + 双端队列的操作有构造函数 + + make-deque, + make_deque, + + 谓词 + + empty-deque, + is_empty_deque, + + 选择器 + + front-deque + front_deque + + + 和 + + rear-deque, + rear_deque, + + 以及修改器 + + front-insert-deque!, + front_insert_deque, + + + + front-delete-deque!, + front_delete_deque, + + + + rear-insert-deque!, + rear_insert_deque, + + + 和 + + rear-delete-deque。 + rear_delete_deque。 + + + 展示如何使用序对来表示双端队列,并给出这些操作的实现。注意不要让解释器尝试打印一个包含循环的结构。(见 练习。) + 所有操作都应该在 + $\Theta(1)$ 步骤内完成。 + + + + +// solution provided by GitHub user clean99 +function make_deque() { + return pair(null, null); +} + +function front_ptr(deque) { + return head(deque); +} + +function rear_ptr(deque) { + return tail(deque); +} + +function set_front_ptr(deque, item) { + set_head(deque, item); +} + +function set_rear_ptr(deque, item) { + set_tail(deque, item); +} + +function is_empty_deque(deque) { + return is_null(front_ptr(deque)) + ? true + : is_null(rear_ptr(deque)) + ? true + : false; +} + +function is_one_item_deque(deque) { + return front_ptr(deque) === rear_ptr(deque); +} + +function front_insert_deque(deque, item) { + // use another pair to store a forward pointer + const new_pair = pair(pair(item, null), null); + if (is_empty_deque(deque)) { + set_front_ptr(deque, new_pair); + set_rear_ptr(deque, new_pair); + } else { + set_tail(new_pair, front_ptr(deque)); + // set forward pointer to new_pair + set_tail(head(front_ptr(deque)), new_pair); + set_front_ptr(deque, new_pair); + } +} + +function front_delete_deque(deque) { + if (is_empty_deque(deque)) { + error(deque, "front_delete_deque called with an empty deque"); + } else if(is_one_item_deque(deque)) { + set_front_ptr(deque, null); + set_rear_ptr(deque, null); + return deque; + } else { + set_front_ptr(deque, tail(front_ptr(deque))); + return deque; + } +} + +function rear_insert_deque(deque, item) { + if (is_empty_deque(deque)) { + const new_pair = pair(pair(item, null), null); + set_front_ptr(deque, new_pair); + set_rear_ptr(deque, new_pair); + } else { + // set new_pair forward pointer to last item + const new_pair = pair(pair(item, rear_ptr(deque)), null); + set_tail(rear_ptr(deque), new_pair); + set_rear_ptr(deque, new_pair); + } +} + +function rear_delete_deque(deque) { + if (is_empty_deque(deque)) { + error(deque, "rear_delete_deque called with an empty deque"); + } else if(is_one_item_deque(deque)) { + set_front_ptr(deque, null); + set_rear_ptr(deque, null); + return deque; + } else { + // update rear_ptr to last item's forward pointer + set_rear_ptr(deque, tail(head(rear_ptr(deque)))); + return deque; + } +} + + + + + 队列 +
diff --git a/xml/cn/chapter3/section3/subsection3.xml b/xml/cn/chapter3/section3/subsection3.xml new file mode 100644 index 000000000..647159345 --- /dev/null +++ b/xml/cn/chapter3/section3/subsection3.xml @@ -0,0 +1,1000 @@ + + + 表示表 + + + + + + + + 当我们在章节研究表示集合的各种方法时,我们在部分提到了通过识别键维护一个记录记录的键在表中的任务。在部分的数据导向编程的实现中,我们大量使用了二维表,其中通过两个键存储和检索信息。在这里,我们介绍如何将表构建为可变的列表结构。 + + + + 我们首先考虑一个一维一维表,其中每个值都存储在单个键下。我们将表实现为一个记录的列表,每个记录实现为一个由键和值组成的对。记录通过那些指向相继记录的carsheads对连接在一起形成列表。这些连接的对称为表的的主干主干。为了在向表中添加新记录时可以更改的位置,我们将表构建为一个带头结点的列表列表带头结点带头结点的列表。带头结点的列表在开始处有一个特殊的主干对,该对持有一个伪记录在这种情况下,任意选择的符号*table*.字符串"*table*".显示了表的方框与指针表示法。 + + +a: 1 +b: 2 +c: 3 + + +a: 1 +b: 2 +c: 3 + + + + +
+
+ 作为带头结点列表表示的表。 + +
+ + +
+
+ 作为带头结点列表表示的表。 + +
+
+
+
+ + + 要从表中提取信息,我们使用 +lookup + + 过程, + 函数, + + 它接受一个键作为参数并返回相关的值( + + 假 + 未定义 + + 如果 + 在那个键下没有存储值)。 + + 查找 + 函数查找 + + + 是根据 +assoc 操作,期望一个键和一个记录列表作为参数。注意assoc 从未看到伪记录。 + + Assoc + 函数assoc + + + 返回具有给定键作为其carhead的记录。 + + + + 因为assoc使用 + equal?,它可以识别 + 是符号、数字或列表结构的键。 + + + 因为 assoc 使用 + equal,它可以识别 + 是字符串、数字或列表结构的键。<!-- + --><!-- + --> + + 查找 + 函数 查找 + + + 然后检查所返回的记录 +assoc 不是 + + 假, + 未定义, + + 并返回值(记录的 + + cdr) + tail + 。 + + lookup1_example + make_table1 + insert_into_table1 + +const t = make_table(); +insert("a", 10, t); +lookup("a", t); + + + + lookupin one-dimensional table + assoc + 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)); +} + + + + + + 要在指定键下插入一个值到表中,我们首先使用 +assoc 查看表中是否已有该键的记录。如果没有,我们通过将键与值cons组成pair一个新记录,并将其插入到表的记录列表的头部,在伪记录之后。如果已有该键的记录,我们设置此记录的cdrtail为指定的新值。表的头部为我们提供了一个固定的位置进行修改以插入新记录。因此,第一个主干对是表示表“自身”的对象;即,指向表的指针就是指向这个对的指针。同一主干对总是开始于表。如果我们不这样安排, + + 插入! + 插入 + + + 在添加新记录时将需要返回表开始的新值。 + + insertin one-dimensional table + 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"; +} + + + + + + 要构建新的表,我们只需创建一个包含 + + + 符号*table*: + + + 仅包含字符串"*table*": + + + + make_table一维表 + make_table1 + +(define (make-table) + (list '*table*)) + + +function make_table() { + return list("*table*"); +} + + + 一维 + + + + 二维表 + + + 二维 + + + + 在二维表中,每个值由两个键索引。我们可以将这样的表构建为一维表,其中每个键标识一个子表。 + + + 图 + + + 图 + + + 显示了表的方框与指针表示法 + + +math: + +: 43 + -: 45 + *: 42 +letters: + a: 97 + b: 98 + + +"math": + "+": 43 + "-": 45 + "*": 42 +"letters": + "a": 97 + "b": 98 + + + 其具有两个子表。 (子表不需要特殊的头符号,串,因为标识子表的键起到这个作用。) + + +
+
+ 一个二维表。 + +
+ + +
+
+ 一个二维表。 + +
+
+
+
+ + + 当查找一个项目时,我们使用第一个键来识别正确的子表。然后我们使用第二个键来识别子表中的记录。 + + 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); + } +} + + + + + + 要在一对键下插入新项目,我们使用 +assoc 看是否已有子表存储在第一个键下。如果没有,我们构建一个包含单个记录的新子表 + + (key-2, + (key_2, + +value ) 并将其插入到第一个键下的表中。如果第一个键已经存在子表,我们将新记录插入到该子表中,使用上述一维表的插入方法: + + insertin two-dimensional table + 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"; +} + + + 二维 + + + + 创建局部表 + + + 局部 + + + 该 +lookup 和 + + 插入! + 插入 + + 上述操作将表作为参数。这使我们能够使用访问多个表的程序。处理多个表的另一种方法是拥有单独的 +lookup 和 + + 插入! + 插入 + + + 过程 + 函数 + + 为每个表提供。我们可以通过过程化地表示一个表来做到这一点,作为一个维护内部表的对象,作为其局部状态的一部分。当收到合适的消息时,此表对象提供操作内部表的 + + 过程 + 函数 + + 。这是以这种方式表示的二维表的生成器: + + make_tablemessage-passing implementation + 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; +} + + + + + + 使用 + + make-table, + make_table, + + 我们可以 + 操作与类型表实现 + 实现该 +get 和 + put 操作用于部分的数据导向编程,如下所示: + + operation_table_example + +put("a", "b", 10); +get("a", "b"); + + + + get + put + operation_table + make_table2 + operation_table_example + 10 + + operation-and-type tableimplementing +(define operation-table (make-table)) +(define get (operation-table 'lookup-proc)) +(define put (operation-table 'insert-proc!)) + + +const operation_table = make_table(); +const get = operation_table("lookup"); +const put = operation_table("insert"); + + + + 获取 + 函数 get + + + 接受两个键作为参数,并 +put 接受两个键和一个值作为参数。两个操作都访问同一个局部表,该表封装在 + + make-table. + make_table. + + 局部 + + + + 在上述表实现中,使用 + 测试键相等性 + 记录的键测试相等性 + 通过 + + equal? + equal + + 进行测试(由 +assoc ). 这并不总是合适的测试。例如,我们可能有一个带有数值键的表,其中我们查找的不是需要与数字完全匹配,而是只需在某个容差范围内的数字。设计一个表构造器 + + make-table + make_table + + 接受一个 + + same-key? + same_key + + + 过程 + 函数 + + 作为参数,用于测试键的“相等性”。 + + Make-table + 函数 make_table + + 应返回一个【134:12†cn.txt】。dispatch + + 过程 + 函数 + + 可用于访问适当的 + lookup 和 + + 插入! + 插入 + + + 过程函数 + + 用于局部表。 + + + + +// Solution by GitHub user clean99 + +function make_table(same_key) { + const local_table = list("*table*"); + function assoc(key, records) { + return is_null(records) + ? undefined + : same_key(key, head(head(records))) + ? head(records) + : assoc(key, tail(records)); + } + function lookup(key) { + const record = assoc(key, tail(local_table)); + return is_undefined(record) + ? undefined + : tail(record); + } + function insert(key, value) { + const record = assoc(key, tail(local_table)); + if (is_undefined(record)) { + set_tail(local_table, + pair(pair(key, value), tail(local_table))); + } else { + set_tail(record, value); + } + return "ok"; + } + function dispatch(m) { + return m === "lookup" + ? lookup + : m === "insert" + ? insert + : error(m, "unknow operation -- table"); + } + return dispatch; +} + +const operation_table = make_table((a, b) => a === b); +const get = operation_table("lookup"); +const put = operation_table("insert"); + + + + + + + 概括一维和二维表,展示如何实现一个在其中值存储在一个 + n$n$维 + 任意数量的键的表,并且不同的值可以存储在不同数量的键下。 + 该【150:4†cn.txt】 + lookup 和 + + 插入! + 插入 + + + 过程 + 函数 + + 应接受一个用于访问表的键列表作为输入。 + + + + solution_3_25 + solution_3_25_example + +// contributed by GitHub user tttinkl + +function assoc(key, records, same_key) { + return is_null(records) + ? undefined + : same_key(key, head(head(records))) + ? head(records) + : assoc(key, tail(records), same_key); +} + + +function make_table(same_key) { + const local_table = list("*table"); + + const get_value = tail; + + function is_table(t) { + return is_pair(t) && head(t) === "*table"; + } + + function lookup(keys) { + function lookup_generic(keys, table) { + if (is_null(keys)) { + return table; + } + const key_1 = head(keys); + const key_rest = tail(keys); + const record = assoc(key_1, tail(table), same_key); + if (is_undefined(record)) { + return undefined; + } + if (is_null(key_rest)) { + return get_value(record); + } else if (is_table(get_value(record))) { + return lookup_generic(key_rest, get_value(record)); + } else { + error('invalid key'); + } + } + return lookup_generic(keys, local_table); + } + + + function insert(keys, value) { + function insert_generic(keys, value, table) { + const key_1 = head(keys); + const key_rest = tail(keys); + const record = assoc(key_1, tail(table), same_key); + if (is_undefined(record)) { + if (is_null(key_rest)) { + set_tail( + table, + pair(pair(key_1, value), tail(table))); + } else { + const new_subtable = list("*table"); + set_tail( + table, + pair(pair(key_1, new_subtable), tail(table)) + ); + insert_generic(key_rest, value, new_subtable); + } + } else { + if (is_null(key_rest)) { + set_tail(record, value); + } else { + if (is_table(get_value(record))) { + insert_generic(key_rest, value, get_value(record)); + } else { + const new_subtable = list("*table"); + set_tail(record, new_subtable); + insert_generic(key_rest, value, new_subtable); + } + } + } + } + insert_generic(keys, value, local_table); + } + + function dispatch(m) { + return m === "lookup" + ? lookup + : m === "insert" + ? insert + : m === "show" + ? () => { + display(local_table); + return local_table; + } + : error(m, "unknow operation -- table"); + } + return dispatch; +} + +const table = make_table(equal); + +const get = table('lookup'); +const put = table('insert'); +const show = table('show'); + + + + solution_3_25_example + +put(list("a"), 1); +put(list("b", "c"), 2); +put(list("d", "e", "f"), 3); + +display(get(list("a"))); +display(get(list("b", "c"))); +display(get(list("d", "e", "f"))); + +put(list("a", "b"), 1); +display(get(list("a"))); +put(list("b", "c", "d"), 2); +display(get(list("b", "c"))); +put(list("b"), 1); +display(get(list("b"))); + + + + + + + 要搜索上面实现的表,需扫描记录列表。这基本上是部分中无序列表的表示。对于大型表,可能更有效率的是以不同方式构建表。描述一种表的实现方式,其中(key, value)记录使用 + 二叉树表结构为 + 表示为二叉树 vs. 无序列表 + 二叉树组织,假设键可以以某种方式排序(例如,数字或字母顺序)。(比较练习的章节。) + + + + ex_3_26_solution + ex_3_26_solution_example + +// provided by GitHub user devinryu + +function entry(tree) { return head(tree); } +function left_branch(tree) { return head(tail(tree)); } +function right_branch(tree) { return head(tail(tail(tree))); } +function make_tree(entry, left, right) { + return list(entry, left, right); +} + +// kv is list(key, value) +function adjoin_set(kv, set) { + return is_null(set) + ? make_tree(kv, null, null) + : head(kv) === head(entry(set)) + ? set + : head(kv) < head(entry(set)) + ? make_tree(entry(set), + adjoin_set(kv, left_branch(set)), + right_branch(set)) + : make_tree(entry(set), + left_branch(set), + adjoin_set(kv, right_branch(set))); +} + +function make_table() { + let local_table = null; + function lookup(given_key, tree_of_records) { + if (is_null(tree_of_records)) { + return null; + } else { + const this_entry = entry(tree_of_records); + const this_key = head(this_entry); + return given_key === this_key + ? this_entry + : given_key < this_key + ? lookup(given_key, + left_branch(tree_of_records)) + : lookup(given_key, + right_branch(tree_of_records)); + } + } + function insert(k, v) { + let record = lookup(k, local_table); + if(is_null(record)) { + local_table = adjoin_set(list(k, v), local_table); + } else { + // do nothing + } + } + function get(k) { + return head(tail(lookup(k, local_table))); + } + function print() { + return display(local_table); + } + function dispatch(m) { + return m === "lookup" + ? get + : m === "insert" + ? insert + : m === "print" + ? print + : error(m, "error"); + } + return dispatch; +} + + + + ex_3_26_solution_example + +const t = make_table(); +const get = t("lookup"); +const put = t("insert"); +const print = t("print"); + +// The test results + +put(3, "d"); +put(1, "a"); +put(2, "b"); +put(2, "c"); +put(4, "e"); +put(5, "f"); + +print(); + +display(get(2)); // displays: "b" + + + + + + + 记忆化 + 记忆化 + 制表法 + 用于存储计算值 + 记忆化 + (也称为制表法)是一种技术,它使 + + 过程 + 函数 + + 能够在局部表中记录先前计算过的值。这个技术可以显著改善程序的性能。一个记忆化的 + + 过程 + 函数 + + 维护一个表,其中将之前调用的值使用生成这些值的参数作为键存储。当记忆化的 + + 过程 + 函数 + + 被要求计算一个值时,首先查看表中是否已有该值,如果有,则直接返回该值。否则,它按常规方式计算新值并存储在表中。作为记忆化的一个例子,回忆部分用于计算斐波那契数的指数过程: + + fib_example + 8 + +(define (fib n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else (+ (fib (- n 1)) + (fib (- n 2)))))) + + +function fib(n) { + return n === 0 + ? 0 + : n === 1 + ? 1 + : fib(n - 1) + fib(n - 2); +} + + + 相同过程函数的记忆化版本是 + + memorize_example + +memo_fib(5); + + + + fibwith memoization + memo_fib + memo_fib + memorize + memorize_example + 5 + +(define memo-fib + (memoize (lambda (n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else (+ (memo-fib (- n 1)) + (memo-fib (- n 2)))))))) + + +const memo_fib = memoize(n => n === 0 + ? 0 + : n === 1 + ? 1 + : memo_fib(n - 1) + + memo_fib(n - 2) + ); + + + 记忆化器定义为 + + memoize + make_table1 + lookup1 + insert_into_table1 + memorize + +(define (memoize f) + (let ((table (make-table))) + (lambda (x) + (let ((previously-computed-result (lookup x table))) + (or previously-computed-result + (let ((result (f x))) + (insert! x result table) + result)))))) + + +function memoize(f) { + const table = make_table(); + return x => { + const previously_computed_result = + lookup(x, table); + if (is_undefined(previously_computed_result)) { + const result = f(x); + insert(x, result, table); + return result; + } else { + return previously_computed_result; + } + }; +} + + + 其中记忆化版本的 + + 过程 + 函数 + + 是 + $n$ 第 n 个斐波那契数,其步骤数与 【166:2†cn.txt】 的比例相同。 + $n$ 。该方案是否仍然有效,如果我们仅仅定义 + + memo-fib + memo_fib + + 为 + + (memoize fib)? + memoize(fib) + + + + +
diff --git a/xml/cn/chapter3/section3/subsection4.xml b/xml/cn/chapter3/section3/subsection4.xml new file mode 100644 index 000000000..4369afdfa --- /dev/null +++ b/xml/cn/chapter3/section3/subsection4.xml @@ -0,0 +1,1960 @@ + + + 数字电路模拟器 + + + + 数字电路模拟 + + + 设计复杂的数字系统,例如计算机,是一项重要的工程活动。 数字系统通过连接简单元素构建。 尽管这些单个元素的行为很简单,但它们的网络可以表现出非常复杂的行为。 对拟议电路设计进行计算机模拟是数字系统工程师使用的重要工具。 在本节中,我们设计了一个用于执行数字逻辑模拟的系统。 该系统是称为事件驱动模拟模拟事件驱动事件驱动模拟的程序类型的典型代表,其中的动作 (事件) 触发稍后发生的更多事件,进而触发更多事件,以此类推。 + + + + 我们的电路计算模型将由对象组成,这些对象对应于构建该电路的基本组件。 有 + 导线 (在数字电路中) + 导线 ,其承载 信号 (数字) + 数字信号 + 数字信号 。 一个 数字信号在任何时候只能有两个可能的值之一,0 和 1。 还有各种类型的数字 函数盒 (在数字电路中) + 函数盒 ,其连接承载输入信号的导线到其他输出导线。 这些盒子产生的输出信号是从其输入信号计算得出的。 输出信号是 延迟延迟 (在数字电路中) + 由函数盒的类型决定的时间延迟。 例如,一个 + 反相器 + 反相器 + 是一个 + 原语函数盒,其输入反转。 如果输入信号变为反相器的输入信号改变为0,那么一个 + + 是一个反相器,其输入反转。 如果 + 输入信号变为0, + + 稍后反相器将把其输出信号改变为 1。 如果反相器的输入信号变为 1,那么一个 + 反相器延迟 + 稍后反相器将改变其输出信号为 0。 我们以图形象征性地绘制一个反相器 + 。 一个 + 与门 + 与门 , + 也如图所示 + ,是一个原语 + 函数盒,具有两个输入和一个输出。 它将其输出信号驱动到一个 + 逻辑与 (数字逻辑) + 逻辑与 + 输入的。 也就是说,如果 + 两个输入信号都变为 + 1,那么一个 与门延迟 + 时间 + 之后,与门将强制其输出信号变为 1;否则输出将为 0。 一个 + 或门 + 或门 + 是一个类似的二输入原语函数 + 盒,将其输出信号驱动到一个 + 逻辑或 (数字逻辑) + 逻辑或 的输入。 也就是说,如果至少一个输入信号为 1,则输出将变为 1;否则输出将变为0。 + + +
+
+ 数字逻辑模拟器中的原语函数。 + + +
+ + + 我们可以将原语函数连接在一起以构建更复杂的函数。 为此,我们将一些 + 函数盒的输出连接到其他函数盒的输入。 例如, + 半加器 + 加法器 + 半加器电路如图 + 所示,由一个 + 或门、两个与门和一个反相器组成。 它接受两个输入信号, + $A$ 和 $B$,并有 + 两个输出信号,$S$ 和 $C$。 + 当且仅当$A$ 和 $B$ + 中正好有一个为1 时,$S$ 将变为 1, + 当且仅当$A$ 和 $B$ 都为 1 时,$C$ 将变为 1。 从图中我们可以看到,由于涉及的 + 延迟,输出可能在不同时间产生。 + 数字电路设计中的许多困难源于这一事实。 +
+
+ 半加器电路。 + + +
+
+ + + 现在我们将构建一个程序来对我们希望研究的数字逻辑电路进行建模。 该程序将构造计算对象以模拟导线,这些对象将保持信号。 函数 + 盒将由 + + 过程 + 函数 + + 模拟,以确保信号之间的正确关系。 + + + + 我们模拟的一个基本元素将是一个 + + 过程 + 函数 + + make_wire + + make-wire, + make_wire, + + 其用于创建导线。例如,我们可以如下创建六根导线: + + make_wire_usage + make_wire + +(define a (make-wire)) +(define b (make-wire)) +(define c (make-wire)) +(define d (make-wire)) +(define e (make-wire)) +(define s (make-wire)) + + +const a = make_wire(); +const b = make_wire(); +const c = make_wire(); +const d = make_wire(); +const e = make_wire(); +const s = make_wire(); + + + 我们通过调用一个 + + 过程 + 函数 + + 将函数盒附加到一组导线上,该函数构造该类型的盒子。 构造函数的 + 参数 + + 过程 + 函数 + + 是要附加到盒子的导线。 例如,给定 + 我们可以构造与门、或门和反相器,我们可以将 + 中显示的半加器连接在一起: + + or_gate_example + or_gate + make_wire_usage + 'ok' + +(or-gate a b d) + + +ok + + +or_gate(a, b, d); + + +"ok" + + + + and_gate_example + and_gate + make_wire_usage + 'ok' + +(and-gate a b c) + + +ok + + +and_gate(a, b, c); + + +"ok" + + + + inverter_example + inverter + make_wire_usage + 'ok' + +(inverter c e) + + +ok + + +inverter(c, e); + + +"ok" + + + + and_gate_example_2 + and_gate + make_wire_usage + 'ok' + +(and-gate d e s) + + +ok + + +and_gate(d, e, s); + + +"ok" + + + + + + 更好的是,我们可以通过定义一个 + + 过程 + 函数 + + + half-adder + half_@adder + + 显式地命名此操作,该函数构建此电路,给定要附加到半加器的四个外部导线: + + half_adder_example + half_adder + +const a = make_wire(); +const b = make_wire(); +const s = make_wire(); +const c = make_wire(); +half_adder(a, b, s, c); + + + + half-adderhalf_adder + half_adder + make_wire + or_gate + and_gate + inverter + half_adder_example + 'ok' + +(define (half-adder a b s c) + (let ((d (make-wire)) (e (make-wire))) + (or-gate a b d) + (and-gate a b c) + (inverter c e) + (and-gate d e s) + 'ok)) + + +function half_adder(a, b, s, c) { + const d = make_wire(); + const e = make_wire(); + or_gate(a, b, d); + and_gate(a, b, c); + inverter(c, e); + and_gate(d, e, s); + return "ok"; +} + + + 定义这个操作的好处是我们可以使用 + + half-adder + half_adder + + 本身作为构建块来创建更复杂的 + 电路。 例如,图显示了一个 + 全加器 + 加法器 + 全加器由两个半加器和一个或门组成。 + 全加器是用于加两个二进制数字的基本电路元素。 这里 $A$ 和 $B$ + 是两个数中对应位置的位,$C_{\mathit{in}}$ 是 + 从右边一位的加法进位位。 该电路生成 + $\mathit{SUM}$,即对应位置的和位,以及 + $C_{\mathit{out}}$,即 + 需要向左传播的进位位。 我们可以如下构建一个 + 全加器: + + full_adder_example + full_adder + +const a = make_wire(); +const b = make_wire(); +const c_in = make_wire(); +const sum = make_wire(); +const c_out = make_wire(); +full_adder(a, b, c_in, sum, c_out); + + + + full-adderfull_adder + full_adder + make_wire + half_adder + or_gate + full_adder_example + 'ok' + +(define (full-adder a b c-in sum c-out) + (let ((s (make-wire)) + (c1 (make-wire)) + (c2 (make-wire))) + (half-adder b c-in s c1) + (half-adder a s sum c2) + (or-gate c1 c2 c-out) + 'ok)) + + +function full_adder(a, b, c_in, sum, c_out) { + const s = make_wire(); + const c1 = make_wire(); + const c2 = make_wire(); + half_adder(b, c_in, s, c1); + half_adder(a, s, sum, c2); + or_gate(c1, c2, c_out); + return "ok"; +} + + + 已经定义了 + + full-adder + full_adder + + 作为一个 + + 过程, + 函数, + + 我们现在可以将其用作构建块来创建更复杂的 + 电路。 (例如,请参见练习。) + + <!-- 图形因 SICP JS 分页而移动 --> + <!-- 图形代码在该文件后文的 PDF_ONLY 中重复 --> +
+
+ 全加器电路。 + + +
+
+
+ + + 从本质上讲,我们的模拟器为我们提供了构造 + 电路语言的工具。 如果我们采用我们在 + + Lisp + JavaScript + + 中研究语言的总体视角,如章节所述, + 我们可以说原语函数盒构成了语言的基本 + 元素,将盒子连接在一起提供了一种 + 组合的方法,而将布线模式指定为 + + 过程 + 函数 + + 则作为一种抽象的方法。 + + + + 原语函数盒 + + + + + 原语函数盒 + 数字电路模拟原语函数盒 + 实现了作用力,使得一个导线信号的变化影响其他 + 导线上的信号。 为了构建函数盒,我们使用以下导线操作: +
    +
  • + + (get-signal wire): + + get_signal(wire) + + + get_signal +

    + 返回导线上信号的当前值。 +
  • +
  • + + (set-signal! wire new-value): + + + set_signal(wire, new-value): + + + set_signal +

    + 将导线上信号的值更改为新值。 +
  • +
  • + + + (add-action! wire procedure-of-no-arguments): + + + add_action(wire, function-of-no-arguments): + + + add_action +

    + 声明指定的 + + 过程 + 函数 + + 应该在导线上的信号值改变时运行。这样 + + 过程 + 函数 + + 便是将导线信号值的变化传递到其他导线的方式。 +
  • +
+ 此外,我们将使用一个 + + 过程 + 函数 + + after_delay + + + after-delay + + + after_delay + + + 接受一个时间延迟和一个 + + 过程 + 函数 + + 来执行,并在指定的 + 时间延迟后执行给定的 + + 过程 + 函数 + 【4:4†cn.txt】。 +
+ + + 使用这些 + + 过程, + 函数, + + 我们可以定义原语数字逻辑函数。 要通过反相器将输入连接到输出,我们使用 + + add-action! + add_action + + + 以将一个 + + 过程 + 函数 + + 与输入导线相关联,该函数将在输入导线的信号值变化时运行。 + 该 + + 过程 + 函数 + + 计算输入信号的 + + 逻辑非 + logical_not + + + ,然后在一个 + + 反相器延迟 + inverter_delay, + + + 之后,将输出信号设置为此新值: + + inverterinverter + logical_not + inverter + get_signal + after_delay + inverter_example + 'ok' + +(define (inverter input output) + (define (invert-input) + (let ((new-value (logical-not (get-signal input)))) + (after-delay inverter-delay + (lambda () + (set-signal! output new-value))))) + (add-action! input invert-input) + 'ok) + +(define (logical-not s) + (cond ((= s 0) 1) + ((= s 1) 0) + (else (error "Invalid signal" s)))) + + +function inverter(input, output) { + function invert_input() { + const new_value = logical_not(get_signal(input)); + after_delay(inverter_delay, + () => set_signal(output, new_value)); + } + add_action(input, invert_input); + return "ok"; +} +function logical_not(s) { + return s === 0 + ? 1 + : s === 1 + ? 0 + : error(s, "invalid signal"); +} + + + + + + logical_and + +function logical_and(s1, s2) { + return s1 === 1 && s2 === 1 + ? 1 + : s1 === 0 || s1 === 1 + ? s2 === 0 || s2 === 1 + ? 0 + : error(s2, "invalid signal") + : error(s1, "invalid signal"); +} + + + + <!-- 图形因 SICP JS 分页而移动 --> + <!-- 图形代码是该文件之前 WEB_ONLY 中代码的副本 --> +
+
+ 全加器电路。 + + +
+
+ + + + 与门稍微复杂些。 操作 + + 过程 + 函数 + + 必须在门的任一输入发生变化时运行。它计算输入导线信号值的 + + 逻辑与 + (使用类似于 + 逻辑非的过程) + + + logical_and + (使用类似于 + logical_not的函数) + + + ,并在一个 + + 与门延迟 + and_gate_delay. + + + 之后,将新值出现在输出导线上。【192:6†cn.txt】 + + and-gateand_gate + and_gate + get_signal + after_delay + logical_and + and_gate_example + 'ok' + +(define (and-gate a1 a2 output) + (define (and-action-procedure) + (let ((new-value + (logical-and (get-signal a1) (get-signal a2)))) + (after-delay and-gate-delay + (lambda () + (set-signal! output new-value))))) + (add-action! a1 and-action-procedure) + (add-action! a2 and-action-procedure) + 'ok) + + +function and_gate(a1, a2, output) { + function and_action_function() { + const new_value = logical_and(get_signal(a1), + get_signal(a2)); + after_delay(and_gate_delay, + () => set_signal(output, new_value)); + } + add_action(a1, and_action_function); + add_action(a2, and_action_function); + return "ok"; +} + + + + + + 定义一个 + or-gate或门or-gateor_gate + 或门作为一个原语函数盒。 你的 + + or-gate + or_gate + + + 构造器应与 + + and-gate. + and_gate. + + + 类似。 + + + + logical_or + +// 由 GitHub 用户 clean99 提供 + +function logical_or(s1, s2) { + return s1 === 0 && s2 === 0 + ? 0 + : s1 === 0 || s1 === 1 + ? s2 === 0 || s2 === 1 + ? 1 + : error(s2, "invalid signal") + : error(s1, "invalid signal"); +} + + + + or_gate + get_signal + after_delay + logical_or + +// 由 GitHub 用户 clean99 提供 + +function or_gate(a1, a2, output) { + function or_action_function() { + const new_value = logical_or(get_signal(a1), + get_signal(a2)); + after_delay(or_gate_delay, + () => set_signal(output, new_value)); + } + add_action(a1, or_action_function); + add_action(a2, or_action_function); + return "ok"; +} + + + + + + + 另一种构造 + or-gate或门or-gateor_gate + 或门的方法是作为一种由与门和反相器构建的复合数字逻辑 + 设备。 定义一个 + + 过程 + 函数 + + + or-gate + or_gate + + 来完成此任务。 在 + + 与门延迟 + and_gate_delay + + + 和 + + 反相器延迟? + inverter_delay? + + + 下计算或门的延迟时间。 + + (由 GitHub 用户 taimoon 提供) + + 思路:~(~a & ~b) = nand(~a, ~b) = ~~a | ~~b = a | b + + +function nand_gate(a1, a2, out){ + const tmp = make_wire(); + and_gate(a1, a2, tmp); + inverter(tmp, out); +} + +function or_gate(a1, a2, out){ + const not_a1 = make_wire(); + const not_a2 = make_wire(); + inverter(a1, not_a1); + inverter(a2, not_a2); + nand_gate(not_a1, not_a2, out); +} + + + nand门的延迟时间为 + nand_gate_delay = and_gate_delay + inverter_delay,上述或门的延迟时间为 + or_gate_delay = nand_gate_delay + inverter_delay = and_gate_delay + 2 * inverter_delay。 + + + + + + 图显示了一个 + 行波进位加法器 + 加法器行波进位 + 行波进位加法器通过串接形成 +$n$ + 图显示了一个 + 行波进位加法器 + 加法器行波进位 + 行波进位加法器通过串接多个 + 全加器构成。 + 这是用于加两个二进制数的最简单的并行加法器形式。 +$n$ + 图显示了一个 + 行波进位加法器 + 加法器行波进位 + 行波进位加法器通过串接多个全加器构成。 + 这是用于加两个-bit二进制数的最简单的并行加法器形式。 + 输入 +$A_{1}$, + $A_{2}$, + $A_{3}$, , +$A_{n}$ 和 +$B_{1}$, + $B_{2}$, + $B_{3}$, , +$B_{n}$ + 是要相加的两个二进制数(每个 +$A_{k}$ 和 +$B_{k}$ + 是0或1)。电路生成 +$S_{1}$, + $S_{2}$, + $S_{3}$, + , +$S_{n}$ , + 这个 +$n$ + , + 这个 + 总和的位数,和 +$C$ ,从加法进位。编写一个 + + 过程 + 函数 + + + ripple-carry-adder + ripple_carry_adder + + + 来生成这个电路。该 + + 过程 + 函数 + + 应接受三个列表作为参数 +$n$ + 导线,每个这个 +$A_{k}$ ,这个 +$B_{k}$ ,和这个 +$S_{k}$ 并且 + 也有另一根导线 +$C$ +。行波进位加法器的主要缺点是需要等待进位信号传播。获取完整输出所需的延迟为 +$n$ -位行波进位加法器的完整输出所需的延迟,用于与门、或门和反相器的延迟来表示。 + + + +
+
+ + $n$-位数的行波进位加法器。 + + +
+ + 数字电路模拟原语函数盒 + + + 表示导线 + + + 在我们的模拟中 + 数字电路模拟表示导线 + ,导线将是一个具有两个局部 + 状态变量的计算对象: + + a信号值, + asignal_value + + + (初始值为0)和一组 + + 操作过程, + action_functions + + + ,当信号值变化时会运行。我们采用 + 消息传递在数字电路模拟中 + 的消息传递风格 + 实现导线,作为一系列局部 + + 过程 + 函数 + + 的集合 +dispatch + 过程 + 函数 + + ,用于选择适当的局部操作,就像我们 + 在章节中处理简单的银行账户对象那样: + + make_wire + make_wire + call_each + make_wire_usage + +(define (make-wire) + (let ((signal-value 0) (action-procedures '())) + (define (set-my-signal! new-value) + (if (not (= signal-value new-value)) + (begin (set! signal-value new-value) + (call-each action-procedures)) + 'done)) + +(define (accept-action-procedure! proc) + (set! action-procedures (cons proc action-procedures)) + (proc)) + +(define (dispatch m) + (cond ((eq? m 'get-signal) signal-value) + ((eq? m 'set-signal!) set-my-signal!) + ((eq? m 'add-action!) accept-action-procedure!) + (else (error "Unknown operation -- WIRE" m)))) + dispatch)) + + +function make_wire() { + let signal_value = 0; + let action_functions = null; + function set_my_signal(new_value) { + if (signal_value !== new_value) { + signal_value = new_value; + return call_each(action_functions); + } else { + return "done"; + } + } + function accept_action_function(fun) { + action_functions = pair(fun, action_functions); + fun(); + } + function dispatch(m) { + return m === "get_signal" + ? signal_value + : m === "set_signal" + ? set_my_signal + : m === "add_action" + ? accept_action_function + : error(m, "unknown operation -- wire"); + } + return dispatch; +} + + +局部的 + + 过程 + 函数 + + + set-my-signal + set_my_signal + + +测试新信号值是否改变了导线上信号。 如果是这样,它则运行每一个 + + 过程 + 函数 + +使用以下 + + 过程 + 函数 + + + call-each, + call_each + +用于调用不带参数的过程列表中的每一项: + + 过程: + 函数: + + + call_each + call_each + +(define (call-each procedures) + (if (null? procedures) + 'done + (begin + ((car procedures)) + (call-each (cdr procedures))))) + + +function call_each(functions) { + if (is_null(functions)) { + return "done"; + } else { + head(functions)(); + return call_each(tail(functions)); + } +} + + +局部 + + 过程 + 函数 + + + 接受-操作-过程, + accept_action_function + + +将给定的 + + 过程 + 函数 + +添加到要执行的 + + 过程列表中, + 函数 + +然后运行新的 + + 过程 + 函数 + +一次。 (参见练习。) + + + + 使用局部 +dispatch + + 过程 + 函数 + + 设置为指定,可以提供以下 + + 过程 + 函数 + + 以访问 + 导线上的局部操作: + 这些 + + 过程 + 函数 + +只是语法糖,允许我们使用普通的 + + + 过程性 + + + 函数式 + + +语法访问对象的局部 + + 过程 + 函数 +。令人惊讶的是,我们可以如此简单地交换 +过程 +函数 + +和 +数据的角色。 例如,如果我们写 + + (wire 'get-signal) + wire("get_signal") + + +我们认为 + wire 作为一个 + + 过程 + 函数 + +,用消息 + + get-signal + "get_signal" + + +作为输入进行调用。或者,编写 + + (get-signal wire) + get_signal(wire) + + +鼓励我们思考 +wire 作为一个数据 +对象,它是 + + 过程 + 函数 + +的输入 + + 获取信号 + get_signal + + +。 问题的本质在于,在一个可以将 +过程 +函数 + +视为对象的语言中, +过程 +函数 + +和数据之间没有根本区别,我们可以选择我们的语法糖使我们能以我们选择的风格进行编程。 + + + get_signal + set_signal + add_action + get_signal + +(define (get-signal wire) + (wire 'get-signal)) + +(define (set-signal! wire new-value) + ((wire 'set-signal!) new-value)) + + +(define (add-action! wire action-procedure) + ((wire 'add-action!) action-procedure)) + + +function get_signal(wire) { + return wire("get_signal"); +} +function set_signal(wire, new_value) { + return wire("set_signal")(new_value); +} +function add_action(wire, action_function) { + return wire("add_action")(action_function); +} + + + + + +导线,其中包含随时间变化的信号,可能逐步连接到设备上,是可变对象的典型代表。我们将它们建模为具有局部状态变量的 + + 过程 + 函数 + +,通过赋值进行修改。当创建新导线时,会分配一组新的状态变量(通过 + + let 表达式在 + + let 语句在 + + + + make-wire) + make_wire) + +中)并构造和返回一个新的 dispatch + + 过程 + 函数 + +,捕获具有新状态变量的环境。 + + + +导线是在已连接的各种设备之间共享的。因此,通过与一个设备的交互所做的更改将影响所有附加到该导线的其他设备。导线通过调用在建立连接时提供给它的动作 + + 过程 + 函数 + +来与其邻居通信更改。 + + + 数字电路模拟表示导线 + + + 议程 + + + 数字电路模拟议程 + + + 完成模拟器所需的唯一元素是 + + after-delay. + after_delay. + + 这里的想法是我们维护一个数据结构,称为 + 议程,它包含待办事项的计划。 + 为议程定义了以下操作: +
    +
  • + + (make-agenda): + make_agenda(): + + + make_agenda +

    + 返回一个新的空议程。 +
  • +
  • + + (empty-agenda? agenda): + + + is_empty_agenda(agenda) + + + is_empty_agenda +

    + 如果指定的议程为空,则为 true。 +
  • +
  • + + (first-agenda-item agenda): + + + first_agenda_item(agenda) + + + first_agenda_item +

    + 返回议程上的第一个项目。 +
  • +
  • + + + (remove-first-agenda-item! agenda): + + + remove_first_agenda_item(agenda) + + + remove_first_agenda_item +

    + 通过移除第一个项目来修改议程。 +
  • +
  • + + + (add-to-agenda! time action agenda): + + + add_to_agenda(time, action, agenda) + + + add_to_agenda +

    + 通过添加指定的动作 + + 过程 + 函数 + + 来修改议程,以便在指定的时间运行。 +
  • +
  • + + (current-time agenda): + + + current_time(agenda) + + + current_time +

    + 返回当前的模拟时间。 +
  • +
+
+ + + 我们使用的特定议程表示为 + + 议程. + 议程. + + 该 + + 过程 + 函数 + + + 延迟后 + after_delay + + 向 + + 议程中添加新元素: + 议程中添加新元素: + + + after_delay + after_delay + 添加到议程中 + 创建议程 + 议程 + +(define (after-delay delay action) + (add-to-agenda! (+ delay (current-time the-agenda)) + action + the-agenda)) + + +function after_delay(delay, action) { + add_to_agenda(delay + current_time(the_agenda), + action, + the_agenda); +} + + + + + + + + 模拟由过程驱动 + propagate,它在 + the-agenda上运行, + 顺序执行议程中的每个过程。 + + + 模拟由函数驱动 + propagate,它顺序执行 + the_agenda + 上的每个函数。 + + + 一般来说,随着模拟运行,新的项目 + 将被添加到议程中,propagate + 将继续模拟,只要议程上还有项目: + + propagate + propagate + 删除第一个议程项目 + 第一个议程项目 + 议程 + +(define (propagate) + (if (empty-agenda? the-agenda) + 'done + (let ((first-item (first-agenda-item the-agenda))) + (first-item) + (remove-first-agenda-item! the-agenda) + (propagate)))) + + +function propagate() { + if (is_empty_agenda(the_agenda)) { + return "done"; + } else { + const first_item = first_agenda_item(the_agenda); + first_item(); + remove_first_agenda_item(the_agenda); + return propagate(); + } +} + + + + + 数字电路模拟议程 + + + + 模拟示例 + + + 数字电路模拟模拟示例 + 数字电路模拟模拟示例 + + 半加器模拟 + + + 我们首先通过初始化议程并为 + 原语函数盒指定延迟: + + the_agenda + make_agenda + +(define the-agenda (make-agenda)) +(define inverter-delay 2) +(define and-gate-delay 3) +(define or-gate-delay 5) + + +const the_agenda = make_agenda(); +const inverter_delay = 2; +const and_gate_delay = 3; +const or_gate_delay = 5; + + + 现在我们定义四根导线,并在其中两根上放置探测器: + + probing_two_wires + make_wire + probe + +(define input-1 (make-wire)) +(define input-2 (make-wire)) +(define sum (make-wire)) +(define carry (make-wire)) + +(probe 'sum sum) + + + sum 0 New-value = 0 + + +const input_1 = make_wire(); +const input_2 = make_wire(); +const sum = make_wire(); +const carry = make_wire(); + +probe("sum", sum); + + +"sum 0, new value = 0" + + + + probe_carry + probing_two_wires + +(probe 'carry carry) + + +carry 0 New-value = 0 + + +probe("carry", carry); + + +"carry 0, new value = 0" + + + + 过程, + 函数, + + 一个在导线上放置探测器的过程/函数,展示了模拟器的 + 一种用法。探测器指示导线每当其信号改变时,需打印新信号值,连同 + 当前时间和识别 + 导线: + 导线: + + + probe在数字电路模拟器中 + 探测器 + 议程 + get_signal + +(define (探测器 名称 导线) + (add-action! 导线 + (lambda () + (newline) + (display 名称) + (display " ") + (display (current-time 议程)) + (display " 新值 = ") + (display (get-signal 导线))))) + + +function probe(name, wire) { + add_action(wire, + () => display(name + " " + + stringify(current_time(the_agenda)) + + ", new value = " + + stringify(get_signal(wire)))); +} + + +function probe(name, wire) { + add_action(wire, + () => name + " " + + stringify(current_time(the_agenda)) + + ", new value = " + + stringify(get_signal(wire))); +} + + + + half_adder_example_2 + half_adder + probe_carry + 'ok' + +(half-adder input-1 input-2 sum carry) + + +ok + + +half_adder(input_1, input_2, sum, carry); + + +"ok" + + + + set_signal_example + half_adder_example_2 + 'done' + +(set-signal! input-1 1) + + +done + + +set_signal(input_1, 1); + + +"done" + + + + propagate_example_1 + set_signal_example + propagate + 'done' + +(propagate) + + +sum 8 New-value = 1 +done + + +propagate(); + + +"sum 8, new value = 1" +"done" + + + 这个 +sum + 信号在时间8变为1。 + 我们现在距离模拟开始时已有八个时间单位。 + 在此时,我们可以设置 + + 输入-2 + input_2 + + 的信号为1,并让值传播: + + set_signal_example_2 + propagate_example_1 + 'done' + +(set-signal! input-2 1) + + + done + + +set_signal(input_2, 1); + + +"done" + + + + propagate_example_2 + set_signal_example_2 + 'done' + +(propagate) + + +carry 11 New value = 1 +sum 16 New value = 0 +done + + +propagate(); + + +"carry 11, new value = 1" +"sum 16, new value = 0" +"done" + + + The carry changes to 1 at time 11 and the + sum changes to 0 at time 16. + + + digital-circuit simulationsample simulation + half-addersimulation of + + + + The internal + + procedure + function + + + accept-action-procedure! + accept_action_function + + + defined in + make_wire + + make-wire + make_wire + + + specifies that when a new action + + procedure + function + + is added to + a wire, the + + procedure + function + + is immediately run. Explain why this initialization + is necessary. In particular, trace through the half-adder example in + the paragraphs above and say how the systems response would differ + if we had defined + + accept-action-procedure! + accept_action_function + + + as + + +(define (accept-action-procedure! proc) + (set! action-procedures (cons proc action-procedures))) + + +function accept_action_function(fun) { + action_functions = pair(fun, action_functions); +} + + + + + + Implementing the agenda + + + digital-circuit simulationagenda implementation + + + Finally, we give details of the agenda data structure, which holds the + + procedures + functions + + that are scheduled for future execution. + + + + The agenda is made up of + time segment, in agenda + time segments. Each time segment is a + pair consisting of a number (the time) and a + queuesimulationin simulation agenda + queue (see + exercise) that holds the + + procedures + functions + + that are scheduled to be run during that time segment. + + make_time_segment + segment_time + segment_queue + make_time_segment + +(define (make-time-segment time queue) + (cons time queue)) + +(define (segment-time s) (car s)) + +(define (segment-queue s) (cdr s)) + + +function make_time_segment(time, queue) { + return pair(time, queue); +} +function segment_time(s) { return head(s); } + +function segment_queue(s) { return tail(s); } + + + We will operate on the time-segment queues using the queue operations + described in section. + + + + The agenda itself is a one-dimensional + tableused in simulation agenda + table of time segments. It + differs from the tables described in section + in that the segments will be sorted in order of increasing time. In + addition, we store the + current time, for simulation agenda + current time (i.e., the time of the last action + that was processed) at the head of the agenda. A newly constructed + agenda has no time segments and has a current time of 0:The + agenda is a + headed list + list(s)headed + headed list, like the tables in section, + but since the list is headed by the time, we do not need an additional + dummy header (such as the + + + *table* symbol + + + "*table*" string + + + used + with tables). + + make_agenda + current_time + set_current_time + segments + set_segments + first_segment + rest_segments + make_agenda + +(define (make-agenda) (list 0)) + +(define (current-time agenda) (car agenda)) + +(define (set-current-time! agenda time) + (set-car! agenda time)) + +(define (segments agenda) (cdr agenda)) + +(define (set-segments! agenda segments) + (set-cdr! agenda segments)) + +(define (first-segment agenda) (car (segments agenda))) + +(define (rest-segments agenda) (cdr (segments agenda))) + + +function make_agenda() { return list(0); } + +function current_time(agenda) { return head(agenda); } + +function set_current_time(agenda, time) { + set_head(agenda, time); +} +function segments(agenda) { return tail(agenda); } + +function set_segments(agenda, segs) { + set_tail(agenda, segs); +} +function first_segment(agenda) { return head(segments(agenda)); } + +function rest_segments(agenda) { return tail(segments(agenda)); } + + + + + + An agenda is empty if it has no time segments: + + is_empty_agenda + is_empty_agenda + make_agenda + +(define (empty-agenda? agenda) + (null? (segments agenda))) + + +function is_empty_agenda(agenda) { + return is_null(segments(agenda)); +} + + + + + + To add an action to an agenda, we first check if the agenda is empty. + If so, we create a time segment for the action and install this in + the agenda. Otherwise, we scan the agenda, examining the time of each + segment. If we find a segment for our appointed time, we add the + action to the associated queue. If we reach a time later than the one + to which we are appointed, we insert a new time segment into the + agenda just before it. If we reach the end of the agenda, we must + create a new time segment at the end. + + add_to_agenda + add_to_agenda + make_time_segment + make_queue + insert_queue + make_time_segment + make_agenda + +(define (add-to-agenda! time action agenda) + (define (belongs-before? segments) + (or (null? segments) + (< time (segment-time (car segments))))) + (define (make-new-time-segment time action) + (let ((q (make-queue))) + (insert-queue! q action) + (make-time-segment time q))) + (define (add-to-segments! segments) + (if (= (segment-time (car segments)) time) + (insert-queue! (segment-queue (car segments)) + action) + (let ((rest (cdr segments))) + (if (belongs-before? rest) + (set-cdr! + segments + (cons (make-new-time-segment time action) + (cdr segments))) + (add-to-segments! rest))))) + (let ((segments (segments agenda))) + (if (belongs-before? segments) + (set-segments! + agenda + (cons (make-new-time-segment time action) + segments)) + (add-to-segments! segments)))) + + +function add_to_agenda(time, action, agenda) { + function belongs_before(segs) { + return is_null(segs) || time < segment_time(head(segs)); + } + function make_new_time_segment(time, action) { + const q = make_queue(); + insert_queue(q, action); + return make_time_segment(time, q); + } + function add_to_segments(segs) { + if (segment_time(head(segs)) === time) { + insert_queue(segment_queue(head(segs)), action); + } else { + const rest = tail(segs); + if (belongs_before(rest)) { + set_tail(segs, pair(make_new_time_segment(time, action), + tail(segs))); + } else { + add_to_segments(rest); + } + } + } + const segs = segments(agenda); + if (belongs_before(segs)) { + set_segments(agenda, + pair(make_new_time_segment(time, action), segs)); + } else { + add_to_segments(segs); + } +} + + + + + + The + + procedure + function + + that removes the first item from the agenda deletes the + item at the front of the queue in the first time segment. If this + deletion makes the time segment empty, we remove it from the list of + segments:Observe that the + + + + if + expression in this + procedure + has no alternative expression. + + + conditional statement in this + function has an + blockempty + empty block as its alternative statement. + + + Such a + conditional statementone-armed (without alternative) + + + one-armed if expression + + + one-armed conditional statement + + + is used to decide whether to do something, rather than to select between two + + + expressions. + An if expression returns an + unspecified value if the predicate is false and there is no + alternative. + + statements. + + + + remove_first_agenda_item + remove_first_agenda_item + make_agenda + is_empty_queue + delete_queue + make_time_segment + +(define (remove-first-agenda-item! agenda) + (let ((q (segment-queue (first-segment agenda)))) + (delete-queue! q) + (if (empty-queue? q) + (set-segments! agenda (rest-segments agenda))))) + + +function remove_first_agenda_item(agenda) { + const q = segment_queue(first_segment(agenda)); + delete_queue(q); + if (is_empty_queue(q)) { + set_segments(agenda, rest_segments(agenda)); + } else {} +} + + + + + + The first agenda item is found at the head of the queue in the first + time segment. Whenever we extract an item, we also update the current + time:In this way, the current time will always be the time + of the action most recently processed. Storing this time at the head + of the agenda ensures that it will still be available even if the + associated time segment has been deleted. + + first_agenda_item + first_agenda_item + is_empty_agenda + make_time_segment + front_queue + +(define (first-agenda-item agenda) + (if (empty-agenda? agenda) + (error "Agenda is empty -- FIRST-AGENDA-ITEM") + (let ((first-seg (first-segment agenda))) + (set-current-time! agenda (segment-time first-seg)) + (front-queue (segment-queue first-seg))))) + + +function first_agenda_item(agenda) { + if (is_empty_agenda(agenda)) { + error("agenda is empty -- first_agenda_item"); + } else { + const first_seg = first_segment(agenda); + set_current_time(agenda, segment_time(first_seg)); + return front_queue(segment_queue(first_seg)); + } +} + + + + + + The + + procedures + functions + + to be run during each time segment of the agenda are kept in a queue. + Thus, the + + procedures + functions + + for each segment are called in the order in which they were added to the + agenda (first in, first out). Explain why this order must be used. In + particular, trace the behavior of an and-gate whose inputs change from + 0,1 to 1,0 in the same segment and say how the behavior would differ if + we stored a segments + + procedures + functions + + in an ordinary list, adding and removing + + procedures + functions + + only at the front (last in, first out). + + + + digital-circuit simulation + digital-circuit simulationagenda implementation + +
diff --git a/xml/cn/chapter3/section4/section4.xml b/xml/cn/chapter3/section4/section4.xml new file mode 100644 index 000000000..068c28074 --- /dev/null +++ b/xml/cn/chapter3/section4/section4.xml @@ -0,0 +1,75 @@ +
+ 并发: 时间至关重要 + + + + + + 并发 + + + 我们已经见识过具有局部状态的计算对象作为建模工具的强大功能。然而,正如章节所警告的,这种功能是有代价的:丧失了引用透明性,引发了一系列关于相同性和变化的问题,并且需要放弃替换模型的求值,转而使用更加复杂的环境模型。 + + + + 隐藏在状态的复杂性、相同性与变化之下的核心问题是,通过引入赋值,我们被迫将时间与赋值时间引入到我们的计算模型中。在引入赋值之前,我们的所有程序都是无时间性的,因为任何具有值的表达式总是拥有相同的值。相比之下,回忆在章节开始时介绍的一个示例:模拟银行账户的提款并返回结果余额。 + + + withdraw_example_3_4 + withdraw + 75 + +(withdraw 25) + + +withdraw(25); + + +75 + + +75 + + + + + withdraw_example_3_4_second + withdraw + withdraw_example_3_4 + 50 + +(withdraw 25) + + +withdraw(25); + + +50 + + +50 + + + 在这里,对同一表达式的连续求值产生不同的值。这种行为源于执行赋值(在本例中,对变量balance的赋值)划定了值改变的时间点。求值表达式的结果不仅取决于表达式本身,还取决于求值是否发生在这些时间点之前或之后。使用具有局部状态的计算对象构建模型迫使我们将时间视为编程中的一个基本概念。 + + + + 我们可以进一步构建计算模型以匹配我们对物理世界的感知。世界中的对象并不是一个接一个地依次改变的。相反,我们感知它们为并发的同时发生的。因此,将系统建模为并发执行的计算过程thread线程(计算步骤的序列)集合通常是自然的。在整本书中,这样的顺序线程被称为过程,但在本节中,我们使用术语线程来强调它们对共享内存的访问。就像我们通过具有分离局部状态的对象组织模型使程序模块化一样,将计算模型划分为分开并且并发演化的部分通常也是合适的。即使程序是在一个顺序计算机上执行,把程序写得好像它们要并发执行一样,迫使程序员避免非本质的时间约束,从而使程序更模块化。 + + + + + 除了使程序更模块化之外,并发计算还可以比顺序计算提供速度优势。顺序计算机一次只执行一个操作,因此执行任务所需的时间与执行的总操作数成正比。大多数实际处理器实际上一次执行几个操作,采用一种称为流水线流水线的策略。尽管这种技术极大地提高了硬件的有效利用率,但仅用于加速顺序指令流的执行,同时保持顺序程序的行为。然而,如果可能将问题分解为相对独立且只需偶尔通信的部分,则可能将这些部分分配给不同的计算处理器,从而产生与可用处理器数量成正比的速度优势。 + + + + 不幸的是,由赋值引入的复杂性在并发存在时变得更加成问题。并发执行的事实,无论是因为世界并行运作还是因为我们的计算机这样做,都带来了我们对时间理解上的额外复杂性。 + + + + &subsection3.4.1; + + + &subsection3.4.2; + +
diff --git a/xml/cn/chapter3/section4/subsection1.xml b/xml/cn/chapter3/section4/subsection1.xml new file mode 100644 index 000000000..7cdf096c4 --- /dev/null +++ b/xml/cn/chapter3/section4/subsection1.xml @@ -0,0 +1,458 @@ + + + The Nature of Time in Concurrent Systems + + + + + timeconcurrentin concurrent systems + + + On the surface, time seems straightforward. It + is an ordering imposed on events.To quote some graffiti seen + on a + building wall in Cambridge, + Massachusetts: Time + timepurpose of + is a device that was invented to keep + everything from happening at once. + For any events $A$ and + $B$, + either $A$ occurs before + $B$, + $A$and + $B$ are simultaneous, or + $A$ occurs after + $B$. For instance, + returning to the bank account example, suppose that Peter withdraws + 10 and Paul withdraws 25 from a + bank accountjoint, with concurrent access + joint account that initially + contains 100, leaving 65 in the account. Depending on the + order of the two withdrawals, the sequence of balances in the account is + either $\$100 \rightarrow \$90 \rightarrow\$65$ + or $\$100 \rightarrow \$75 \rightarrow\$65$. + In a computer implementation of the banking system, this changing + sequence of balances could be modeled by successive assignments to + a variable balance. + + + + In complex situations, however, such a view can be problematic. + Suppose that Peter and Paul, and other people besides, are + accessing the same bank account through a network of banking machines + distributed all over the world. The actual sequence of balances in + the account will depend critically on the detailed timing of the + accesses and the details of the communication among the machines. + + + + This + order of eventsindeterminacy in concurrent systems + indeterminacy in the order of events can pose serious problems in + the design of concurrent systems. For instance, suppose that the + withdrawals made by Peter and Paul are implemented as two separate + processes + threads + sharing a common variable balance, each + process + thread + specified by the + procedure + function + given in section: + + withdraw_concurrency + balance_concurrency + withdraw_example_concurrency + 75 + +(define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + + +function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } +} + + + + + balance_concurrency + +let balance = 100; + + + + + withdraw_example_concurrency + +(withdraw 25) ;; output: 75 + + +75 + + +withdraw(25); // output: 75 + + + If the two + processes + threads + operate independently, then Peter might test the + balance and attempt to withdraw a legitimate amount. + withdrawproblems in concurrent system + However, Paul + might withdraw some funds in between the time that Peter checks the + balance and the time Peter completes the withdrawal, thus invalidating + Peters test. + + + + Things can be worse still. Consider the + + + expression + + + statement + + + + +(set! balance (- balance amount)) + + +balance = balance - amount; + + + executed as part of each withdrawal process. This consists of three + steps: (1) accessing the value of the balance + variable; (2) computing the new balance; (3) setting + balance to this new value. If Peter and + Pauls withdrawals execute this statement concurrently, then the + two withdrawals might interleave the order in which they access + balance and set it to the new value. + + + + + + +
+ + Timing diagram + timing diagram + showing how interleaving the order of events + in two banking withdrawals can lead to an incorrect final balance. + +
+
+ +
+ + Timing diagram + timing diagram + showing how interleaving the order of events + in two banking withdrawals can lead to an incorrect final balance. + +
+
+
+ + + The timing diagram in + + + figure + + + figure + + + depicts + an order of events where balance starts at 100, + Peter withdraws 10, Paul withdraws 25, and yet the final value of + balance is 75. As shown in the diagram, + the reason for this anomaly is that Pauls assignment of 75 to + balance is made under the assumption that + the value of balance to be decremented is 100. + That assumption, however, became invalid when Peter changed + balance to 90. This is a catastrophic + failure for the banking system, because the total amount of money in the + system is not conserved. Before the transactions, the total amount of + money was 100. Afterwards, Peter has 10, Paul has + 25, and the bank has 75.An even worse + failure for this system could occur if the two + + set! operations + assignments + + attempt to change the balance simultaneously, in which case the actual data + appearing in memory might end up being a random combination of the + information being written by the two + processes + threads. + Most computers have interlocks on + the primitive memory-write operations, which protect against such + simultaneous access. Even this seemingly simple kind of protection, + however, raises implementation challenges in the design of + multiprocessing computers, where elaborate + cache-coherence protocols + cache-coherence + protocols are required to ensure that the various processors will + maintain a consistent view of memory contents, despite the fact that + data may be replicated (cached) among the different + processors to increase the speed of memory access. + + + + The general phenomenon illustrated + here is that several + processes + threads + may + stateshared + shared state + share a common state variable. What makes this complicated is that more than one + process + thread + may be trying to manipulate the shared state at the same + time. For the bank account example, during each transaction, each + customer should be able to act as if the other customers did not + exist. When + + a customer changes + customers change + + the balance in a way that depends on + the balance, + + he + they + + must be able to assume that, just before the moment of + change, the balance is still what + + he + they + + thought it was. + + + + Correct behavior of concurrent programs + + + concurrencycorrectness of concurrent programs + + + The above example typifies the subtle bugs that can creep into + concurrent programs. The root of this complexity lies in the + assignments to variables that are shared among the different + processes + threads. + We already know that we must be careful in writing programs that use + set!, + assignment, + because the results of a computation depend on the order in which the + assignments occur.The factorial program in + section illustrates this for + a single sequential + process + thread. + With concurrent + processes + threads + we must be especially careful about + assignments, because we may not be able to control the order of the + assignments made by the different + processes + threads. + If several such changes + might be made concurrently (as with two depositors accessing a joint + account) we need some way to ensure that our system behaves correctly. + For example, in the case of withdrawals from a joint bank account, we + must ensure that money is conserved. + To make concurrent programs behave correctly, we may have to + place some restrictions on concurrent execution. + + + One possible restriction on concurrency would stipulate that no two + operations that change any shared state variables can occur at the same + time. This is an extremely stringent requirement. For distributed banking, + it would require the system designer to ensure that only one transaction + could proceed at a time. This would be both inefficient and overly + conservative. Figure shows + Peter and Paul sharing a bank account, where Paul has a private account + as well. The diagram illustrates two withdrawals from the shared account + (one by Peter and one by Paul) and a deposit to Pauls private + account.The columns show the contents of Peters wallet, + the joint account (in Bank1), Pauls wallet, and Pauls + private account (in Bank2), before and after each withdrawal (W) and + deposit (D). Peter withdraws 10 from Bank1; Paul deposits + 5 in Bank2, then withdraws 25 from Bank1. + The two withdrawals from the shared account must not be concurrent (since + both access and update the same account), and Pauls deposit and + withdrawal must not be concurrent (since both access and update the amount + in Pauls wallet). But there should be no problem permitting + Pauls deposit to his private account to proceed concurrently with + Peters withdrawal from the shared account. +
+
+ + Concurrent deposits and withdrawals from a + joint account in Bank1 and a private account in Bank2. + +
+
+ + + A less stringent restriction on concurrency would ensure that a + concurrent system produces the same result as if the + processes + threads + had run sequentially in some order. There are two important aspects to this + requirement. First, it does not require the + + processes + threads + + to actually run sequentially, but only to produce results that are the same + as if they had run sequentially. For the example in + figure, the designer of the + bank account system can safely allow Pauls deposit and Peters + withdrawal to happen concurrently, because the net result will be the same as + if the two operations had happened sequentially. Second, there may be more + than one possible correct result produced by a concurrent + program, because we require only that the result be the same as for + some sequential order. For example, suppose that Peter and + Pauls joint account starts out with 100, and Peter deposits + 40 while Paul concurrently withdraws half the money in the account. + Then sequential execution could result in the account balance being either + 70 or 90 (see + exercise).A more formal way + to express this idea is to say that concurrent programs are inherently + nondeterminism, in behavior of concurrent programs + nondeterministic. That is, they are described not by single-valued + functions, but by functions whose results are sets of possible values. + In section we will + study a language for expressing nondeterministic computations. + + + + + There are still weaker requirements for correct execution of concurrent + programs. A program for simulating + diffusion, simulation of + diffusion (say, the flow of heat in an object) might consist of a large + number of + + processes, + threads, + each one representing a small volume of space, that update their values + concurrently. Each + + process + thread + + repeatedly changes its value to the average of its own value and its + neighbors values. This algorithm converges to the right answer + independent of the order in which the operations are done; there is no + need for any restrictions on concurrent use of the shared values. + + + + + Suppose that + Peter, Paul and Mary + Peter, Paul, and Mary share a joint bank account that + initially contains 100. Concurrently, Peter deposits + 10, Paul withdraws 20, and Mary withdraws half the + money in the account, by executing the following commands: + + + + + + + + + + + + + + + +
Peter:(set! balance (+ balance 10))
Paul:(set! balance (- balance 20))
Mary:(set! balance (- balance (/ balance 2))) +
+
+ + + + + + + + + + + + + + +
Peter:balance = balance + 10
Paul:balance = balance - 20
Mary:balance = balance - (balance / 2)
+
+
+
    +
  1. + List all the different possible values for + balance + balance + + after these three transactions have been completed, + assuming that the banking system forces the three + processes + threads + to run sequentially in some order. +
  2. +
  3. + What are some other values + that could be produced if the system allows the + + processes + threads + + to be interleaved? Draw timing diagrams like the one in + figure to explain + how these values can occur. +
  4. +
+
+ + timeconcurrentin concurrent systems + concurrencycorrectness of concurrent programs + +
diff --git a/xml/cn/chapter3/section4/subsection2.xml b/xml/cn/chapter3/section4/subsection2.xml new file mode 100644 index 000000000..140e4d921 --- /dev/null +++ b/xml/cn/chapter3/section4/subsection2.xml @@ -0,0 +1,1999 @@ + + + Mechanisms for Controlling Concurrency + + + concurrencymechanisms for controlling + + + Weve seen that the difficulty in dealing with concurrent + processes + threads + is rooted in the need to consider the interleaving of the order of events + in the different + processes + threads. + For example, suppose we have two + processes + threads, + one with three ordered events $(a,b,c)$ + and one with three ordered events $(x,y,z)$. + If the two + + processes + threads + + run concurrently, with no constraints on how their execution is + interleaved, then there are 20 different possible orderings for the events + that are consistent with the individual orderings for the two + processes + threads: + + \[ \begin{array}{cccc} + (a,b,c,x,y,z) & (a,x,b,y,c,z) & (x,a,b,c,y,z) & (x,a,y,z,b,c)\\ + (a,b,x,c,y,z) & (a,x,b,y,z,c) & (x,a,b,y,c,z) & (x,y,a,b,c,z)\\ + (a,b,x,y,c,z) & (a,x,y,b,c,z) & (x,a,b,y,z,c) & (x,y,a,b,z,c)\\ + (a,b,x,y,z,c) & (a,x,y,b,z,c) & (x,a,y,b,c,z) & (x,y,a,z,b,c)\\ + (a,x,b,c,y,z) & (a,x,y,z,b,c) & (x,a,y,b,z,c) & (x,y,z,a,b,c) + \end{array} \] + + As programmers designing this system, we would have to consider the + effects of each of these 20 orderings and check that each behavior is + acceptable. Such an approach rapidly becomes unwieldy as the numbers of + processes + threads and events increase. + + + + A more practical approach to the design of concurrent systems is to + devise general mechanisms that allow us to constrain the interleaving + of concurrent + processes + threads + so that we can be sure that the program + behavior is correct. Many mechanisms have been developed for this + purpose. In this section, we describe one of them, the + serializer. + + + + Serializing access to shared state + + + serializer + + + Serialization implements the following idea: + Processes + Threads + will execute concurrently, but there will be certain collections of + procedures + functions + that cannot be executed concurrently. More precisely, serialization + creates distinguished sets of + procedures + functions + such that only one execution of a + procedure + function + in each serialized set is permitted to happen at a time. If some + procedure + function + in the set is being executed, then a + process + thread + that attempts to execute any + procedure + function + in the set will be forced to wait + until the first execution has finished. + + + + We can use serialization to control access to shared variables. + For example, if we want to update a shared variable based on the + previous value of that variable, we put the access to the previous + value of the variable and the assignment of the new value to the + variable in the same + procedure + function. + We then ensure that no other + procedure + function + that assigns to the variable can run concurrently with this + procedure + function + by serializing all of these + procedures + functions + with the same serializer. This guarantees that the value of the + variable cannot be changed between an access and the corresponding + assignment. + + + + + + + Serializers in Scheme + + + + + Serializers + + + + + + To make the above mechanism more concrete, suppose that we have + extended + Scheme + JavaScript + to include a + procedure + function + called + + parallel-execute: + + concurrent_execute + concurrent_execute: + + + + +(parallel-execute p$_{1}$ p$_{2}$ $\ldots$ p$_{k}$) + + +concurrent_execute($f_{1}$, $f_{2}$, $\ldots$, $f_{k}$) + + + + + Each p must be a procedure of no arguments. + Parallel-execute + creates a separate process for each + p, which applies + p (to no arguments). + + + Each $f$ must be a function of no arguments. + The function concurrent_execute + creates a separate thread for each + $f$, which applies + $f$ (to no arguments). + + + These + processes + threads + all run concurrently. + + + Parallel-execute is not part of standard + Scheme, but it can be implemented in MIT Scheme. In our implementation, + the new concurrent processes also run concurrently with the original + Scheme process. Also, in our implementation, the value returned by + parallel-execute is a special control + object that can be used to halt the newly created processes. + + + The function concurrent_execute is + not part of the JavaScript standard, but the examples in this section + can be implemented in ECMAScript 2020. + + + + + + + + As an example of how this is used, consider + + concurrent_execute_example + 'all threads terminated' + +(define x 10) + +(parallel-execute (lambda () (set! x (* x x))) + (lambda () (set! x (+ x 1)))) + + +let x = 10; + +concurrent_execute(() => { x = x * x; }, + () => { x = x + 1; }); + + + + + This creates two concurrent + processes$P_1$, which + sets x to + x times x, + and $P_2$, which + incrementsx. After execution is + complete, x will be left with one of five + possible values, depending on the interleaving of the events of + $P_1$ and $P_2$: + + + + + + + + + + + + + + + + + + + + + + + + + +
101:$P_1$ + sets x to 100 and then + $P_2$ increments + x to 101.
121:$P_2$ increments + x to 11 and then + $P_1$ sets x + to x times + x.
110:$P_2$ changes + x from 10 to 11 between the two + times that $P_1$ +
+ + accesses the value of + x during the evaluation of + (* x x). +
11:$P_2$ accesses + x, then + $P_1$ sets x + to 100, + then $P_2$ sets + x.
100:$P_1$ accesses + x (twice), + then $P_2$ sets + x to 11, + then $P_1$ sets + x.
+
+ + This creates two concurrent + threads$T_1$, which sets + x to x times + x, and $T_2$, + which increments x. After execution is + complete, x 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 x to 100 and then + $T_2$ increments + x to 101.
121:$T_2$ increments + x to 11 and then + $T_1$ sets x + to x times + x.
110:$T_2$ changes + x from 10 to 11 between the two + times that $T_1$
+ accesses the value of + x during the evaluation of + x * x. +
11:$T_2$ accesses + x, then + $T_1$ sets x + to 100, + then $T_2$ sets + x.
100:$T_1$ accesses + x (twice), + then $T_2$ sets + x to 11, + then $T_1$ sets + x.
+
+
+
+ + We can constrain the concurrency by using serialized + procedures, + functions, + which are created by serializers. Serializers are constructed by + + make-serializer, + + make_serializer, + + + whose implementation is given below. A serializer takes a + procedure + function + as argument and returns a serialized + procedure + function + that behaves like the original + procedure. + function. + All calls to a given serializer return serialized + procedures + functions + in the same set. + + + + Thus, in contrast to the example above, executing + + serializer_example + serializer + 'all threads terminated' + +(define x 10) + +(define s (make-serializer)) + +(parallel-execute (s (lambda () (set! x (* x x)))) + (s (lambda () (set! x (+ x 1))))) + + +let x = 10; + +const s = make_serializer(); + +concurrent_execute(s(() => { x = x * x; }), + s(() => { x = x + 1; })); + + + can produce only two possible values for + x, 101 or 121. + The other possibilities are eliminated, because the execution of + + + $P_1$ and + $P_2$ cannot be interleaved. + + + $T_1$ and + $T_2$ cannot be interleaved. + + + + + Here is a version of the + make-account + make_account + + + procedure + function + from section, + where the deposits and withdrawals have been + bank accountserialized + serialized: + + make_account_serialize_attempt_example + +const my_account = make_account(100); + +concurrent_execute( + () => { + display(my_account("balance"), "T1 balance"); + my_account("withdraw")(50); + display(my_account("balance"), "T1 balance"); + my_account("deposit")(100); + }, + () => { display(my_account("balance"), "T2 balance"); + my_account("withdraw")(50); + display(my_account("balance"), "T2 balance"); + my_account("deposit")(100); + } +); + + + + make_accountwith serialization + make_account_serialize_attempt + serializer + make_account_serialize_attempt_example + +(define (make-account balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (let ((protected (make-serializer))) + (define (dispatch m) + (cond ((eq? m 'withdraw) (protected withdraw)) + ((eq? m 'deposit) (protected deposit)) + ((eq? m 'balance) balance) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch)) + + +function make_account(balance) { + function withdraw(amount) { + if (balance > amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + const protect = make_serializer(); + function dispatch(m) { + return m === "withdraw" + ? protect(withdraw) + : m === "deposit" + ? protect(deposit) + : m === "balance" + ? balance + : error(m, "unknown request -- make_account"); + } + return dispatch; +} + + + With this implementation, two + processes + threads + cannot be withdrawing from or + depositing into a single account concurrently. This eliminates the source + of the error illustrated in figure, + where Peter changes the account balance between the times when Paul accesses + the balance to compute the new value and when Paul actually performs the + assignment. On the other hand, each account has its own serializer, + so that deposits and withdrawals for different accounts can proceed + concurrently. + + + + Which of the five possibilities in the + parallel + concurrent + execution shown above remain if we instead serialize execution as follows: + + serializer_example_2 + serializer + 'all threads terminated' + +(define x 10) + +(define s (make-serializer)) + +(parallel-execute (lambda () (set! x ((s (lambda () (* x x)))))) + (s (lambda () (set! x (+ x 1))))) + + +let x = 10; + +const s = make_serializer(); + +concurrent_execute( () => { x = s(() => x * x)(); }, + s(() => { x = x + 1; })); + + + + + + + Give all possible values of x + that can result from executing + + serializer_example_3 + 'all threads terminated' + +(define x 10) + +(parallel-execute (lambda () (set! x (* x x))) + (lambda () (set! x (* x x x)))) + + +let x = 10; + +concurrent_execute(() => { x = x * x; }, + () => { x = x * x * x; }); + + + \newpage\noindent Which of these possibilities remain if we instead use serialized + procedures: + functions: + + serializer_example_4 + serializer + 'all threads terminated' + +(define x 10) + +(define s (make-serializer)) + +(parallel-execute (s (lambda () (set! x (* x x)))) + (s (lambda () (set! x (* x x x))))) + + +let x = 10; + +const s = make_serializer(); + +concurrent_execute(s(() => { x = x * x; }), + s(() => { x = x * x * x; })); + + + + + + + + Ben Bitdiddle worries that it would be better to implement the bank + account as follows (where the commented line has been changed): + + make_accountwith serialization + serializer_example_5 + serializer + make_account_serialize_attempt_example + +(define (make-account balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (let ((protected (make-serializer))) + (define (dispatch m) + (cond ((eq? m 'withdraw) (protected withdraw)) + ((eq? m 'deposit) (protected deposit)) + ((eq? m 'balance) + ((protected (lambda () balance)))) ; serialized + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch)) + + +function make_account(balance) { + function withdraw(amount) { + if (balance > amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + const protect = make_serializer(); + function dispatch(m) { + return m === "withdraw" + ? protect(withdraw) + : m === "deposit" + ? protect(deposit) + : m === "balance" + ? protect(() => balance)(undefined) // serialized + : error(m, "unknown request -- make_account"); + } + return dispatch; +} + + + because allowing unserialized access to the bank balance can + result in anomalous behavior. Do you agree? Is there any + scenario that demonstrates Bens concern? + + + + Ben Bitdiddle suggests that its a waste of time to + create a new serialized + procedure + function + in response to every withdraw and + deposit message. He says that + + make-account + make_account + + could be changed so that the calls to + + + protected + + + protect + + + are done outside the dispatch + + procedure. + function. + + That is, an account would return the same serialized + procedure + function + (which was created at the same time as the account) each time + it is asked for a withdrawal + + procedure. + function. + + + make_accountwith serialization + serializer_example_6 + serializer + make_account_serialize_attempt_example + +(define (make-account balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (let ((protected (make-serializer))) + (let ((protected-withdraw (protected withdraw)) + (protected-deposit (protected deposit))) + (define (dispatch m) + (cond ((eq? m 'withdraw) protected-withdraw) + ((eq? m 'deposit) protected-deposit) + ((eq? m 'balance) balance) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch))) + + +function make_account(balance) { + function withdraw(amount) { + if (balance > amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + const protect = make_serializer(); + const protect_withdraw = protect(withdraw); + const protect_deposit = protect(deposit); + function dispatch(m) { + return m === "withdraw" + ? protect_withdraw + : m === "deposit" + ? protect_deposit + : m === "balance" + ? balance + : error(m, "unknown request -- make_account"); + } + return dispatch; +} + + + Is this a safe change to make? In particular, is there any difference + in what concurrency is allowed by these two versions of + make-account + make_account + ? + + + + serializer + + + Complexity of using multiple shared resources + + + serializerwith multiple shared resources + shared resources + + + Serializers provide a powerful abstraction that helps isolate the + complexities of concurrent programs so that they can be dealt with + carefully and (hopefully) correctly. However, while using serializers + is relatively straightforward when there is only a single shared + resource (such as a single bank account), concurrent programming can + be treacherously difficult when there are multiple shared resources. + + + + To illustrate one of the difficulties that can arise, suppose we wish to + bank accountexchanging balances + swap the balances in two bank accounts. We access each account to find + the balance, compute the difference between the balances, withdraw this + difference from one account, and deposit it in the other account. + We could implement this as + follows:We have simplified exchange + by exploiting the fact that our deposit + message accepts negative amounts. (This is a serious bug in our banking + system!) + + exchange_example + +const a1 = make_account(100); +const a2 = make_account(200); +const a3 = make_account(300); + +concurrent_execute( + () => { + display(a1("balance"), "Peter balance a1 before"); + display(a2("balance"), "Peter balance a2 before"); + exchange(a1, a2); + display(a1("balance"), "Peter balance a1 after"); + display(a2("balance"), "Peter balance a2 after"); + }, + () => { + display(a1("balance"), "Paul balance a1 before"); + display(a3("balance"), "Paul balance a3 before"); + exchange(a1, a3); + display(a1("balance"), "Paul balance a1 after"); + display(a3("balance"), "Paul balance a3 after"); + } +); + + + + exchange + exchange + make_account_serialize_attempt + exchange_example + +(define (exchange account1 account2) + (let ((difference (- (account1 'balance) + (account2 'balance)))) + ((account1 'withdraw) difference) + ((account2 'deposit) difference))) + + +function exchange(account1, account2) { + const difference = account1("balance") - account2("balance"); + account1("withdraw")(difference); + account2("deposit")(difference); +} + + + + + + This + + procedure + function + + works well when only a single + process + thread + is trying to do the exchange. Suppose, however, that Peter and Paul both + have access to accounts $a_1$, + $a_2$, and $a_3$, and + that Peter exchanges $a_1$ and + $a_2$ while Paul concurrently exchanges + $a_1$ and $a_3$. + Even with account deposits and withdrawals + serialized for individual accounts (as in the + + make-account + make_account + + + procedure + function + + shown above in this section), exchange can + still produce incorrect results. For example, Peter might compute the + difference in the balances for $a_1$ and + $a_2$, but then Paul might change the balance in + $a_1$ before Peter is able to complete the + exchange.If the account balances start out as 10, + 20, and 30, then after any number of concurrent exchanges, + the balances should still be 10, 20, and 30 in + some order. Serializing the deposits to individual accounts is not + sufficient to guarantee this. See + exercise. + For correct behavior, we must arrange for the + exchange + + procedure + function + + to lock out any other concurrent accesses to the accounts during the + entire time of the exchange. + + + + One way we can accomplish this is by using both accounts serializers + to serialize the entire exchange + procedure + function. + To do this, we will arrange for access to an accounts serializer. + Note that we are deliberately breaking the modularity of the bank-account + object by exposing the serializer. The following version of + make-account + make_@account + + is identical to the original version given in + section, except that a + serializer is provided to protect the balance variable, and the serializer + is exported via message passing: + + make_account_and_serializer + make_account_and_serializer + serializer + 'all threads terminated' + +(define (make-account-and-serializer balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (let ((balance-serializer (make-serializer))) + (define (dispatch m) + (cond ((eq? m 'withdraw) withdraw) + ((eq? m 'deposit) deposit) + ((eq? m 'balance) balance) + ((eq? m 'serializer) balance-serializer) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch)) + + +function make_account_and_serializer(balance) { + function withdraw(amount) { + if (balance > amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + const balance_serializer = make_serializer(); + return m => m === "withdraw" + ? withdraw + : m === "deposit" + ? deposit + : m === "balance" + ? balance + : m === "serializer" + ? balance_serializer + : error(m, "unknown request -- make_account"); +} + + + + + + We can use this to do serialized deposits and withdrawals. However, + unlike our earlier serialized account, it is now the responsibility of + each user of bank-account objects to explicitly manage the + serialization, for example as + follows:Exercise + investigates why deposits and withdrawals are no longer automatically + serialized by the account. + + make_account_and_serializer_example + +const my_account = make_account_and_serializer(100); + +deposit(my_account, 200); + +display(my_account("balance")); + + + + deposit, with external serializer + make_account_and_serializer_usage + make_account_and_serializer + make_account_and_serializer_example + +(define (deposit account amount) + (let ((s (account 'serializer)) + (d (account 'deposit))) + ((s d) amount))) + + +function deposit(account, amount) { + const s = account("serializer"); + const d = account("deposit"); + s(d(amount)); +} + + + + + + Exporting the serializer in this way gives us enough flexibility to + implement a serialized exchange program. We simply serialize the original + exchange + procedure + function + with the serializers for both accounts: + + serialized_exchange_example + +const a1 = make_account_and_serializer(100); +const a2 = make_account_and_serializer(200); +const a3 = make_account_and_serializer(300); + +concurrent_execute( + () => { + display(a1("balance"), "Peter balance a1 before"); + display(a2("balance"), "Peter balance a2 before"); + serialized_exchange(a1, a2); + display(a1("balance"), "Peter balance a1 after"); + display(a2("balance"), "Peter balance a2 after"); + }, + () => { + display(a1("balance"), "Paul balance a1 before"); + display(a3("balance"), "Paul balance a3 before"); + serialized_exchange(a1, a3); + display(a1("balance"), "Paul balance a1 after"); + display(a3("balance"), "Paul balance a3 after"); + } +); + + + + serialized_exchange + serialized_exchange + make_account_and_serializer + exchange + serialized_exchange_example + +(define (serialized-exchange account1 account2) + (let ((serializer1 (account1 'serializer)) + (serializer2 (account2 'serializer))) + ((serializer1 (serializer2 exchange)) + account1 + account2))) + + +function serialized_exchange(account1, account2) { + const serializer1 = account1("serializer"); + const serializer2 = account2("serializer"); + serializer1(serializer2(exchange))(account1, account2); +} + + + + + + Suppose that the balances in three accounts start out as 10, + 20, and 30, and that multiple + + processes + threads + + run, exchanging the balances in the accounts. Argue that if the + + processes + threads + + are run sequentially, + after any number of concurrent exchanges, the account balances should be + 10, 20, and 30 in some order. + Draw a timing diagram like the one in + figure to + show how this condition can be violated if the exchanges are + implemented using the first version of the account-exchange program in + this section. On the other hand, argue that even with this + exchange program, the sum of the balances + in the accounts will be preserved. Draw a timing diagram to show how + even this condition would be violated if we did not serialize the + transactions on individual accounts. + + + + + Consider the problem of + bank accounttransferring money + transferring an amount from one account to + another. Ben Bitdiddle claims that this can be accomplished with the + following + procedure, + function, + even if there are multiple people concurrently + transferring money among multiple accounts, using any account + mechanism that serializes deposit and withdrawal transactions, for + example, the version of + make-account + + make_account + + + in the text above. + + transfer_example + make_account_serialize_attempt + +const a1 = make_account(400); +const a2 = make_account(100); + +concurrent_execute( + () => { + display(a1("balance"), "Peter balance a1 before"); + display(a2("balance"), "Peter balance a2 before"); + transfer(a1, a2, 50); + display(a1("balance"), "Peter balance a1 after"); + display(a2("balance"), "Peter balance a2 after"); + }, + () => { + display(a1("balance"), "Paul balance a1"); + display(a2("balance"), "Paul balance a2"); + display(a1("balance"), "Paul balance a1"); + display(a2("balance"), "Paul balance a2"); + display(a1("balance"), "Paul balance a1"); + display(a2("balance"), "Paul balance a2"); + } +); + + + + transfer + transfer_example + make_account_and_serializer + +(define (transfer from-account to-account amount) + ((from-account 'withdraw) amount) + ((to-account 'deposit) amount)) + + +function transfer(from_account, to_account, amount) { + from_account("withdraw")(amount); + to_account("deposit")(amount); +} + + + Louis Reasoner claims that there is a problem here, and that we need + to use a more sophisticated method, such as the one required for + dealing with the exchange problem. Is Louis right? If not, what is + the essential difference between the transfer problem and the exchange + problem? (You should assume that the balance in + + from-account + from_account + + is at least amount.) + + + + + Louis Reasoner thinks our bank-account system is unnecessarily complex + and error-prone now that deposits and withdrawals arent + automatically serialized. He suggests that + + + make-account-and-serializer + + + make_@account_@and_@serializer + + + should have exported the serializer + + + (for use by such procedures as + serialized-exchange) + + + (for use by such functions as + serialized_exchange) + + + in addition to (rather than instead of) using it to serialize accounts and + deposits as + + make-account + make_account + + did. He proposes to redefine accounts as follows: + + make_account_and_serializer_exercise + serializer + make_account_and_serializer_exercise_example + +(define (make-account-and-serializer balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (let ((balance-serializer (make-serializer))) + (define (dispatch m) + (cond ((eq? m 'withdraw) (balance-serializer withdraw)) + ((eq? m 'deposit) (balance-serializer deposit)) + ((eq? m 'balance) balance) + ((eq? m 'serializer) balance-serializer) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch)) + + +function make_account_and_serializer(balance) { + function withdraw(amount) { + if (balance > amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + const balance_serializer = make_serializer(); + return m => m === "withdraw" + ? balance_serializer(withdraw) + : m === "deposit" + ? balance_serializer(deposit) + : m === "balance" + ? balance + : m === "serializer" + ? balance_serializer + : error(m, "unknown request -- make_account"); +} + + + Then deposits are handled as with the original + + make-account: + make_account: + + + make_account_and_serializer_exercise_usage + make_account_and_serializer_exercise + make_account_and_serializer_exercise_example + +(define (deposit account amount) + ((account 'deposit) amount)) + + +function deposit(account, amount) { + account("deposit")(amount); +} + + + + make_account_and_serializer_exercise_example + make_account_and_serializer_exercise_usage + + + +const my_account = make_account_and_serializer(300); +deposit(my_account, 100); +display(my_account("balance")); + + + Explain what is wrong with Louis's reasoning. In particular, + consider what happens when + + serialized-exchange + serialized_exchange + + + is called. + + + + serializerwith multiple shared resources + shared resources + + + + Implementing serializers + + + serializerimplementing + + + We implement serializers in terms of a more primitive synchronization + mechanism called a + mutex + mutex. A mutex is an object that supports two + operationsthe mutex can be + acquire a mutex + acquired, and the mutex can be + release a mutex + released. Once a mutex has been acquired, no other acquire + operations on that mutex may proceed until the mutex is + released.The term mutex is an abbreviation for + mutual exclusion + mutual exclusion. The general problem of arranging a mechanism + that permits concurrent + processes + threads + to safely share resources is called the mutual exclusion problem. Our + mutex is a simple variant of the + semaphore + semaphore mechanism (see + exercise), which was introduced in the + THE Multiprogramming System + THE Multiprogramming System developed at the + Technological University of Eindhoven + Technological University of Eindhoven and named for the universitys + initials in Dutch + Dijkstra, Edsger Wybe + (Dijkstra 1968a). The acquire and + release operations were originally called + PP operation on semaphore + VV operation on semaphore + P and V, from the Dutch + words passeren (to pass) and vrijgeven (to release), in + reference to the semaphores used on railroad systems. Dijkstras + classic exposition (1968b) was one of the first to clearly present the + issues of concurrency control, and showed how to use semaphores to + handle a variety of concurrency problems. + In our implementation, each serializer has an associated mutex. Given a + + + procedure + p, + + + function + f, + + + the serializer returns a + procedure + function + that acquires the mutex, runs + + + p, + + + f, + + + and then releases the mutex. This ensures that only one of the + + procedures + functions + + produced by the serializer can be running at once, which is + precisely the serialization property that we need to guarantee. + + + To apply serializers to functions that take an arbitrary number of arguments, + we use JavaScript's rest parameter and spread syntax. + 0a3... (rest parameter and spread syntax) + vector (data structure)restused in spread and rest parameter syntax + rest parameter and spread syntax + spread and rest parameter syntax + with any number of arguments + argument(s)arbitrary number of + 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. + The + ... in front of + args in + the application + f(...args) + spreads the elements of + args so that + they become separate arguments of + f. + + + + make_serializer + serializer + mutex + 'all threads terminated' + +(define (make-serializer) + (let ((mutex (make-mutex))) + (lambda (p) + (define (serialized-p . args) + (mutex 'acquire) + (let ((val (apply p args))) + (mutex 'release) + val)) + serialized-p))) + + +function make_serializer() { + const mutex = make_mutex(); + return f => { + function serialized_f(...args) { + mutex("acquire"); + const val = f(...args); + mutex("release"); + return val; + } + return serialized_f; + }; +} + + +function make_serializer() { + const mutex = make_mutex(); + return f => { + function serialized_f() { + mutex("acquire"); + const val = f(); + mutex("release"); + return val; + } + return serialized_f; + }; +} + + + + + + The mutex is a mutable object (here well use a one-element list, + which well refer to as a + cell, in serializer implementation + cell) that can hold the value true or false. When the value is + false, the mutex is available to be acquired. When the value is true, the + mutex is unavailable, and any + + process + thread + + that attempts to acquire the mutex must wait. + + + + Our mutex constructor + + make-mutex + make_mutex + + begins by initializing the cell contents to false. To acquire the mutex, + we test the cell. If the mutex is available, we set the cell contents to + true and proceed. Otherwise, we wait in a loop, attempting to acquire over + and over again, until we find that the mutex is available.In most + time-shared operating systems, + + processes + threads + + that are + blocked process + blocked by a mutex do + not waste time + busy-waiting + busy-waiting as above. Instead, the system + schedules another + + process + thread + + to run while the first is waiting, and the blocked + process + thread + is awakened when the mutex becomes available. + To release the mutex, we set the cell contents to false. + + make_mutex + mutex + 'all threads terminated' + +(define (make-mutex) + (let ((cell (list false))) + (define (the-mutex m) + (cond ((eq? m 'acquire) + (if (test-and-set! cell) + (the-mutex 'acquire))) ; retry + ((eq? m 'release) (clear! cell)))) + the-mutex)) + +(define (clear! cell) + (set-car! cell false)) + + +function make_mutex() { + const cell = list(false); + function the_mutex(m) { + return m === "acquire" + ? test_and_set(cell) + ? the_mutex("acquire") // retry + : true + : m === "release" + ? clear(cell) + : error(m, "unknown request -- mutex"); + } + return the_mutex; +} +function clear(cell) { + set_head(cell, false); +} + + + + + + + Test-and-set! + The function test_and_set + + + tests the cell and returns the result of the test. In addition, if the + test was false, + + test-and-set! + test_and_set + + sets the cell contents to true before returning false. We can express this + behavior as the following + + procedure: + function: + + + test_and_set + test_and_set + 'all threads terminated' + +(define (test-and-set! cell) + (if (car cell) + true + (begin (set-car! cell true) + false))) + + +function test_and_set(cell) { + if (head(cell)) { + return true; + } else { + set_head(cell, true); + return false; + } +} + + + + + + However, this implementation of + + test-and-set! + test_and_set + + does not suffice as it stands. There is a crucial subtlety here, which is + the essential place where concurrency control enters the system: The + + test-and-set! + test_and_set + + + operation must be performed + atomic requirement for test-and-set!test_and_set + atomically. That is, we must guarantee that, once a + + process + thread + + has tested the cell and found it to be false, the cell contents will + actually be set to true before any other + + process + thread + + can test the cell. If we do not make this guarantee, then the mutex can + fail in a way similar to the bank-account failure in + figure. (See + exercise.) + + + + The actual implementation of + + test-and-set! + test_and_set + + depends on the details of how our system runs concurrent + + processes. + threads. + + For example, we might be executing concurrent + + processes + threads + + on a sequential processor using a + time slicing + time-slicing mechanism that cycles through the + + processes, + threads, + + permitting each + + process + thread + + to run for a short time before interrupting it + and moving on to the next + process. + thread. + + In that case, + + test-and-set! + + test_and_set + + + can work by disabling time slicing during the testing and + setting.In MIT Scheme + for a single processor, which uses a time-slicing + model, test-and-set! + can be implemented as follows: + without_interrupts + MIT Schemewithoutwithout-interrupts + + test-and-set! + +(define (test-and-set! cell) + (without-interrupts + (lambda () + (if (car cell) + true + (begin (set-car! cell true) + false))))) + + + Without-interrupts disables time-slicing interrupts while its procedure + argument is being executed. + Alternatively, multiprocessing computers provide instructions that + support atomic operations directly in hardware.There are many + variants of such + atomic operations supported in hardware + instructionsincluding test-and-set, test-and-clear, swap, + compare-and-exchange, load-reserve, and store-conditionalwhose + design must be carefully matched to the machines + processormemory interface. One issue that arises here is to + determine what happens if two + processes + threads + attempt to acquire the same resource at exactly the same time by using such + an instruction. This requires some mechanism for making a decision about + which + process + thread + gets control. Such a mechanism is called an + arbiter + arbiter. Arbiters usually boil down to some sort of hardware + device. Unfortunately, it is possible to prove that one cannot physically + construct a fair arbiter that works 100% of the time unless one + allows the arbiter an arbitrarily long time to make its decision. + The fundamental phenomenon here was originally observed by the + fourteenth-century French philosopher + Buridan, Jean + Jean Buridan in his commentary on + Aristotles De caelo (Buridans commentary on) + Aristotles De caelo. Buridan argued that a perfectly rational + dog, perfectly rational behavior of + dog placed between two equally attractive sources of food will starve to + death, because it is incapable of deciding which to go to first. + + + + Suppose that we implement + + test-and-set! + test_and_set + + using an ordinary + + procedure + function + + as shown in the text, without attempting to make the operation atomic. + Draw a timing diagram like the one in + figure to demonstrate how the mutex + implementation can fail by allowing two + + processes + threads + + to acquire the mutex at the same time. + + + + + + A semaphore + semaphoreof size $n$ + (of size $n$) is a generalization of + a mutex. Like a mutex, a semaphore supports acquire and release operations, + but it is more general in that up to $n$ + processes + threads + can acquire it + concurrently. Additional + processes + threads + that attempt to acquire the semaphore must wait for release operations. + Give implementations of semaphores +
    +
  1. in terms of mutexes +
  2. +
  3. + in terms of atomic + + test-and-set! + test_and_set + + + operations. +
  4. +
+
+ + serializerimplementing + + + Deadlock + + + concurrencydeadlock + deadlock + + + + Now that we have seen how to implement serializers, we can see + that account exchanging still has a problem, even with the + + serialized-exchange + serialized_exchange + + + procedure + function + above. + Imagine that Peter attempts to exchange $a_1$ + with $a_2$ while Paul concurrently attempts to + exchange $a_2$ with + $a_1$. Suppose that Peters + + process + thread + + reaches the point where it has entered a serialized + + procedure + function + + protecting $a_1$ and, just after that, + Pauls + + process + thread + + enters a serialized + + procedure + function + + protecting $a_2$. Now Peter cannot proceed (to + enter a serialized + + procedure + function + + protecting $a_2$) until Paul exits the serialized + + procedure + function + + protecting $a_2$. Similarly, Paul cannot proceed + until Peter exits the serialized + + procedure + function + + protecting $a_1$. Each + + process + thread + + is stalled forever, waiting for the other. This situation is called a + deadlock. Deadlock is always a danger in systems that provide + concurrent access to multiple shared resources. + + + + One way to avoid the + deadlockavoidance + deadlock in this situation is to give each account a + unique identification number and rewrite + + serialized-exchange + serialized_exchange + + + so that a + process + thread + + will always attempt to enter a + + procedure + function + + protecting the lowest-numbered account first. Although this method works + well for the exchange problem, there are other situations that require more + sophisticated deadlock-avoidance techniques, or where deadlock cannot + be avoided at all. (See exercises + and.)The general + technique for avoiding + deadlockrecovery + deadlock by numbering the + shared resources and acquiring them in order is due to + Havender, J. + Havender (1968). Situations where deadlock cannot be + avoided require deadlock-recovery methods, which entail having + + processes + threads + + back out of the deadlocked state and try again. + Deadlock-recovery mechanisms are widely used in + data-base-management systems, a topic that is treated in detail in + Gray, Jim + Reuter, Andreas + Gray and Reuter 1993. + + + + Explain in detail why the + serialized_exchangewith deadlock avoidance + deadlock-avoidance method described above, + (i.e., the accounts are numbered, and each + process + thread + attempts to acquire the smaller-numbered account first) avoids + deadlock in the exchange problem. Rewrite + + serialized-exchange + serialized_exchange + + + to incorporate this idea. (You will also need to modify + + make-account + make_account + + + so that each account is created with a number, which can be accessed by + sending an appropriate message.) + + + + + Give a scenario where the deadlock-avoidance mechanism described + above does not work. (Hint: In the exchange problem, each + process + thread + knows in advance which accounts it will need to get access to. Consider a + situation where a + + process + thread + + must get access to some shared resources before it can know which additional + shared resources it will require.) + + + + concurrencydeadlock + deadlock + + + Concurrency, time, and communication + + + + Weve seen how programming concurrent systems requires controlling + the ordering of events when different + + processes + threads + + access shared state, and weve seen how to achieve this control + through judicious use of serializers. But the problems of concurrency + lie deeper than this, because, from a fundamental point of view, its + not always clear what is meant by shared state. + + + + Mechanisms such as + + test-and-set! + test_and_set + + + require + + processes + threads + + to examine a global shared flag at arbitrary times. This is problematic + and inefficient to implement in modern high-speed processors, where + due to optimization techniques such as pipelining and cached memory, + the contents of memory may not be in a consistent state at every instant. + In + + contemporary + some + + multiprocessing systems, therefore, the serializer paradigm + is being supplanted by + + new + other + + approaches to concurrency + control.One such alternative to serialization is called + barrier synchronization + barrier synchronization. The programmer permits concurrent + processes + threads + to execute as they please, but establishes certain synchronization points + (barriers) through which no + + process + thread + + can proceed until all the + + processes + threads + + have reached the barrier. + + Modern + Some + + processors provide machine instructions + that permit programmers to establish synchronization points at places where + consistency is required. The + PowerPC + PowerPC$^{\textrm{TM}}$, for example, includes + for this purpose two instructions called + SYNC + SYNC and + EIEIO + EIEIO (Enforced In-order Execution of Input/Output). + + + + + The problematic aspects of shared state also arise in large, distributed + systems. For instance, imagine a distributed banking system where + individual branch banks maintain local values for bank balances and + periodically compare these with values maintained by other branches. In + such a system the value of the account balance would be + undetermined, except right after synchronization. If Peter deposits money + in an account he holds jointly with Paul, when should we say that the + account balance has changedwhen the balance in the local branch + changes, or not until after the synchronization? And if Paul accesses the + account from a different branch, what are the reasonable constraints to + place on the banking system such that the behavior is + correct? The only thing that might matter for correctness + is the behavior observed by Peter and Paul individually and the + state of the account immediately after synchronization. + Questions about the real account balance or the order of + events between synchronizations may be irrelevant or + meaningless.This may seem like a strange point of view, but there + are + systems that work this way. + credit-card accounts, international + International charges to credit-card accounts, + for example, are normally cleared on a per-country basis, and the charges + made in different countries are periodically reconciled. Thus the account + balance may be different in different countries. + + + + The basic phenomenon here is that synchronizing different + + processes, + threads, + + establishing shared state, or imposing an order on events requires + communication among the + + processes. + threads. + + timecommunication and + In essence, any notion of time in concurrency control must be intimately + tied to communication.For distributed systems, this perspective + was pursued by + Lamport, Leslie + Lamport (1978), who showed how to use communication to establish + global clocks that can be used to establish orderings on + events in distributed systems. It is intriguing that a similar + connection between time and communication also arises in the + relativity, theory of + Theory of Relativity, where the speed of light (the fastest signal that can + be used to synchronize events) is a fundamental constant relating time and + space. The complexities we encounter in dealing with time and state in our + computational models may in fact mirror a fundamental complexity of + the physical universe. + + concurrency + concurrencymechanisms for controlling + + +
diff --git a/xml/cn/chapter3/section5/section5.xml b/xml/cn/chapter3/section5/section5.xml new file mode 100644 index 000000000..d93f395b1 --- /dev/null +++ b/xml/cn/chapter3/section5/section5.xml @@ -0,0 +1,96 @@ +
+ Streams + + + + + + stream(s) + + + Weve gained a good understanding of assignment as a tool in modeling, + as well as an appreciation of the complex problems that assignment + raises. It is time to ask whether we could have gone about things in a + different way, so as to avoid some of these problems. In this + section, we explore an alternative approach to modeling state, based + on data structures called streams. As we shall see, streams can + mitigate some of the complexity of modeling state. + + + + Lets step back and review where this complexity comes from. In an + attempt to model real-world phenomena, we made some apparently + reasonable decisions: We modeled real-world objects with local state + by computational objects with local variables. We identified time + variation in the real world with time variation in the computer. We + implemented the time variation of the states of the model objects in + the computer with assignments to the local variables of the model + objects. + + + + + Is there another approach? Can we avoid identifying time in the + computer with time in the modeled world? Must we make the model + change with time in order to model phenomena in a changing world? + Think about the issue in terms of mathematical functions. We can + describe the time-varying behavior of a quantity + $x$ as a function of time + $x(t)$. + If we concentrate on $x$ instant by instant, + we think of it as a changing quantity. Yet if we concentrate on the entire + time history of values, we do not emphasize changethe function + itself does not change.Physicists sometimes adopt this view by + introducing the + world line of a particle + world lines of particles as a device for reasoning about + motion. Weve also already mentioned + (section) that + this is the natural way to think about signal-processing systems. We will + explore applications of streams to signal processing in + section. + + + + If time is measured in discrete steps, then we can model a time function as + a (possibly infinite) sequence. In this section, we will see how to + model change in terms of sequences that represent the time histories + of the systems being modeled. To accomplish this, we introduce new + data structures called streams. From an abstract point of view, + a stream is simply a sequence. However, we will find that the + straightforward implementation of streams as lists (as in + section) doesnt fully reveal + the power of stream processing. As an alternative, we introduce the + technique of + delayed evaluation + delayed evaluation, which enables us to represent + very large (even infinite) sequences as streams. + + + + Stream processing lets us model systems that have state without ever + using assignment or mutable data. This has important implications, + both theoretical and practical, because we can build models that avoid + the drawbacks inherent in introducing assignment. Onthe other hand, + the stream framework raises difficulties of its own, and the question + of which modeling technique leads to more modular and more easily + maintained systems remains open. + + + + &subsection3.5.1; + + + &subsection3.5.2; + + + &subsection3.5.3; + + + &subsection3.5.4; + + + &subsection3.5.5; + +
diff --git a/xml/cn/chapter3/section5/subsection1.xml b/xml/cn/chapter3/section5/subsection1.xml new file mode 100644 index 000000000..de0e52495 --- /dev/null +++ b/xml/cn/chapter3/section5/subsection1.xml @@ -0,0 +1,1530 @@ + + + Streams Are Delayed Lists + + + + + stream(s)implemented as delayed lists + + + As we saw in + section, + sequences can serve as standard interfaces for combining program + modules. We formulated powerful abstractions for manipulating + sequences, such as map, + filter, and + accumulate, that capture a wide variety of + operations in a manner that is both succinct and elegant. + + + + Unfortunately, if we represent sequences as lists, this elegance is + bought at the price of severe inefficiency with respect to both the + time and space required by our computations. + When we represent manipulations on sequences as transformations + of lists, our programs must construct and copy data structures (which + may be huge) at every step of a process. + + + + To see why this is true, let us compare two programs for computing the + sum of all the prime numbers in an interval. The first program is + written in standard iterative style:Assume that we have a + predicate + + prime? + is_prime + + (e.g., as in section) that + tests for primality. + + sum_primes + prime_definition + sum_primes1 + sum_primes1_example + 3437 + +(define (sum-primes a b) + (define (iter count accum) + (cond ((> count b) accum) + ((prime? count) (iter (+ count 1) (+ count accum))) + (else (iter (+ count 1) accum)))) + (iter a 0)) + + +function sum_primes(a, b) { + function iter(count, accum) { + return count > b + ? accum + : is_prime(count) + ? iter(count + 1, count + accum) + : iter(count + 1, accum); + } + return iter(a, 0); +} + + + + sum_primes1_example + +sum_primes(7, 182); + + + The second program performs the same computation using the sequence + operations of + section: + + sum_primes + sum_primes2 + prime_definition + enumerate_interval + sum_primes1_example + 3437 + +(define (sum-primes a b) + (accumulate + + 0 + (filter prime? (enumerate-interval a b)))) + + +function sum_primes(a, b) { + return accumulate((x, y) => x + y, + 0, + filter(is_prime, + enumerate_interval(a, b))); +} + + + + + + In carrying out the computation, the first program needs to store only + the sum being accumulated. In contrast, the filter in the second + program cannot do any testing until + + enumerate-interval + enumerate_interval + + + has constructed a complete list of the numbers in the interval. + The filter generates another list, which in turn is passed to + accumulate before being collapsed to form + a sum. Such large intermediate storage is not needed by the first program, + which we can think of as enumerating the interval incrementally, adding + each prime to the sum as it is generated. + + + + The 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 1,000,000 by evaluating the expression + + painfully + prime_definition + enumerate_interval + +(car (cdr (filter prime? + (enumerate-interval 10000 1000000)))) + + +head(tail(filter(is_prime, + enumerate_interval(10000, 1000000)))); + + + This expression does find the second prime, but the computational overhead + is outrageous. We construct a list of almost a million integers, filter + this list by testing each element for primality, and then ignore almost + all of the result. In a more traditional programming style, we would + interleave the enumeration and the filtering, and stop when we reached + the second prime. + + + + Streams are a clever idea that allows one to use sequence + manipulations without incurring the costs of manipulating sequences as + lists. With streams we can achieve the best of both worlds: We can + formulate programs elegantly as sequence manipulations, while attaining + the efficiency of incremental computation. The basic idea is to arrange + to construct a stream only partially, and to pass the partial + construction to the program that consumes the stream. If the consumer + attempts to access a part of the stream that has not yet been + + constructed, the stream will automatically construct just enough more + of itself to produce the required part, thus preserving the illusion + that the entire stream exists. In other words, although we will write + programs as if we were processing complete sequences, we design our + stream implementation to automatically and transparently interleave + the construction of the stream with its use. + + + + The special form delay allowed the authors + of the original version to (1) hide the lambda being constructed, and (2) + elegantly sneak in the memoization optimization. There are no special forms + in JavaScript, so the lambda expression in the tail of a stream must be + explicit. This is not much + of a loss; streams are somewhat demystified with explicit lambda expressions. + We decided to not use memoization as default, in order to keep + stream tails in their bare lambda expression simplicity. We mention + memoization at the end of this section as an optional optimization, and + refer to this variant in a few exercises in the following sections. + + + + + + On the surface, streams are just lists with different names for the + procedures that manipulate them. There is a constructor, + cons-stream + cons-stream, and two selectors, + stream-car + stream-car and + stream-cdr + stream-cdr, which satisfy the constraints + \begin{eqnarray*} + \mbox{(stream-car (cons-stream x y))} &=& \mbox{x} \\ + \mbox{(stream-cdr (cons-stream x y))} &=& \mbox{y} + \end{eqnarray*} + There is a distinguishable object, + empty stream + stream(s)empty + the-empty-stream + the-empty-stream, which + cannot be the result of any cons-stream + operation, and which can be identified with the predicate + stream-null? + stream-null?.In the MIT + implementation, + the-empty-streamin MIT Scheme + stream-null?in MIT Scheme + MIT Schemeemptythe empty stream + the-empty-stream is the + same as the empty list '(), and + stream-null? is the same + as null?. + + + + Thus we can make and use streams, in just the same way as we can make + and use lists, to represent aggregate data arranged in a sequence. In + particular, we can build stream analogs of the list operations from + chapter, such as list-ref, + map, and + for-each:This should bother you. + The fact that we are defining such similar procedures + for streams and lists indicates that we are missing some + underlying abstraction. Unfortunately, in order to exploit this + abstraction, we will need to exert finer control over the process of + evaluation than we can at present. We will discuss this point further + at the end of + section. + In section, well + develop a framework that unifies lists and streams. + + stream-ref + stream-map + stream-for-each + +(define (stream-ref s n) + (if (= n 0) + (stream-car s) + (stream-ref (stream-cdr s) (- n 1)))) + +(define (stream-map proc s) + (if (stream-null? s) + the-empty-stream + (cons-stream (proc (stream-car s)) + (stream-map proc (stream-cdr s))))) + +(define (stream-for-each proc s) + (if (stream-null? s) + 'done + (begin (proc (stream-car s)) + (stream-for-each proc (stream-cdr s))))) + + + + + + Stream-for-each is useful for viewing streams: + + display-stream + display-line + +(define (display-stream s) + (stream-for-each display-line s)) + +(define (display-line x) + (newline) + (display x)) + + + + + + To make the stream implementation automatically and transparently + interleave the construction of a stream with its use, we will arrange + for the cdr of a stream to be evaluated + when it is accessed by the stream-cdr + procedure rather than when the stream is constructed by + cons-stream. This implementation choice + is reminiscent of our discussion of rational numbers in + section, where we saw + that we can choose to implement rational numbers so that the reduction + of numerator and denominator to lowest terms is performed either at + construction time or at selection time. The two rational-number + implementations produce the same data abstraction, but the choice has + an effect on efficiency. There is a similar relationship between + streams and ordinary lists. As a data abstraction, streams are the + same as lists. The difference is the time at which the elements are + evaluated. With ordinary lists, both the + car and the cdr + are evaluated at construction time. With streams, the + cdr is evaluated at selection time. + + + + Our implementation of streams will be based on a special form called + delay + special forms (those marked ns are not in the IEEE Scheme standard)delay + delay. Evaluating + (delay exp) + does not evaluate the expression exp, but + rather returns a so-called + delayed object + delayed object, which we can think of as a + promise to evaluate exp at + some future time. As a companion to delay, + there is a procedure called + force + force that takes a delayed object as + argument and performs the evaluationin effect, forcing the + delay to fulfill its promise. We will see + below how delay and + force can be implemented, but first let us + use these to construct streams. + + + + Cons-stream + special forms (those marked ns are not in the IEEE Scheme standard)cons-stream + cons-stream + is a special form defined so that + + +(cons-stream a b) + + + is equivalent to + + +(cons a (delay b)) + + + + + + What this means is that we will construct streams using pairs. However, + rather than placing the value of the rest of the stream + into the cdr of the + pair we will put there a promise to compute the rest if it is ever + requested. Stream-car and + stream-cdr can now be defined as procedures: + + stream-car + stream-cdr + +(define (stream-car stream) (car stream)) + +(define (stream-cdr stream) (force (cdr stream))) + + + + + + Stream-car selects the + car of the pair; + stream-cdr selects the + cdr of the pair and evaluates the delayed + expression found there to obtain the rest of the + stream.Although stream-car and + cons-streamwhy a special form + delaywhy a special form + stream-cdr can be defined as procedures, + cons-stream must be a special form. If + cons-stream were a procedure, then, + according to our model of evaluation, evaluating + (cons-stream a b) + would automatically cause b to be + evaluated, which is precisely what we do not want to happen. For the + same reason, delay must be a special form, + though force can be an ordinary + procedure. + stream(s)implemented as delayed lists + + + + The stream implementation in action + + + + To see how this implementation behaves, let us analyze the + outrageous prime computation we saw above, reformulated + in terms of streams: + + +(stream-car + (stream-cdr + (stream-filter prime? + (stream-enumerate-interval 10000 1000000)))) + + + We will see that it does indeed work efficiently. + + + + We begin by calling + stream-enumerate-interval with the + arguments 10,000 and 1,000,000. + Stream-enumerate-interval is the stream + analog of enumerate-interval + (section): + + stream-enumerate-interval + +(define (stream-enumerate-interval low high) + (if (> low high) + the-empty-stream + (cons-stream + low + (stream-enumerate-interval (+ low 1) high)))) + + + and thus the result returned by + stream-enumerate-interval, formed by the + cons-stream, isThe numbers shown + here do not really appear in the delayed expression. What actually + appears is the original expression, in an environment in which the + variables are bound to the appropriate numbers. For example, + (+ low 1) with + low bound to 10,000 actually appears where + 10001 is shown. + + +(cons 10000 + (delay (stream-enumerate-interval 10001 1000000))) + + + That is, stream-enumerate-interval returns + a stream represented as a pair whose car + is 10,000 and whose cdr is a promise to + enumerate more of the interval if so requested. This stream is now + filtered for primes, using the stream analog of the + filter procedure + (section): + + stream-filter + +(define (stream-filter pred stream) + (cond ((stream-null? stream) the-empty-stream) + ((pred (stream-car stream)) + (cons-stream (stream-car stream) + (stream-filter pred + (stream-cdr stream)))) + (else (stream-filter pred (stream-cdr stream))))) + + + Stream-filter tests the + stream-car of the stream (the + car of the pair, which is 10,000). + Since this is not prime, + stream-filter examines the + stream-cdr of its input + stream. The call to stream-cdr forces + evaluation of the delayed + stream-enumerate-interval, which now returns + + +(cons 10001 + (delay (stream-enumerate-interval 10002 1000000))) + + + Stream-filter now looks at the + stream-car of this stream, + 10,001, sees that this is not prime either, forces another + stream-cdr, and so on, until + stream-enumerate-interval yields + the prime 10,007, whereupon stream-filter, + according to its definition, returns + + +(cons-stream (stream-car stream) + (stream-filter pred (stream-cdr stream))) + + + which in this case is + + +(cons 10007 + (delay + (stream-filter + prime? + (cons 10008 + (delay + (stream-enumerate-interval 10009 + 1000000)))))) + + + This result is now passed to stream-cdr in + our original expression. This forces the delayed + stream-filter, which in turn keeps forcing + the delayed stream-enumerate-interval until + it finds the next prime, which is 10,009. Finally, the result passed to + stream-car in our original expression is + + +(cons 10009 + (delay + (stream-filter + prime? + (cons 10010 + (delay + (stream-enumerate-interval 10011 + 1000000)))))) + + + Stream-car returns 10,009, and the + computation is complete. Only as many integers were tested for primality + as were necessary to find the second prime, and the interval was + enumerated only as far as was necessary to feed the prime filter. + + + + In general, we can think of delayed evaluation as + programmingdemand-driven + demand-driven + programming, whereby each stage in the stream process is activated + only enough to satisfy the next stage. What we have done is to + order of eventsdecoupling apparent from actual + decouple the actual order of events in the computation from the apparent + structure of our procedures. We write procedures as if the streams + existed all at once when, in reality, the computation is + performed incrementally, as in traditional programming styles. + + + + Implementing delay and + force + + + + Although + delayimplementation using lambda + delay and + force may seem like mysterious operations, + their implementation is really quite straightforward. + Delay must package an expression so that it + can be evaluated later on demand, and we can accomplish this simply by + treating the expression as the body of a procedure. + Delay can be a special form such that + + +(delay exp) + + + is syntactic sugar for + + +(lambda () exp) + + + + + + Force simply calls the procedure + (of no arguments) produced by delay, + so we can implement force as + a procedure: + + force + +(define (force delayed-object) + (delayed-object)) + + + + + + This implementation suffices for delay and + force to work as advertised, but there is + an important optimization that we can include. In many applications, we + end up forcing the same delayed object many times. This can lead to + serious inefficiency in recursive programs involving streams. (See + exercise.) The solution + is to build delayed objects so that the first time they are forced, they + store the value that is computed. Subsequent forcings will simply + return the stored value without repeating the computation. In other + words, we implement delay as a + special-purpose + delaymemoized + memoizationdelayby delay + memoized procedure similar to the one described in + exercise. One way to accomplish this + is to use the following procedure, which takes as argument a procedure + (of no arguments) and returns a memoized version of the procedure. The + first time the memoized procedure is run, it saves the computed result. + On subsequent evaluations, it simply returns the result. + + memo-proc + +(define (memo-proc proc) + (let ((already-run? false) (result false)) + (lambda () + (if (not already-run?) + (begin (set! result (proc)) + (set! already-run? true) + result) + result)))) + + + + + + Delay is then defined so that + (delay exp) is equivalent to + + +(memo-proc (lambda () exp)) + + + and force is as defined + previously.There are many possible implementations of streams + other than the one described in this section. Delayed evaluation, which + is the key to making streams practical, was inherent in + Algolcall-by-name argument passing + call-by-name argument passing + Algol 60s call-by-name parameter-passing method. The + use of this mechanism to implement streams was first described by + Landin, Peter + Landin (1965). Delayed evaluation for streams was introduced into Lisp by + Friedman, Daniel P. + Wise, David S. + Friedman and Wise (1976). In their implementation, + cons + always delays evaluating its + arguments, so that lists automatically behave as streams. The + memoizing optimization is also known as + call-by-need argument passing + thunkcall-by-name + thunkcall-by-need + Algolthunks + call-by-need. The Algol community would refer to our original + delayed objects as call-by-name thunks and to the optimized + versions as call-by-need thunks. + + + + + Complete the following definition, which + generalizes stream-map to allow procedures + that take multiple arguments, analogous to + map in + section, + footnote. + + stream-mapwith multiple arguments + +(define (stream-map proc . argstreams) + (if (?? (car argstreams)) + the-empty-stream + (?? + (apply proc (map ?? argstreams)) + (apply stream-map + (cons proc (map ?? argstreams)))))) + + + + + + + In order to take a closer look at + delayed evaluationprinting and + delayed evaluation, we will use the + following procedure, which simply returns its argument after printing it: + + +(define (show x) + (display-line x) + x) + + + What does the interpreter print in response to evaluating each + expression in the following sequence?Exercises such + as + and + are valuable for testing our understanding of how + delay works. On the other hand, intermixing + delayed evaluation with printingand, even worse, with + assignmentis extremely confusing, and instructors of courses + on computer languages have traditionally tormented their students with + examination questions such as the ones in this section. Needless to say, + writing programs that depend on such subtleties is + programmingodious style + odious programming style. Part of the power of stream processing is + that it lets us ignore the order in which events actually happen in + our programs. Unfortunately, this is precisely what we cannot afford + to do in the presence of assignment, which forces us to be concerned + with time and change. + + +(define x (stream-map show (stream-enumerate-interval 0 10))) + +(stream-ref x 5) + +(stream-ref x 7) + + + + + + Consider the sequence of expressions + + +(define sum 0) + +(define (accum x) + (set! sum (+ x sum)) + sum) + +(define seq (stream-map accum (stream-enumerate-interval 1 20))) +(define y (stream-filter even? seq)) +(define z (stream-filter (lambda (x) (= (remainder x 5) 0)) + seq)) + +(stream-ref y 7) + +(display-stream z) + + + What is the value of sum after each of the + above expressions is evaluated? + delayed evaluationassignment and + What is the printed response to + evaluating the stream-ref and + display-stream expressions? Would these + responses differ if we had implemented + (delayexp) simply as + (lambda () exp) without using the + optimization provided by + memo-proc$\,$? + Explain. + + + + + + + To accomplish this, we will construct streams using pairs, + with the first item of the stream in the head of the pair. + However, rather than placing the value of the rest of the stream + promise to evaluate + into the tail of the pair, we will put there a promise + to compute the rest if it is ever requested. + If we have a data item + h and a stream + t, we construct a stream + whose head is + h and whose tail is + t by evaluating + pair(h, () => t)the + tail + t of a stream is + wrapped in a function of no arguments, + delayed expression + so that its evaluation will be delayed. + empty stream + stream(s)empty + The empty stream is + null, the same as the empty list. + + + To access the first data item of a nonempty stream, + we simply select the + head of the pair, as with a list. + But to access the tail of a stream, we need to evaluate the + delayed expression. + For convenience, we define + + stream_tail + stream_tail + stream_tail_example + 5 + +function stream_tail(stream) { + return tail(stream)(); +} + + + + stream_tail_example + +stream_tail(pair(4, () => pair(5, () => null))); + + +head(stream_tail(pair(4, () => pair(5, () => null)))); + + + This selects the tail of the pair and applies the function + found there to obtain the next pair of the stream + (or + null if the tail of the stream + is empty)in effect, + forcingtail of stream + forcing the function in the + tail of the pair to fulfill its promise. + stream(s)implemented as delayed lists + + + + We can make and use streams, in just the same way as we can make + and use lists, to represent aggregate data arranged in a sequence. In + particular, we can build stream analogs of the list operations from + chapter, such as list_ref, + map, and + for_each:This should + bother you. The fact that we are defining such similar functions + for streams and lists indicates that we are missing some underlying + abstraction. Unfortunately, in order to exploit this abstraction, we + will need to exert finer control over the process of evaluation than we + can at present. We will discuss this point further at the end of + section. + In section, well + develop a framework that unifies lists and streams. + + stream_ref + stream_map + stream_for_each + stream_tail + stream_functions + stream_functions_example + +function stream_ref(s, n) { + return n === 0 + ? head(s) + : stream_ref(stream_tail(s), n - 1); +} +function stream_map(f, s) { + return is_null(s) + ? null + : pair(f(head(s)), + () => stream_map(f, stream_tail(s))); +} +function stream_for_each(fun, s) { + if (is_null(s)) { + return true; + } else { + fun(head(s)); + return stream_for_each(fun, stream_tail(s)); + } +} + + + + stream_tail + stream_functions_example + +const my_stream = pair(4, () => pair(5, () => null)); +display(stream_ref(my_stream, 1)); +const my_stream_2 = stream_map(x => x + 1, my_stream); +stream_for_each(display, my_stream_2); + + +const my_stream = pair(4, () => pair(5, () => null)); +const my_stream_2 = stream_map(x => x + 1, my_stream); +let acc = 0; +stream_for_each(x => {acc = acc + x;}, my_stream_2); +acc; + + + The function + stream_for_each is useful for + viewing streams: + + display_stream + display_stream + display_stream_example + +function display_stream(s) { + return stream_for_each(display, s); +} + + +const max_display = 9; +function display_stream(s) { + function display_stream_iter(st, n) { + if (is_null(st)) { + } else if (n === 0) { + display('', "..."); + } else { + display(head(st)); + display_stream_iter(stream_tail(st), n - 1); + } + } + display_stream_iter(s, max_display); +} + + + + stream_tail + display_stream + display_stream_example + +const my_stream = pair(4, () => pair(5, () => null)); +display_stream(my_stream); + + + + + + To make the stream implementation automatically and transparently + interleave the construction of a stream with its use, we have arranged + for the tail + of a stream to be evaluated when it is accessed by the + stream_tail + function rather than when the stream is constructed by + pair. + This implementation choice is reminiscent of our discussion of rational numbers + in section, where we saw + that we can choose to implement rational numbers so that the reduction + of numerator and denominator to lowest terms is performed either at + construction time or at selection time. The two rational-number + implementations produce the same data abstraction, but the choice has + an effect on efficiency. There is a similar relationship between + streams and ordinary lists. As a data abstraction, streams are the + same as lists. The difference is the time at which the elements are + evaluated. With ordinary lists, both the + head and the + tail + are evaluated at construction time. With streams, the + tail is evaluated at selection time. + + + + Streams in action + + + + To see how this data structure behaves, let us analyze the + outrageous prime computation we saw above, reformulated + in terms of streams: + + + no_more_outrageous + prime_definition + stream_enumerate_interval + 10009 + +head(stream_tail(stream_filter( + is_prime, + stream_enumerate_interval(10000, 1000000)))); + + + We will see that it does indeed work efficiently. + + + + We begin by calling + stream_enumerate_interval with + the arguments 10,000 and 1,000,000. The function + stream_enumerate_interval + is the stream analog of + enumerate_interval + (section): + + stream_enumerate_interval_example + stream_enumerate_interval + +stream_enumerate_interval(10000, 1000000); + + +stream_ref(stream_enumerate_interval(10000, 1000000), 100); + + + + stream_enumerate_interval + prime_definition + stream_enumerate_interval + stream_enumerate_interval_example + 10100 + +function stream_enumerate_interval(low, high) { + return low > high + ? null + : pair(low, + () => stream_enumerate_interval(low + 1, high)); +} + + + and thus the result returned by + stream_enumerate_interval, + formed by the pair, + isThe numbers shown here do not really appear in the delayed + expression. What actually appears is the original expression, in an + environment in which the variables are bound to the appropriate numbers. + For example, low + 1 with + low bound to 10,000 actually appears + where 10001 is shown. + + stream_enumerate_interval_example_2 + stream_enumerate_interval + 10100 + +pair(10000, () => stream_enumerate_interval(10001, 1000000)); + + +stream_ref(pair(10000, () => stream_enumerate_interval(10001, 1000000)), 100); + + + That is, stream_enumerate_interval + returns a stream represented as a pair whose + head + is 10,000 and whose tail + is a promise to enumerate more of the + interval if so requested. This stream is now filtered for primes, + using the stream analog of the filter + function + (section): + + stream_filter + stream_tail + stream_filter + stream_filter_example + 6 + +function stream_filter(pred, stream) { + return is_null(stream) + ? null + : pred(head(stream)) + ? pair(head(stream), + () => stream_filter(pred, stream_tail(stream))) + : stream_filter(pred, stream_tail(stream)); +} + + + + stream_filter_example + display_stream + +const my_stream = pair(5, () => pair(6, () => pair(7, () => null))); +const my_filtered_stream = + stream_filter(x => x % 2 === 0, my_stream); +display_stream(my_filtered_stream); + + +const my_stream = pair(5, () => pair(6, () => pair(7, () => null))); +const my_filtered_stream = + stream_filter(x => x % 2 === 0, my_stream); +head(my_filtered_stream); + + + The function stream_filter tests the + head of the stream (which is 10,000). Since + this is not prime, stream_filter + examines the tail of its input stream. The call to + stream_tail forces evaluation of the + delayed stream_enumerate_interval, + which now returns + + enumerate_interval_example_3 + stream_enumerate_interval + 10101 + +pair(10001, () => stream_enumerate_interval(10002, 1000000)); + + +stream_ref(pair(10001, () => stream_enumerate_interval(10002, 1000000)), 100); + + + The 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 on, until + stream_@enumerate_interval yields + the prime 10,007, whereupon + stream_@filter, according to its + definition, returns + + +pair(head(stream), + () => stream_filter(pred, stream_tail(stream))); + + + which in this case is + + which_in_this_case + prime_definition + stream_enumerate_interval + 10949 + +pair(10007, + () => stream_filter( + is_prime, + pair(10008, + () => stream_enumerate_interval(10009, 1000000)))); + + +stream_ref(pair(10007, + () => stream_filter( + is_prime, + pair(10008, + () => stream_enumerate_interval(10009, 1000000)) + ) + ), 100); + + + This result is now passed to + stream_tail in our original + expression. This forces the delayed + stream_filter, + which in turn keeps forcing the delayed + stream_@enumerate_interval until it + finds the next prime, which is 10,009. Finally, the result passed to + head in our original expression is + + now_passed_to + prime_definition + stream_enumerate_interval + 10957 + +pair(10009, + () => stream_filter( + is_prime, + pair(10010, + () => stream_enumerate_interval(10011, 1000000)))); + + +stream_ref(pair(10009, + () => stream_filter( + is_prime, + pair(10010, + () => stream_enumerate_interval(10011, 1000000)) + ) + ), 100); + + + The function head returns 10,009, and the + computation is complete. Only as many integers were tested for + primality as were necessary to find the second prime, and the interval + was enumerated only as far as was necessary to feed the prime filter. + + + + + In general, we can think of delayed evaluation as + programmingdemand-driven + demand-driven programming, whereby each stage in the + stream process is activated only enough to satisfy the next stage. What + we have done is to + order of eventsdecoupling apparent from actual + decouple the actual order of events in the computation from the apparent + structure of our functions. We write functions as if the streams existed + all at once when, in reality, the computation is + performed incrementally, as in traditional programming styles. + + + + + An optimization + + + + When we construct stream pairs, we delay the evaluation of their tail + expressions by wrapping these expressions in a function. We force their + evaluation when needed, by applying the function. + + + + + This implementation suffices for streams to work as advertised, but + there is an important optimization that we shall consider where needed. + In many applications, we end up forcing the same delayed object many + times. This can lead to serious inefficiency in recursive programs + involving streams. (See + exercise.) + The solution is to build delayed objects so that the first time they are + forced, they store the value that is computed. Subsequent forcings will + simply return the stored value without repeating the computation. In + other words, we implement the construction of stream pairs as a + delayed expressionmemoized + memoizationstream tailin stream tail + memoized function similar to the one described in + exercise. One way to accomplish this + is to use the following function, which takes as argument a function + (of no arguments) and returns a memoized version of the function. + The first time the memoized function is run, it saves the computed + result. On subsequent evaluations, it simply returns + the result.There are many possible implementations of streams + other than the one described in this section. Delayed evaluation, which + is the key to making streams practical, was inherent in + Algolcall-by-name argument passing + Algol 60s + call-by-name argument passing + call-by-name + parameter-passing method. The use of this mechanism to implement + streams was first described by + Landin, Peter + Landin (1965). Delayed evaluation for + streams was introduced into Lisp by + Friedman, Daniel P. + Wise, David S. + Friedman and Wise (1976). In their + implementation, + cons (the Lisp + equivalent of our + pair function) + always delays evaluating its arguments, so + that lists automatically behave as streams. The memoizing + optimization is also known as + call-by-need argument passing + call-by-need. The Algol community would refer to our original + delayed objects as + thunkcall-by-name + thunkcall-by-need + Algolthunks + call-by-name thunks and to the optimized + versions as call-by-need thunks. + + memo + memo + memo_example + 1 + +function memo(fun) { + let already_run = false; + let result = undefined; + return () => { + if (!already_run) { + result = fun(); + already_run = true; + return result; + } else { + return result; + } + }; +} + + + + memo_example + +function square_4() { + const result = 4 * 4; + display("multiplication carried out"); + return result; +} +const memo_square_4 = memo(square_4); +display(memo_square_4()); // shows "multipl.." +display(memo_square_4()); // does not show "multipl.." + + +let calls = 0; +function square_4() { + const result = 4 * 4; + calls = calls + 1; + return result; +} +const memo_square_4 = memo(square_4); +memo_square_4(); +memo_square_4(); +calls; + + + + + + We can make use of memo whenever + we construct a stream pair. For example, instead of + + stream_map + stream_map_example + 3 + +function stream_map(f, s) { + return is_null(s) + ? null + : pair(f(head(s)), + () => stream_map(f, stream_tail(s))); +} + + + + stream_tail + stream_map_example + +const my_stream = pair(4, () => pair(5, () => null)); + +const my_stream_2 = + stream_map(display, my_stream); + +stream_ref(my_stream_2, 1); +stream_ref(my_stream_2, 1); +// the number 5 is shown twice +// because the same delayed +// object is forced twice + + +const my_stream = pair(4, () => pair(5, () => null)); +let calls = 0; +const my_stream_2 = + stream_map(x => {calls = calls + 1;}, my_stream); + +stream_ref(my_stream_2, 1); +stream_ref(my_stream_2, 1); +calls; + + + we can define an optimized function + stream_map as + follows: + + stream_map_optimized + stream_map_optimized + memo + stream_map_optimized_example + 2 + +function stream_map_optimized(f, s) { + return is_null(s) + ? null + : pair(f(head(s)), + memo(() => + stream_map_optimized(f, stream_tail(s)))); +} + + + + stream_map_optimized_example + +const my_stream = pair(4, () => pair(5, () => null)); + +const my_stream_2 = + stream_map(display, my_stream); + +stream_ref(my_stream_2, 1); +stream_ref(my_stream_2, 1); +// the number 5 is shown twice +// because the same delayed +// object is forced twice + +const my_stream_3 = + stream_map_optimized(display, my_stream); + +stream_ref(my_stream_3, 1); +stream_ref(my_stream_3, 1); +// the number 5 is shown only once +// because the result of forcing +// the delayed object is memoized + + +const my_stream = pair(4, () => pair(5, () => null)); + +const my_stream_2 = + stream_map(x => x, my_stream); + +stream_ref(my_stream_2, 1); +stream_ref(my_stream_2, 1); +// the number 5 is shown twice +// because the same delayed +// object is forced twice + +let calls = 0; +const my_stream_3 = + stream_map_optimized(x => {calls = calls + 1;}, my_stream); + +stream_ref(my_stream_3, 1); +stream_ref(my_stream_3, 1); +calls; + + + + + + + Declare a function stream_map_2 + that takes a binary function and two streams as arguments and returns + a stream whose elements are the results of applying the function + pairwise to the corresponding elements of the argument streams. + + stream_map_2 + +function stream_map_2(f, s1, s2) { + ... +} + + + + stream_combine + memo + +function stream_map_2(f, s1, s2) { + return is_null(s1) && is_null(s2) + ? null + : is_null(s1) || is_null(s2) + ? error(null, "unexpected argument -- stream_map_2") + : pair(f(head(s1), head(s2)), + memo(() => stream_map_2(f, stream_tail(s1), + stream_tail(s2)))); +} + + + Similar to stream_map_optimized, + declare a function + stream_map_2_optimized by + modifying your + stream_map_2 + such that the result stream employs memoization. + + + stream_map_2_optimized_example + +const ints1 = integers_from(1); +const ints0 = integers_from(0); +const adds = (x, y) => x + y; + +display(eval_stream(stream_map_2(adds, ints1, ints0), 5)); +display(eval_stream(stream_map_2_optimized(adds, ints1, ints0), 5)); + + + + stream_map_2_optimized + memo + stream_map_2_optimized_example + +function stream_map_2(f, s1, s2) { + return is_null(s1) || is_null(s2) + ? null + : pair(f(head(s1), head(s2)), + () => stream_map_2(f, stream_tail(s1), stream_tail(s2))); +} + +function stream_map_2_optimized(f, s1, s2) { + return is_null(s1) || is_null(s2) + ? null + : pair(f(head(s1), head(s2)), + memo(() => stream_map_2_optimized(f, stream_tail(s1), stream_tail(s2)))); +} + + + + + + + + Note that our primitive function + display returns its argument + after displaying it. + What does the interpreter print in response to evaluating each + statement in the following sequence?Exercises such + as and + are valuable for testing our understanding of how delayed evaluation + works. On the other hand, intermixing delayed evaluation with + delayed evaluationprinting and + printingand, even worse, with assignmentis extremely + confusing, and instructors of courses on computer languages have + traditionally tormented their students with examination questions such + as the ones in this section. Needless to say, writing programs that + depend on such subtleties is + programmingodious style + odious programming style. Part of the power of stream processing is + that it lets us ignore the order in which events actually happen in + our programs. Unfortunately, this is precisely what we cannot afford + to do in the presence of assignment, which forces us to be concerned + with time and change. + + stream_enumerate_interval + +let x = stream_map(display, stream_enumerate_interval(0, 10)); + +stream_ref(x, 5); + +stream_ref(x, 7); + + + What does the interpreter print if + stream_map_optimized + is used instead of stream_map? + + stream_map_optimized_example_2 + stream_map_optimized + stream_enumerate_interval + +let x = stream_map_optimized(display, stream_enumerate_interval(0, 10)); + +stream_ref(x, 5); + +stream_ref(x, 7); + + + + + + Consider the sequence of statements + + assignment_and_streams + stream_enumerate_interval + even_definition + display_stream + 305 + +let sum = 0; + +function accum(x) { + sum = x + sum; + return sum; +} + +const seq = stream_map(accum, stream_enumerate_interval(1, 20)); + +const y = stream_filter(is_even, seq); + +const z = stream_filter(x => x % 5 === 0, seq); + +stream_ref(y, 7); + +display_stream(z); + + +let sum = 0; + +function accum(x) { + sum = x + sum; + return sum; +} + +const seq = stream_map(accum, stream_enumerate_interval(1, 20)); + +const y = stream_filter(is_even, seq); + +const z = stream_filter(x => x % 5 === 0, seq); + +stream_ref(y, 7); + +stream_ref(z, 3); + + + What is the value of sum after each of the + above statements is evaluated? + delayed evaluationassignment and + What is the printed response to evaluating the + stream_ref and + display_stream expressions? + Would these responses differ if we had applied the function + memo + on every tail of every constructed stream pair, as suggested in the + optimization above? Explain. + + + + + diff --git a/xml/cn/chapter3/section5/subsection2.xml b/xml/cn/chapter3/section5/subsection2.xml new file mode 100644 index 000000000..efb55cbef --- /dev/null +++ b/xml/cn/chapter3/section5/subsection2.xml @@ -0,0 +1,1499 @@ + + + Infinite Streams + + + + + infinite stream(s) + + + We have seen how to support the illusion of manipulating streams + as complete entities even though, in actuality, we compute only + as much of the stream as we need to access. We can exploit this + technique to represent sequences efficiently as streams, even if the + sequences are very long. What is more striking, we can use streams to + represent sequences that are infinitely long. For instance, consider + the following definition of the stream of positive integers: + + integers_starting_from_example + +const from_20 = integers_starting_from(20); +eval_stream(from_20, 50); + + +const from_20 = integers_starting_from(20); +stream_ref(from_20, 50); + + + + integers_starting_from + integers_starting_from + integers_starting_from_example + 70 + +(define (integers-starting-from n) + (cons-stream n (integers-starting-from (+ n 1)))) + + +function integers_starting_from(n) { + return pair(n, () => integers_starting_from(n + 1)); +} + + + + integers (infinite stream) + integers_definition + integers_starting_from + integers_definition_example + 51 + +(define integers (integers-starting-from 1)) + + +const integers = integers_starting_from(1); + + + + integers_definition_example + +eval_stream(integers, 50); + + +stream_ref(integers, 50); + + + This makes sense because integers will be a + pair whose + + car + head + + is 1 and whose + + cdr + tail + + is a promise to produce the integers beginning with 2. This is an infinitely + long stream, but in any given time we can examine only a finite portion of + it. Thus, our programs will never know that the entire infinite stream is + not there. + + + + Using integers we can define other infinite + streams, such as the stream of integers that are not divisible by 7: + + is_divisible2_example + +is_divisible(42, 7); + + + + is_divisible + is_divisible2 + is_divisible2_example + +(define (divisible? x y) (= (remainder x y) 0)) + + +function is_divisible(x, y) { return x % y === 0; } + + + + no_sevens + integers_definition + is_divisible2 + no_sevens_example + 27 + +(define no-sevens + (stream-filter (lambda (x) (not (divisible? x 7))) + integers)) + + +const no_sevens = stream_filter(x => ! is_divisible(x, 7), + integers); + + + + no_sevens_example + +eval_stream(no_sevens, 23); + + +stream_ref(no_sevens, 23); + + + Then we can find integers not divisible by 7 simply by accessing + elements of this stream: + + + on_sevens_example + no_sevens + 117 + +(stream-ref no-sevens 100) + + +117 + + +stream_ref(no_sevens, 100); + + +117 + + + + + + In analogy with integers, we can define the + infinite stream of Fibonacci numbers: + + fibgen_example + +eval_stream(fibs, 50); + + +stream_ref(fibs, 50); + + + + fibs (infinite stream) + fibgen + fibgen_example + 12586269025 + +(define (fibgen a b) + (cons-stream a (fibgen b (+ a b)))) + +(define fibs (fibgen 0 1)) + + +function fibgen(a, b) { + return pair(a, () => fibgen(b, a + b)); +} + +const fibs = fibgen(0, 1); + + + + Fibs + The constant fibs + + + is a pair whose + + car + head + + is 0 and whose + + cdr + tail + + is a promise to evaluate + + (fibgen 1 1). + fibgen(1, 1). + + When we evaluate this delayed + + (fibgen 1 1), + fibgen(1, 1), + + it will produce a pair whose + + car + head + + is 1 and whose + + cdr + tail + + is a promise to evaluate + + (fibgen12), + fibgen(1, 2), + + and so on. + + + + For a look at a more exciting infinite stream, we can generalize the + + no-sevens + no_sevens + + example to construct the infinite stream of prime + numbers, using a method known as the + prime number(s)Eratostheness sieve for + sieve of Eratosthenes + sieve of + Eratosthenes.Eratosthenes, + Eratosthenes + a third-century BCE + Alexandrian Greek philosopher, is famous for giving the first accurate + estimate of the + Earth, measuring circumference of + circumference of the Earth, which he computed by + observing shadows cast at noon on the day of the summer solstice. + Eratostheness sieve method, although ancient, has formed the basis + for special-purpose hardware sieves that, until the 1970s, + were the + most powerful tools in existence for locating large primes. Since then, + however, these methods have been superseded by outgrowths of the + probabilistic algorithm + probabilistic techniques discussed in + section. + We start with the integers beginning with 2, which is the first prime. + To get the rest of the primes, we start by filtering the multiples of + 2 from the rest of the integers. This leaves a stream beginning with + 3, which is the next prime. Now we filter the multiples of 3 from the + rest of this stream. This leaves a stream beginning with 5, which is + the next prime, and so on. In other words, we construct the primes by + a sieving process, described as follows: To sieve a stream + S, + form a stream whose first element is the first element of + S and + the rest of which is obtained by filtering all multiples of the + first element of S out of the rest + of S and sieving the result. This + process is readily described in terms of stream operations: + + + sieve_example + +eval_stream(primes, 50); + + +stream_ref(primes, 50); + + + + primes (infinite stream) + sieve of Eratosthenessieve + sieve + is_divisible2 + integers_starting_from + sieve_example + 233 + +(define (sieve stream) + (cons-stream + (stream-car stream) + (sieve (stream-filter + (lambda (x) + (not (divisible? x (stream-car stream)))) + (stream-cdr stream))))) + +(define primes (sieve (integers-starting-from 2))) + + +function sieve(stream) { + return pair(head(stream), + () => sieve(stream_filter( + x => ! is_divisible(x, head(stream)), + stream_tail(stream)))); +} +const primes = sieve(integers_starting_from(2)); + + + Now to find a particular prime we need only ask for it: + + sieve_example_2 + sieve + 233 + +(stream-ref primes 50) + + +233 + + +stream_ref(primes, 50); + + +233 + + + + + + It is interesting to contemplate the signal-processing system set up + by sieve, shown in the + Henderson, PeterHenderson diagram + Henderson diagram in + figurefigure.We have named these + figures after + Henderson, Peter + Peter Henderson, who was the first person to show us diagrams of this sort + as a way of thinking about stream processing. The input stream feeds into an + + unconser + + unpairer + + + that separates the first element of the stream from the rest of the stream. + The first element is used to construct a divisibility filter, through + which the rest is passed, and the output of the filter is fed to + another sieve box. Then the original first element is + + + consed + onto the output of the internal sieve to form the output stream. + + + adjoined to the output of the internal sieve + to form the output stream. + + + Thus, not only is the stream infinite, but the signal processor is also + infinite, because the sieve contains a sieve within it. + + +
+ The prime sieve viewed as a signal-processing system. +Each solid line represents a + stream of values being transmitted. The dashed line from the + car + to the + cons + and the filter indicates that this is a single + value rather than a stream. + + +
+
+ +
+ The prime sieve viewed as a signal-processing system. +Each solid line represents a + stream of values being transmitted. The dashed line from the + head + to the + pair + and the filter indicates that this is a single + value rather than a stream. + + +
+
+
+
+ + + Defining streams implicitly + + + stream(s)implicit definition + + + The integers and + fibs streams above were defined by specifying + generating + + procedures + functions + + that explicitly compute the stream elements one by one. An alternative way + to specify streams is to take advantage of delayed evaluation to define + streams implicitly. For example, the following + + expression + statement + + defines the + stream ones to be an infinite stream of ones: + + ones_example + +eval_stream(ones, 50); + + +stream_ref(ones, 50); + + + + ones (infinite stream) + ones_definition + ones_example + 1 + +(define ones (cons-stream 1 ones)) + + +const ones = pair(1, () => ones); + + + This works much like the declaration of a recursive + + procedure: + function: + + ones is a pair whose + + car + head + + is 1 and whose + + cdr + tail + + is a promise to evaluate ones. Evaluating the + + cdr + tail + + gives us again a 1 and a promise to evaluate + ones, and so on. + + + + + We can do more interesting things by manipulating streams with + operations such as + + add-streams, + add_streams, + + which produces the elementwise sum of two given streams: + + + This uses the generalized version of + stream-map + from exercise. + + + This uses the function + stream_map_2 + from exercise. + + + + + add_streams_example + +const ones = pair(1, () => ones); +const twos = pair(2, () => twos); +const threes = add_streams(ones, twos); +eval_stream(threes, 50); + + +const ones = pair(1, () => ones); +const twos = pair(2, () => twos); +const threes = add_streams(ones, twos); +stream_ref(threes, 50); + + + + add_streams + add_streams + stream_combine + add_streams_example + 3 + +(define (add-streams s1 s2) + (stream-map + s1 s2)) + + +function add_streams(s1, s2) { + return stream_map_2((x1, x2) => x1 + x2, s1, s2); +} + + + Now we can define the integers as follows: + + integers_definition_2_example + +eval_stream(integers, 50); + + +stream_ref(integers, 50); + + + + integers (infinite stream)implicit definition + integers_definition_2 + add_streams + ones_definition + integers_definition_2_example + 51 + +(define integers (cons-stream 1 (add-streams ones integers))) + + +const integers = pair(1, () => add_streams(ones, integers)); + + + This defines integers to be a stream whose + first element is 1 and the rest of which is the sum of + ones and integers. + Thus, the second element of integers is 1 plus + the first element of integers, or 2; the third + element of integers is 1 plus the second + element of integers, or 3; and so on. This + definition works because, at any point, enough of the + integers stream has been generated so that we + can feed it back into the definition to produce the next integer. + + + + We can define the Fibonacci numbers in the same style: + + fibs_by_magic_example + +eval_stream(fibs, 20); + + +stream_ref(fibs, 20); + + + + fibs (infinite stream)implicit definition + fibs_by_magic + fibs_by_magic_example + add_streams + 6765 + +(define fibs + (cons-stream 0 + (cons-stream 1 + (add-streams (stream-cdr fibs) + fibs)))) + + +const fibs = pair(0, + () => pair(1, + () => add_streams(stream_tail(fibs), + fibs))); + + + This definition says that fibs is a stream + beginning with 0 and 1, such that the rest of the stream can be generated + by adding fibs to itself shifted by one place: + + + +\[ +\begin{array}{ccccccccccccl} + & & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & \ldots & = & \texttt{(stream-cdr fibs)} \\ + & & 0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & \ldots & = & \texttt{fibs} \\ \hline +0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & 34 & \ldots & = & \texttt{fibs} +\end{array} +\] + + + + +\[ +\begin{array}{ccccccccccccl} + & & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & \ldots & = & \texttt{stream}\mathtt{\_}\texttt{tail(fibs)} \\ + & & 0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & \ldots & = & \texttt{fibs} \\ \hline +0 & 1 & 1 & 2 & 3 & 5 & 8 & 13 & 21 & 34 & \ldots & = & \texttt{fibs} +\end{array} +\] + + + + + + + + + Scale-stream + is another useful procedure + + + The function + scale_stream + is also useful + + + in formulating such stream definitions. This multiplies each item in a + stream by a given constant: + + scale_stream_example + +const twos = pair(2, () => twos); +const sixes = scale_stream(twos, 3); +eval_stream(sixes, 50); + + +const twos = pair(2, () => twos); +const sixes = scale_stream(twos, 3); +stream_ref(sixes, 50); + + + + scale_stream + scale_stream + scale_stream_example + 6 + +(define (scale-stream stream factor) + (stream-map (lambda (x) (* x factor)) stream)) + + +function scale_stream(stream, factor) { + return stream_map(x => x * factor, + stream); +} + + + For example, + + double_stream_example + +eval_stream(double, 50); + + +stream_ref(double, 50); + + + + double_stream + scale_stream + double_stream_example + 1125899906842624 + +(define double (cons-stream 1 (scale-stream double 2))) + + +const double = pair(1, () => scale_stream(double, 2)); + + + produces the stream of powers of 2: + $1, 2, 4, 8, 16, 32,$ . + + + + + An alternate definition of the stream of primes can be given by + starting with the integers and filtering them by testing for + primality. We will need the first prime, 2, to get started: + + primes_example + +eval_stream(primes, 50); + + +stream_ref(primes, 50); + + + + primes (infinite stream)implicit definition + primes + square_definition + is_divisible2 + integers_starting_from + primes_example + 233 + +(define primes + (cons-stream + 2 + (stream-filter prime? (integers-starting-from 3)))) + + +function is_prime(n) { + function iter(ps) { + return square(head(ps)) > n + ? true + : is_divisible(n, head(ps)) + ? false + : iter(stream_tail(ps)); + } + return iter(primes); +} + +const primes = pair(2, + () => stream_filter(is_prime, + integers_starting_from(3))); + + +const primes = pair(2, + () => stream_filter(is_prime, + integers_starting_from(3))); + + + This definition is not so straightforward as it appears, because we will + test whether a number $n$ is prime by checking + whether $n$ is divisible by a prime (not by just + any integer) less than or equal to $\sqrt{n}$: + + is_prime2_example + +is_prime(100003); + + + + is_prime + is_prime2 + square_definition + is_divisible2 + integers_starting_from + is_prime2_example + true + +(define (prime? n) + (define (iter ps) + (cond ((> (square (stream-car ps)) n) true) + ((divisible? n (stream-car ps)) false) + (else (iter (stream-cdr ps))))) + (iter primes)) + + +function is_prime(n) { + function iter(ps) { + return square(head(ps)) > n + ? true + : is_divisible(n, head(ps)) + ? false + : iter(stream_tail(ps)); + } + return iter(primes); +} + + +function is_prime(n) { + function iter(ps) { + return square(head(ps)) > n + ? true + : is_divisible(n, head(ps)) + ? false + : iter(stream_tail(ps)); + } + return iter(primes); +} +const primes = pair(2, + () => stream_filter( + is_prime, + integers_starting_from(3)) + ); + + + This is a recursive definition, since primes + is defined in terms of the + + prime? + is_prime + + predicate, which itself uses the primes stream. + The reason this + + procedure + function + + works is that, at any point, enough of the + primes stream has been generated to test the + primality of the numbers we need to check next. That is, for every + $n$ we test for primality, either + $n$ is not prime (in which case there is a prime + already generated that divides it) or $n$ is + prime (in which case there is a prime already generatedi.e., a + prime less than $n$that is greater than + $\sqrt{n}$).This last point is very + subtle and relies on the fact that $p_{n+1} \leq p_{n}^2$. (Here, $p_{k}$ denotes the + $k$th prime.) Estimates such as these are very + difficult to establish. The ancient proof by + Euclids proof of infinite number of primes + Euclid that there are an infinite number of primes shows that + $p_{n+1}\leq p_{1} p_{2}\,\cdots\,\, p_{n} +1$, + and no substantially better result was proved until 1851, when the Russian + mathematician + Chebyshev, Pafnutii Lvovich + P. L. Chebyshev established + that $p_{n+1}\leq 2p_{n}$ for all + $n$. This result, originally conjectured in + 1845, is known as + Bertrands Hypothesis + Bertrands hypothesis. A proof can be + found in section 22.3 of + Hardy, Godfrey Harold + Wright, E. M. + Hardy and Wright 1960. + stream(s)implicit definition + + + + + Without running the program, describe the elements of the + stream defined by + + add_streams_exercise_example + +eval_stream(s, 50); + + + + add_streams_exercise + add_streams + add_streams_exercise_example + +(define s (cons-stream 1 (add-streams s s))) + + +const s = pair(1, () => add_streams(s, s)); + + + + This program defines s to be a stream whose + first element is 1 and each next element is the double of the stream's previous + element. The elements of s are therefore + 1, 2, 4, 8, 16,... . + + + + + + Define a + + procedure + function + + mul_streams + infinite stream(s)of factorials + factorialinfinite stream + + mul-streams, + mul_streams, + + analogous to + + add-streams, + add_streams, + + that produces the elementwise product of its two input streams. Use this + together with the stream of integers to + complete the following definition of the stream whose + $n$th element (counting from 0) is + $n+1$ factorial: + + mul_streams_students + +// mul_streams to be written by students + + + + factorials_stream + mul_streams_students + +(define factorials (cons-stream 1 (mul-streams ?? ??))) + + +const factorials = pair(1, () => mul_streams(??, ??)); + + + + + +const factorials = pair(1, () => mul_streams(factorials, integers)); + + + + + + + + Define a + + procedure + function + + partial_sums + + partial-sums + partial_sums + + + that takes as argument a stream $S$ and returns + the stream whose elements are + $S_0, S_0+S_1, S_0+S_1+S_2,$ . + For example, + + (partial-sums integers) + partial_sums(integers) + + + should be the stream $1, 3, 6, 10, 15,\ldots$. + + + + partial_sums + add_streams + +(define (partial-sum s) + (cons-stream (stream-car s) + (add-streams (stream-cdr s) + (partial-sum s)))) + + +function partial_sums(s) { + return pair(head(s), + () => add_streams(stream_tail(s), + partial_sums(s))); +} + + + + + + + + + A famous problem, first raised by + Hamming, Richard Wesley + R. Hamming, is to enumerate, in ascending order with no repetitions, all + positive integers with no prime factors other than 2, 3, or 5. One obvious + way to do this is to simply test each integer in turn to see whether it has + any factors other than 2, 3, and 5. But this is very inefficient, since, as + the integers get larger, fewer and fewer of them fit the requirement. As + an alternative, let us call the required stream of numbers + S and notice the following facts about it. +
    +
  • + S begins with 1. +
  • +
  • + The elements of + + (scale-stream S 2) + scale_stream(S, 2) + + + are also elements of S. +
  • +
  • + The same is true for + + (scale-stream S 3) + scale_stream(S, 3) + + + and + + (scale-stream S 5). + scale_stream(S, 5). + + +
  • +
  • + These are all the elements of S. +
  • +
+ Now all we have to do is combine elements from these sources. For this we + define a + + procedure + function + + infinite stream(s)merging + merge that combines two ordered + streams into one ordered result stream, eliminating repetitions: + + merge_function_example + integers_starting_from + scale_stream + +const positive_integers = integers_starting_from(1); +const multiples_of_three = scale_stream(positive_integers, 3); +const multiples_of_five = scale_stream(positive_integers, 5); +const threes_and_fives = merge(multiples_of_three, multiples_of_five); +eval_stream(threes_and_fives, 50); + + +const positive_integers = integers_starting_from(1); +const multiples_of_three = scale_stream(positive_integers, 3); +const multiples_of_five = scale_stream(positive_integers, 5); +const threes_and_fives = merge(multiples_of_three, multiples_of_five); +stream_ref(threes_and_fives, 50); + + + + merge + merge_function + merge_function_example + 110 + +(define (merge s1 s2) + (cond ((stream-null? s1) s2) + ((stream-null? s2) s1) + (else + (let ((s1car (stream-car s1)) + (s2car (stream-car s2))) + (cond ((< s1car s2car) + (cons-stream s1car (merge (stream-cdr s1) s2))) + ((> s1car s2car) + (cons-stream s2car (merge s1 (stream-cdr s2)))) + (else + (cons-stream s1car + (merge (stream-cdr s1) + (stream-cdr s2))))))))) + + +function merge(s1, s2) { + if (is_null(s1)) { + return s2; + } else if (is_null(s2)) { + return s1; + } else { + const s1head = head(s1); + const s2head = head(s2); + return s1head < s2head + ? pair(s1head, () => merge(stream_tail(s1), s2)) + : s1head > s2head + ? pair(s2head, () => merge(s1, stream_tail(s2))) + : pair(s1head, () => merge(stream_tail(s1), stream_tail(s2))); + } +} + + + Then the required stream may be constructed with + merge, as follows: + + merge_function + +(define S (cons-stream 1 (merge ?? ??))) + + +const S = pair(1, () => merge(??, ??)); + + + Fill in the missing expressions in the places marked + ?? above. + + + hamming_example + integers_starting_from + scale_stream + +eval_stream(S, 50); + + + + hamming_stream + merge_function + hamming_example + +const S = pair(1, () => merge(scale_stream(S, 2), + merge(scale_stream(S, 3), + scale_stream(S, 5)))); + + + +
+ + + + + + How many additions are performed when we compute the + $n$th Fibonacci number using the definition of + fibs based on the + add-streams procedure? + Show that the number of additions would be exponentially greater if we + had implemented (delay exp) simply as + (lambda () exp), without using the + optimization provided by the memo-proc + procedure described in + section.This + exercise shows how call-by-need is closely related to + call-by-need argument passingmemoization and + memoizationcall-by-need and + delaymemoized + ordinary memoization as described in + exercise. In that exercise, we used + assignment to explicitly construct a local table. Our call-by-need stream + optimization effectively constructs such a table automatically, storing + values in the previously forced parts of the stream. + + + How many additions are performed when we compute the + $n$th Fibonacci number using the declaration of + fibs based on the + add_streams function? + Show that this number is exponentially greater than the number + of additions performed if + add_streams had used the function + stream_map_2_optimized + described in exercise.This + exercise shows how call-by-need is closely related to + delayed expressionmemoized + call-by-need argument passingmemoization and + memoizationcall-by-need and + ordinary memoization as described in + exercise. In that exercise, we used + assignment to explicitly construct a local table. Our call-by-need stream + optimization effectively constructs such a table automatically, storing + values in the previously forced parts of the stream. + + + + + + + Give an interpretation of the stream computed by the + + following procedure: + function + + + +(define (expand num den radix) + (cons-stream + (quotient (* num radix) den) + (expand (remainder (* num radix) den) den radix))) + + +function expand(num, den, radix) { + return pair(math_trunc((num * radix) / den), + () => expand((num * radix) % den, den, radix)); +} + + + + + + (Quotient + primitive procedures (those marked ns are not in the IEEE Scheme standard)quotient + quotient (primitive function) + is a primitive that returns the + integer quotient of two integers.) + + + where + math_truncMath.trunc + math_trunc (primitive function) + math_trunc + discards the fractional part of its argument, here the remainder + of the division. + + + What are the successive elements produced by + + (expand 1 7 10)? + + + expand(1, 7, 10)? + + + What is produced by + + (expand 3 8 10)? + + + expand(3, 8, 10)? + + + + This function will produce a infinite stream of numbers which represent the digits of + num / den + in base-radix + system. + expand(1, 7, 10) + will produce a infinite stream of numbers: 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7... While + expand(3, 8, 10) + will produce a stream of numbers: 3, 7, 5, 0, 0, 0, 0 ... + + + + + + In section we saw how to implement a + polynomial arithmetic system representing polynomials as lists of + terms. In a similar way, we can work with + power series, as stream + infinite stream(s)representing power series + e$e^x$, power series for + cosinepower series for + sinepower series for + power series, such as + + \[ + \begin{array}{rll} + e^{x} &=& + 1+x+\dfrac{x^{2}}{2}+\dfrac{x^{3}}{3\cdot2} + +\dfrac{x^{4}}{4\cdot 3\cdot 2}+\cdots, \\[9pt] + \cos x &=& 1-\dfrac{x^{2}}{2}+\dfrac{x^{4}}{4\cdot 3\cdot 2}-\cdots, \\[9pt] + \sin x &=& x-\dfrac{x^{3}}{3\cdot 2} + +\dfrac{x^{5}}{5\cdot 4\cdot 3\cdot 2}- \cdots, + \end{array} + \] + + represented as infinite streams. + We will represent the series + $a_0 + a_1 x + a_2 x^2 + a_3 x^3 + \cdots$ + as the stream whose elements are the coefficients + $a_0, a_1, a_2, a_3,$ . +
    +
  1. + The + integralof a power series + power series, as streamintegrating + integral of the series + $a_0 + a_1 x + a_2 x^2 + a_3 x^3 + \cdots$ + is the series + + \[ + \begin{array}{l} + c + a_0 x + \frac{1}{2}a_1 x^2 + \frac{1}{3}a_2 x^3 + \frac{1}{4}a_3 + x^4 + \cdots + \end{array} + \] + + where $c$ is any constant. + Define a + procedure + function + + integrate_series + + integrate-series + integrate_series + + + that takes as input a stream + $a_0, a_1, a_2,\ldots$representing + a power series and returns the stream + $a_0, \frac{1}{2}a_1, \frac{1}{3}a_2,\ldots$ + of coefficients of the nonconstant terms of the integral of the series. + (Since the result has no constant term, it doesnt represent a power + series; when we use + + integrate-series, + integrate_series, + + + we will + + + cons on the appropriate constant.) + + + use + pair to + adjoin the appropriate constant to the beginning + of the stream.) + + +
  2. +
  3. + The function $x\mapsto e^x$ is its own + derivative. This implies that $e^x$ and the + integral of$e^x$ are the + same series, except for the constant term, which is + $e^0 = 1$. Accordingly, we can generate the + series for $e^x$ as + + +(define exp-series + (cons-stream 1 (integrate-series exp-series))) + + +const exp_series = pair(1, () => integrate_series(exp_series)); + + + Show how to generate the series for sine and cosine, starting from the + facts that the derivative of sine is cosine and the derivative of cosine + is the negative of sine: + + +(define cosine-series + (cons-stream 1 ??)) + +(define sine-series + (cons-stream 0 ??)) + + +const cosine_series = pair(1, ??); +const sine_series = pair(0, ??); + + +
  4. +
+ + + +function integrate_series(s) { + function helper(ss, iter) { + return pair(head(ss) / iter, () => helper(stream_tail(ss), iter + 1)); + } + return helper(s, 1); +} + +const cos_series = pair(1, () => integrate_series( + pair(0, + () => stream_map( + (x) => (-x), + integrate_series(cos_series))))); + +const sin_series = pair(0, () => integrate_series( + pair(1, + () => integrate_series( + stream_map(x => -x, sin_series) + )) + )); + + + +
+ + + + With + power series, as streamadding + power series, as streammultiplying + arithmeticon power series + mul_series + power series represented as streams of coefficients as in + exercise, adding series is implemented + by + + + add-streams. + + + add_streams. + + + Complete the declaration of + the following + + procedure + function + + for multiplying series: + + +(define (mul-series s1 s2) + (cons-stream ?? (add-streams ?? ??))) + + +function mul_series(s1, s2) { + pair(??, () => add_streams(??, ??)); +} + + + + You can test your + + procedure + function + + by verifying that $sin^2 x + cos^2 x = 1$, + using the series from exercise. + + + +function mul_series(s1, s2) { + return pair(head(s1) * head(s2), + () => add_streams( + mul_series(stream_tail(s1), s2), + scale_stream(stream_tail(s2), head(s1)))); +} + + + + + + + + + Let $S$ be a power series + (exercise) + whose constant term is 1. Suppose we want to find the power series + $1/S$, that is, the series + $X$ such that + $S\cdot X= 1$. + Write $S=1+S_R$ where + $S_R$ is the part of + $S$ after the constant term. Then we can solve + for $X$ as follows: + \[ + \begin{array}{rll} + S \cdot X &=& 1 \\ + (1+S_R)\cdot X &=& 1 \\ + X + S_R \cdot X &=& 1 \\ + X &=& 1 - S_R \cdot X + \end{array} + \] + In other words, $X$ is the power series whose + constant term is 1 and whose higher-order terms are given by the negative of + $S_R$ times $X$. + Use this idea to write a + + procedure + function + + + invert-unit-series + invert_@unit_@series + + + that computes $1/S$ for a power series + $S$ with constant term 1. You will need to use + + mul-series + mul_series + + from exercise. + + + +function invert_unit_series(s) { + return pair(1, () => stream_map(x => -x, + mul_series(stream_tail(s), invert_unit_series(s)))); +} + + + + + + + + Use the results of exercises + and + to define a + + procedure + function + + power series, as streamdividing + arithmeticon power series + div_series + + div-series + div_series + + that divides two power series. + + Div-series + The function + div_series + + should work for any two series, provided that the denominator series begins + with a nonzero constant term. (If the denominator has a zero constant term, + then + + div-series + div_series + + should signal an error.) Show how to use + + div-series + div_series + + together with the result of exercise + to generate the power series for + tangentpower series for + tangent. + + + infinite stream(s) + +
diff --git a/xml/cn/chapter3/section5/subsection3.xml b/xml/cn/chapter3/section5/subsection3.xml new file mode 100644 index 000000000..ad18684ae --- /dev/null +++ b/xml/cn/chapter3/section5/subsection3.xml @@ -0,0 +1,1545 @@ + + + Exploiting the Stream Paradigm + + + + + Streams with delayed evaluation can be a powerful modeling tool, + providing many of the benefits of local state and assignment. + Moreover, they avoid some of the theoretical tangles that accompany + the introduction of assignment into a programming language. + + + + The stream approach can be illuminating because it allows us to build + systems with different + modularitystreams and + module boundaries than systems organized around + assignment to state variables. For example, we can think of an entire + time series (or signal) as a focus of interest, rather than the values + of the state variables at individual moments. This makes it + convenient to combine and compare components of state from different + moments. + + + + Formulating iterations as stream processes + + + iterative processas a stream process + + + In section, we introduced + iterative processes, which proceed by updating state variables. We know now + that we can represent state as a timeless stream of values + rather than as a set of variables to be updated. Lets adopt this + perspective in revisiting the square-root + + procedure + function + + from section. Recall that the idea is to + generate a sequence of better and better guesses for the square root of + $x$ by applying over and over again the + + procedure + function + + that improves guesses: + + sqrt_improve_example + +sqrt_improve(1.2, 2); + + + + sqrt_improve + average_definition + sqrt_improve_example + 1.4333333333333333 + +(define (sqrt-improve guess x) + (average guess (/ x guess))) + + +function sqrt_improve(guess, x) { + return average(guess, x / guess); +} + + + + + + In our original + square rootstream of approximations + sqrt + + procedure, + function, + + we made these guesses be the successive values of a state variable. Instead + we can generate the infinite stream of guesses, starting with an initial + + + guess of 1:We can't use + let to bind the local variable + guesses because the value of + guesses depends on + guesses itself. + Exercise + addresses why we want + a local name here. + + + guess of 1: + + + + sqrt_stream + sqrt_stream + sqrt_improve + sqrt_stream_example + +(define (sqrt-stream x) + (define guesses + (cons-stream 1.0 + (stream-map (lambda (guess) + (sqrt-improve guess x)) + guesses))) + guesses) + + +function sqrt_stream(x) { + return pair(1, () => stream_map(guess => sqrt_improve(guess, x), + sqrt_stream(x))); +} + + + + sqrt_stream_example + display_stream + sqrt_stream + 1.414213562373095 + +(display-stream (sqrt-stream 2)) + + +1. +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +... + + +display_stream(sqrt_stream(2)); + + +stream_ref(sqrt_stream(2), 5); + + +1 +1.5 +1.4166666666666665 +1.4142156862745097 +1.4142135623746899 +... + + + We can generate more and more terms of the stream to get better and + better guesses. If we like, we can write a + + procedure + function + + that keeps generating terms until the answer is good enough. + (See exercise.) + + + + Another iteration that we can treat in the same way is to generate an + approximation to + pi$\pi$ (pi)Leibnizs series for + Leibniz, Baron Gottfried Wilhelm vonseries for $\pi$ + pi$\pi$ (pi)stream of approximations + series, summation ofwith streams + summation of a serieswith streams + infinite stream(s)to sum a series + $\pi$, based upon the + alternating series that we saw in + section: + + \[ + \begin{array}{lll} + \dfrac {\pi}{4} &=& 1-\dfrac{1}{3}+\dfrac{1}{5}-\dfrac{1}{7}+\cdots + \end{array} + \] + + We first generate the stream of summands of the series (the reciprocals + of the odd integers, with alternating signs). Then we take the stream + of sums of more and more terms (using the + + partial-sums procedure + partial_sums function + + + of exercise) and scale the result by 4: + + pi_stream + pi_stream + display_stream + scale_stream + partial_sums + pi_stream_example + +(define (pi-summands n) + (cons-stream (/ 1.0 n) + (stream-map - (pi-summands (+ n 2))))) + +(define pi-stream + (scale-stream (partial-sums (pi-summands 1)) 4)) + + +function pi_summands(n) { + return pair(1 / n, () => stream_map(x => - x, pi_summands(n + 2))); +} +const pi_stream = scale_stream(partial_sums(pi_summands(1)), 4); + + + + pi_stream_example + pi_stream + 3.017071817071818 + +(display-stream pi-stream) + + +4. +2.666666666666667 +3.466666666666667 +2.8952380952380956 +3.3396825396825403 +2.9760461760461765 +3.2837384837384844 +3.017071817071818 +... + + +display_stream(pi_stream); + + +stream_ref(pi_stream, 7); + + +4 +2.666666666666667 +3.466666666666667 +2.8952380952380956 +3.3396825396825403 +2.9760461760461765 +3.2837384837384844 +3.017071817071818 +... + + + This gives us a stream of better and better approximations to + $\pi$, although the approximations converge + rather slowly. Eight terms of the sequence bound the value of + $\pi$ between 3.284 and 3.017. + + + + + So far, our use of the stream of states approach is not much different + from updating state variables. But streams give us an opportunity to do + some interesting tricks. For example, we can transform a stream with a + series, summation ofaccelerating sequence of approximations + sequence accelerator + sequence accelerator that converts a sequence of approximations to a + new sequence that converges to the same value as the original, only faster. + + + + One such accelerator, due to the eighteenth-century Swiss mathematician + Euler, Leonhardseries accelerator + Leonhard Euler, works well with sequences that are partial sums of + alternating series (series of terms with alternating signs). In + Eulers technique, if $S_n$ is the + $n$th term of the original sum sequence, then + the accelerated 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 sequence is given by + + euler_transform + euler_transform + square_definition + memo + euler_transform_example + 3.1418396189294033 + +(define (euler-transform s) + (let ((s0 (stream-ref s 0)) ; $S_{n-1}$ + (s1 (stream-ref s 1)) ; $S_{n}$ + (s2 (stream-ref s 2))) ; $S_{n+1}$ + (cons-stream (- s2 (/ (square (- s2 s1)) + (+ s0 (* -2 s1) s2))) + (euler-transform (stream-cdr s))))) + + +function euler_transform(s) { + const s0 = stream_ref(s, 0); // $S_{n-1}$ + const s1 = stream_ref(s, 1); // $S_{n}$ + const s2 = stream_ref(s, 2); // $S_{n+1}$ + return pair(s2 - square(s2 - s1) / (s0 + (-2) * s1 + s2), + memo(() => euler_transform(stream_tail(s)))); +} + + + + + Note that we make use of the memoization optimization of + section, because in the + following we will rely on repeated evaluation of the resulting stream. + + + + + We can demonstrate Euler acceleration with our sequence of + approximations to$\pi$: + + euler_transform_example + euler_transform + display_stream + pi_stream + 3.1418396189294033 + + (display-stream (euler-transform pi-stream)) + + +3.166666666666667 +3.1333333333333337 +3.1452380952380956 +3.13968253968254 +3.1427128427128435 +3.1408813408813416 +3.142071817071818 +3.1412548236077655 +... + + +display_stream(euler_transform(pi_stream)); + + +stream_ref(euler_transform(pi_stream), 8); + + +3.166666666666667 +3.1333333333333337 +3.1452380952380956 +3.13968253968254 +3.1427128427128435 +3.1408813408813416 +3.142071817071818 +3.1412548236077655 +... + + + + + + Even better, we can accelerate the accelerated sequence, and recursively + accelerate that, and so on. Namely, we create a stream of streams (a + structure well call a + tableau + tableau) in which each stream is the transform of the preceding one: + + make_tableau + make_tableau + +(define (make-tableau transform s) + (cons-stream s + (make-tableau transform + (transform s)))) + + +function make_tableau(transform, s) { + return pair(s, () => make_tableau(transform, transform(s))); +} + + + The tableau has the form + + \[ + \begin{array}{llllll} + s_{00} & s_{01} & s_{02} & s_{03} & s_{04} & \ldots\\ + & s_{10} & s_{11} & s_{12} & s_{13} & \ldots\\ + & & s_{20} & s_{21} & s_{22} & \ldots\\ + & & & & \ldots & + \end{array} + \] + + Finally, we form a sequence by taking the first term in each row of + the tableau: + + accelerated_sequence + accelerated_sequence + make_tableau + accelerated_sequence_example + +(define (accelerated-sequence transform s) + (stream-map stream-car + (make-tableau transform s))) + + +function accelerated_sequence(transform, s) { + return stream_map(head, make_tableau(transform, s)); +} + + + + + We can demonstrate this kind of super-acceleration of the + $\pi$ sequence: + + accelerated_sequence_example + accelerated_sequence + euler_transform + pi_stream + display_stream + 3.1415926535911765 + +(display-stream (accelerated-sequence euler-transform + pi-stream)) + + +4. +3.166666666666667 +3.142105263157895 +3.141599357319005 +3.1415927140337785 +3.1415926539752927 +3.1415926535911765 +3.141592653589778 +... + + +display_stream(accelerated_sequence(euler_transform, pi_stream)); + + +stream_ref(accelerated_sequence(euler_transform, pi_stream), + 6); + + +4 +3.166666666666667 +3.142105263157895 +3.141599357319005 +3.1415927140337785 +3.1415926539752927 +3.1415926535911765 +3.141592653589778 +... + + + The result is impressive. Taking eight terms of the sequence yields the + correct value of $\pi$ to 14 decimal places. + If we had used only the original $\pi$ sequence, + we would need to compute on the order of $10^{13}$ + terms (i.e., expanding the series far enough so that the individual terms + are less then $10^{-13}$) to get that much + accuracy! + pi$\pi$ (pi)stream of approximations + + + + We could have implemented these acceleration techniques without using + streams. But the stream formulation is particularly elegant and convenient + because the entire sequence of states is available to us as a data structure + that can be manipulated with a uniform set of operations. + + + + + + + Louis Reasoner asks why the + sqrt-stream procedure + was not written in the following more straightforward way, without + the local variable guesses: + + +(define (sqrt-stream x) + (cons-stream 1.0 + (stream-map (lambda (guess) + (sqrt-improve guess x)) + (sqrt-stream x)))) + + + Alyssa P. Hacker replies that this version of the procedure + is considerably less efficient because it performs redundant computation. + Explain Alyssas answer. Would the two versions still differ in + efficiency if our implementation of delay + used only (lambda () exp) + without using the optimization provided by + memo-proc + (section)? + + + Louis Reasoner is not happy with the performance of the stream + produced by the + sqrt_@stream function and + tries to optimize it using memoization: + + +function sqrt_stream_optimized(x) { + return pair(1, + memo(() => stream_map(guess => + sqrt_improve(guess, x), + sqrt_stream_optimized(x)))); +} + + + Alyssa P. Hacker instead proposes + + +function sqrt_stream_optimized_2(x) { + const guesses = pair(1, + memo(() => stream_map(guess => + sqrt_improve(guess, x), + guesses))); + return guesses; +} + + + and claims that Louis's version is + considerably less efficient than hers, because it performs + redundant computation. Explain Alyssa's answer. + Would Alyssa's approach without memoization be more + efficient + than the original sqrt_stream? + + + + + + Write a + stream_limit + + procedure stream-limit + function stream_limit + + + that takes as arguments a stream + and a number (the tolerance). It should examine the stream until it + finds two successive elements that differ in absolute value by less + than the tolerance, and return the second of the two elements. Using + this, we could compute square roots up to a given tolerance by + + sqrtstreamas stream limit + +(define (sqrt x tolerance) + (stream-limit (sqrt-stream x) tolerance)) + + +function sqrt(x, tolerance) { + return stream_limit(sqrt_stream(x), tolerance); +} + + + + + + Use the series + + \[ + \begin{array}{lll} + \ln 2 &=& 1-\dfrac{1}{2}+\dfrac{1}{3}-\dfrac{1}{4}+\cdots + \end{array} + \] + + to compute three sequences of approximations to the natural logarithm of 2, + logarithm, approximating $\ln 2$ + in the same way we did above for $\pi$. + How rapidly do these sequences converge? + + + + iterative processas a stream process + + + Infinite streams of pairs + + + pair(s)infinite stream of + infinite stream(s)of pairs + mappingnested + + + In section, we saw how the + sequence paradigm handles traditional nested loops as processes defined + on sequences of pairs. If we generalize this technique to infinite streams, + then we can write programs that are not easily represented as loops, because + the looping must range over an infinite set. + + + + For example, suppose we want to generalize the + prime_sum_pairsinfinite stream + + prime-sum-pairs procedure + prime_sum_pairs function + + + of section to produce the stream + of pairs of all integers $(i,j)$ with + $i \leq j$ such that + $i+j$ + is prime. If + + int-pairs + int_pairs + + is the sequence of all pairs of integers $(i,j)$ + with $i \leq j$, then our required stream is + simplyAs in + section, we + represent a pair of integers as a list rather than a + + Lisp + + pair. + + int_pairs + pairs_second_attempt + integers_definition + display_stream + +const int_pairs = pairs(integers, integers); + + + + prime_sum_pairs_stream + is_prime2 + int_pairs + [ 1, [ 12, null ] ] + +(stream-filter (lambda (pair) + (prime? (+ (car pair) (cadr pair)))) + int-pairs) + + +stream_filter(pair => is_prime(head(pair) + head(tail(pair))), + int_pairs); + + +stream_ref(stream_filter(pair => is_prime(head(pair) + head(tail(pair))), + int_pairs), 8); + + + + + + Our problem, then, is to produce the stream + + int-pairs. + int_pairs. + + More generally, suppose we have two streams + $S = (S_i)$ and + $T = (T_j)$, + and imagine the infinite rectangular array + + \[ + \begin{array}{cccc} + (S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \ldots\\ + (S_1,T_0) & (S_1,T_1) & (S_1, T_2) & \ldots\\ + (S_2,T_0) & (S_2,T_1) & (S_2, T_2) & \ldots\\ + \ldots + \end{array} + \] + + We wish to generate a stream that contains all the pairs in the array + that lie on or above the diagonal, i.e., the pairs + + \[ + \begin{array}{cccc} + (S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \ldots\\ + & (S_1,T_1) & (S_1, T_2) & \ldots\\ + & & (S_2, T_2) & \ldots\\ + & & & \ldots + \end{array} + \] + + (If we take both $S$ and + $T$ to be the stream of integers, then this + will be our desired stream + int-pairs.) + int_pairs.) + + + + + + Call the general stream of pairs + (pairs S T), + pairs(S, T), + + and consider it to be composed of three parts: the pair + $(S_0,T_0)$, the rest of the pairs in the first + row, and the remaining pairs:See + exercise for some insight into why we + chose this decomposition. + + + \[ + \begin{array}{c|ccc} + (S_0,T_0) & (S_0,T_1) & (S_0, T_2) & \ldots\\ + \hline{} %--------------------------------------------------- \\ + & (S_1,T_1) & (S_1, T_2) & \ldots\\ + & & (S_2, T_2) & \ldots\\ + & & & \ldots + \end{array} + \] + + Observe that the third piece in this decomposition (pairs that are not in + the first row) is (recursively) the pairs formed from + + (stream-cdr S) + stream_tail(S) + + + and + + (stream-cdr T). + stream_tail(T). + + + Also note that the second piece (the rest of the first row) is + + +(stream-map (lambda (x) (list (stream-car s) x)) + (stream-cdr t)) + + +stream_map(x => list(head(s), x), + stream_tail(t)); + + + Thus we can form our stream of pairs as follows: + + +(define (pairs s t) + (cons-stream + (list (stream-car s) (stream-car t)) + ($\langle combine-in-some-way \rangle$ + (stream-map (lambda (x) (list (stream-car s) x)) + (stream-cdr t)) + (pairs (stream-cdr s) (stream-cdr t))))) + + +function pairs(s, t) { + return pair(list(head(s), head(t)), + () => combine-in-some-way( + stream_map(x => list(head(s), x), + stream_tail(t)), + pairs(stream_tail(s), stream_tail(t)))); +} + + + + + + In order to complete the + + procedure,function, + + we must choose some way to + infinite stream(s)merging + combine the two inner streams. One idea is to + use the stream analog of the append + + procedure + function + + from section: + + stream_append_example + +const ones = pair(1, () => ones); +const twos = pair(2, () => twos); +const appended = stream_append(ones, twos); +stream_ref(appended, 100); + + + + stream_append + stream_append + stream_append_example + 1 + +(define (stream-append s1 s2) + (if (stream-null? s1) + s2 + (cons-stream (stream-car s1) + (stream-append (stream-cdr s1) s2)))) + + +function stream_append(s1, s2) { + return is_null(s1) + ? s2 + : pair(head(s1), + () => stream_append(stream_tail(s1), s2)); +} + + + This is unsuitable for infinite streams, however, because it takes all the + elements from the first stream before incorporating the second stream. In + particular, if we try to generate all pairs of positive integers using + + pairs_first_attempt + stream_append + +function pairs(s, t) { + return pair(list(head(s), head(t)), + () => stream_append( + stream_map(x => list(head(s), x), + stream_tail(t)), + pairs(stream_tail(s), stream_tail(t))) + ); +} + + + + pairs_first_attempt_usage + pairs_first_attempt + pairs_first_attempt_example + [ 1, [ 9, null ] ] + +(pairs integers integers) + + +pairs(integers, integers); + + + + pairs_first_attempt_example + display_stream + integers_definition + +(pairs integers integers) + + +display_stream(pairs(integers, integers)); + + +stream_ref(pairs(integers, integers), 8); + + + our stream of results will first try to run through all pairs with the + first integer equal to 1, and hence will never produce pairs with any + other value of the first integer. + + + + To handle infinite streams, we need to devise an order of combination + that ensures that every element will eventually be reached if we let + our program run long enough. An elegant way to accomplish this is + with the following interleave + procedurefunction:The + precise statement of the required property on the order of combination is + as follows: There should be a function $f$ of + two arguments such that the pair corresponding to + element$i$ of the first stream and + element$j$ of the second stream will + appear as element number $f(i,j)$ of the output + stream. The trick of using interleave + to accomplish this was shown to us by + Turner, David + David Turner, who employed it in the language + KRC + KRC (Turner 1981). + + interleave_example + display_stream + +const ones = pair(1, () => ones); +const twos = pair(2, () => twos); +const interleaved = interleave(ones, twos); +display_stream(interleaved); + + +const ones = pair(1, () => ones); +const twos = pair(2, () => twos); +const interleaved = interleave(ones, twos); +stream_ref(interleaved, 7); + + + + interleave + interleave + interleave_example + 2 + +(define (interleave s1 s2) + (if (stream-null? s1) + s2 + (cons-stream (stream-car s1) + (interleave s2 (stream-cdr s1))))) + + +function interleave(s1, s2) { + return is_null(s1) + ? s2 + : pair(head(s1), + () => interleave(s2, stream_tail(s1))); +} + + + Since interleave takes elements alternately + from the two streams, every element of the second stream will eventually + find its way into the interleaved stream, even if the first stream is + infinite. + + + + We can thus generate the required stream of pairs as + + pairs + pairs_second_attempt + interleave + pairs_second_attempt_example + [ 2, [ 4, null ] ] + +(define (pairs s t) + (cons-stream + (list (stream-car s) (stream-car t)) + (interleave + (stream-map (lambda (x) (list (stream-car s) x)) + (stream-cdr t)) + (pairs (stream-cdr s) (stream-cdr t))))) + + +function pairs(s, t) { + return pair(list(head(s), head(t)), + () => interleave(stream_map(x => list(head(s), x), + stream_tail(t)), + pairs(stream_tail(s), + stream_tail(t)))); +} + + + + pairs_second_attempt_example + display_stream + integers_definition + + (pairs integers integers) + + +display_stream(pairs(integers, integers)); + + +stream_ref(pairs(integers, integers), 8); + + + + + + + Examine the stream + + (pairs integers integers). + pairs(integers, integers). + + + Can you make any general comments about the order in which the pairs are + placed into the stream? For example, approximately how many pairs precede + the pair (1,100)? the pair (99,100)? the pair (100,100)? (If you can make + precise mathematical statements here, all the better. But feel free to give + more qualitative answers if you find yourself getting bogged down.) + + + + + Modify the pairs + + procedure + function + + so that + + (pairs integers integers) + pairs(integers, integers) + + + will produce the stream of all pairs of integers + $(i,j)$ (without the condition + $i \leq j$). Hint: You will need to + mix in an additional stream. + + + + + Louis Reasoner thinks that building a stream of pairs from three parts is + unnecessarily complicated. Instead of separating the pair + $(S_0,T_0)$ from the rest of the pairs in the + first row, he proposes to work with the whole first row, as follows: + + +(define (pairs s t) + (interleave + (stream-map (lambda (x) (list (stream-car s) x)) + t) + (pairs (stream-cdr s) (stream-cdr t)))) + + +function pairs(s, t) { + return interleave(stream_map(x => list(head(s), x), + t), + pair(stream_tail(s), stream_tail(t))); +} + + + Does this work? Consider what happens if we evaluate + + (pairs integers integers) + pairs(integers, integers) + + + using Louiss definition of pairs. + + + + + Write a + + procedure + function + + triples that takes three infinite streams, + $S$, $T$, and + $U$, and produces the stream of triples + $(S_i,T_j,U_k)$ such that + $i \leq j \leq k$. Use + triples to generate the stream of all + Pythagorean tripleswith streams + Pythagorean triples of positive integers, i.e., the triples + $(i,j,k)$ such that + $i \leq j$ and + $i^2 + j^2 =k^2$. + + + + + + It would be nice to be able to generate + infinite stream(s)merging + streams in which the pairs + appear in some useful order, rather than in the order that results + from an ad hoc interleaving process. We can use a technique + similar to the merge + + procedure + function + + of exercise, if we define a way to say that + one pair of integers is less than another. One way to do + this is to define a + weighting function + $W(i,j)$ and stipulate that + $(i_1,j_1)$ is less than + $(i_2,j_2)$ if + $W(i_1,j_1) < W(i_2,j_2)$. Write a + merge_weighted + + procedure merge-weighted + function merge_weighted + + + that is like merge, except that + + merge-weighted + merge_weighted + + + takes an additional argument weight, which is a + + procedure + function + + that computes the weight of a pair, and is used to determine the order in + which elements should appear in the resulting merged stream.We + will require that the weighting function be such that the weight of a pair + increases as we move out along a row or down along a column of the array of + pairs. Using this, generalize pairs + to a + + procedure weighted-pairs + function weighted_pairs + + + that takes two streams, together with a + + procedure + function + + that computes a weighting function, and generates the stream of pairs, + ordered according to weight. Use your + + procedure + function + + to generate +
    +
  1. + the stream of all pairs of positive integers + $(i,j)$ with $i \leq + j$ ordered according to the sum + $i + j$ +
  2. +
  3. + the stream of all pairs of positive integers + $(i,j)$ with $i \leq + j$, where neither $i$ nor + $j$ is divisible by 2, 3, or 5, and the + pairs are ordered according to the sum + $2 i + 3 j + 5 i j$. +
  4. +
+
+ + + Numbers that can be expressed as the sum of two cubes in more than one + way are sometimes called + Ramanujan numbers + Ramanujan numbers, in honor of the + mathematician Srinivasa Ramanujan.To quote from G. H. + Hardys obituary of + Hardy, Godfrey Harold + Ramanujan, Srinivasa + Ramanujan (Hardy 1921): It was + Mr.Littlewood + (I believe) who remarked that every positive integer was one of his + friends. I remember once going to see him when he was lying ill + at Putney. I had ridden in taxi-cab No. 1729, and remarked that the number + seemed to me a rather dull one, and that I hoped it was not an unfavorable + omen. No, he replied, it is a very interesting number; + it is the smallest number expressible as the sum of two cubes in two + different ways. The trick of using weighted pairs to + generate the Ramanujan numbers was shown to us by + Leiserson, Charles E. + Charles + Leiserson. Ordered streams of pairs provide an elegant solution + to the problem of computing these numbers. To find a number that can be + written as the sum of two cubes in two different ways, we need only generate + the stream of pairs of integers $(i,j)$ weighted + according to the sum $i^3 + j^3$ (see + exercise), then search the stream for + two consecutive pairs with the same weight. Write a + + procedure + function + + to generate the Ramanujan numbers. The first + such number is 1,729. What are the next five? + + + + + In a similar way to exercise generate + a stream of all numbers that can be written as the sum of two squares in + three different ways (showing how they can be so written). + + + + pair(s)infinite stream of + infinite stream(s)of pairs + mappingnested + + + Streams as signals + + + signal processingstream model of + infinite stream(s)to model signals + + + + We began our discussion of streams by describing them as computational + analogs of the signals in signal-processing systems. + In fact, we can use streams to model signal-processing systems in a very + direct way, representing the values of a signal at successive time + intervals as consecutive elements of a stream. For instance, we can + implement an + integrator, for signals + integrator or + summer that, for an input stream + $x=(x_{i})$, an initial value$C$, and a small increment $dt$, + accumulates the sum + + \[ + \begin{array}{lll} + S_i &=& C +\sum_{j=1}^{i} x_{j} \, dt + \end{array} + \] + + and returns the stream of values $S=(S_{i})$. + The following integral + + procedure + function + + is reminiscent of the implicit style definition of the + stream of integers (section): + + integral_1_example + +function numbers_starting_from(t, dt) { + return pair(t, + () => numbers_starting_from(t + dt, dt) + ); +} +const dt = 0.01; +const linear = numbers_starting_from(0, dt); +const linear_integral = integral(linear, 0, dt); +// computing integral from 0 to 3 of f(x) = x +// (the integral is g(x) = 0.5 x^2, and therefore +// the result is near 0.5 * 3^2 = 4.5) +stream_ref(linear_integral, math_round(3 / dt)); + + + + integral + integral_1 + add_streams + scale_stream + integral_1_example + 4.484999999999992 + +(define (integral integrand initial-value dt) + (define int + (cons-stream initial-value + (add-streams (scale-stream integrand dt) + int))) + int) + + +function integral(integrand, initial_value, dt) { + const integ = pair(initial_value, + () => add_streams(scale_stream(integrand, dt), + integ)); + return integ; +} + + + + +
+ The integral + procedure viewed as a signal-processing system. + + +
+
+ +
+ The integral + function viewed as a signal-processing system. + + +
+
+
+
+ +\noindent + + + Figure + + + Figure + + + is a picture of a signal-processing + system that corresponds to the integral + + procedure. + function. + + The input stream is scaled by $dt$ and passed + through an adder, whose output is passed back through the same adder. + The self-reference in the definition of + + + int + + + integ + + + is reflected in the figure by the feedback loop that + connects the output of the adder to one of the inputs. + + + + + We can model electrical circuits using streams to represent the values + of currents or voltages at a sequence of times. For instance, suppose + we have an + RC circuit + circuitmodeled with streams + electrical circuits, modeled with streams + RC circuit consisting of a resistor of resistance + $R$ and a capacitor of capacitance + $C$ in series. The voltage response + $v$ of the circuit to an injected current + $i$ is determined by the formula in + figure, whose structure is shown by the + accompanying signal-flow diagram. + + + +
+
+ An RC circuit and the associated + signal-flow diagram + signal-flow diagram. + + +
+
+

+ Write a + + procedure + function + + RC that models this circuit. + RC should take as inputs the values of + $R$, $C$, and + $dt$ and should return a + + procedure + function + + that takes as inputs a stream representing the current + $i$ and an initial value for the capacitor + voltage $v_{0}$ and produces as output the + stream of voltages $v$. For example, you + should be able to use RC to model an RC + circuit with $R = 5$ ohms, + $C = 1$ farad, and a 0.5-second time step by + evaluating + + (define RC1 (RC 5 1 0.5)). + + + const RC1 = RC(5, 1, 0.5). + + + This defines RC1 as a + + procedure + function + + that takes a stream representing the time sequence of currents and an + initial capacitor voltage and produces the output stream of voltages. +

+
+ + + Alyssa P. Hacker is designing a system to process signals coming from + physical sensors. One important feature she wishes to produce is a signal + that describes the + signal processingzero crossings of a signal + zero crossings of a signal + zero crossings of the input signal. That is, + the resulting signal should be $+1$ whenever the + input signal changes from negative to positive, + $-1$ whenever the input signal changes from + positive to negative, and 0 otherwise. (Assume that the sign of a 0 input + is positive.) For example, a typical input signal with its associated + zero-crossing signal would be + + +$\ldots$ 1 2 1.5 1 0.5 -0.1 -2 -3 -2 -0.5 0.2 3 4 $\ldots$ +$\ldots$ 0 0 0 0 0 -1 0 0 0 0 1 0 0 $\ldots$ + + +$\ldots$ 1 2 1.5 1 0.5 -0.1 -2 -3 -2 -0.5 0.2 3 4 $\ldots$ +$\ldots\;$ 0 0 0 0 0 -1 0 0 0 0 1 0 0 $\ldots$ + + + + In Alyssas system, the signal from the sensor is represented as a + stream + + sense-data + sense_data + + and the stream + + zero-crossings + zero_crossings + + + is the corresponding stream of zero crossings. Alyssa first writes a + + procedure + function + + + sign-change-detector + sign_change_detector + + + that takes two values as arguments and compares the signs of the values to + produce an appropriate $0$, + $1$, or $-1$. She + then constructs her zero-crossing stream as follows: + + + +(define (make-zero-crossings input-stream last-value) + (cons-stream + (sign-change-detector (stream-car input-stream) last-value) + (make-zero-crossings (stream-cdr input-stream) + (stream-car input-stream)))) + +(define zero-crossings (make-zero-crossings sense-data 0)) + + +function make_zero_crossings(input_stream, last_value) { + return pair(sign_change_detector(head(input_stream), last_value), + () => make_zero_crossings(stream_tail(input_stream), + head(input_stream))); +} +const zero_crossings = make_zero_crossings(sense_data, 0); + + + + Alyssas boss, Eva Lu Ator, walks by and suggests that this program is + approximately equivalent to the following one, which uses + + + the generalized version of + stream-map + from exercise: + + + the function + stream_map_2 + from exercise: + + + + +(define zero-crossings + (stream-map sign-change-detector sense-data expression)) + + +const zero_crossings = stream_map_2(sign_change_detector, + sense_data, + expression); + + + Complete the program by supplying the indicated + expression. + + + + + Unfortunately, Alyssas + signal processingzero crossings of a signal + zero crossings of a signal + signal processingsmoothing a signal + smoothing a signal + zero-crossing detector in + exercise proves to be insufficient, + because the noisy signal from the sensor leads to spurious zero crossings. + Lem E. Tweakit, a hardware specialist, suggests that Alyssa smooth the + signal to filter out the noise before extracting the zero crossings. + Alyssa takes his advice and decides to extract the zero crossings from + the signal constructed by averaging each value of the sense data with + the previous value. She explains the problem to her assistant, Louis + Reasoner, who attempts to implement the idea, altering Alyssas + program as follows: + + +(define (make-zero-crossings input-stream last-value) + (let ((avpt (/ (+ (stream-car input-stream) last-value) 2))) + (cons-stream (sign-change-detector avpt last-value) + (make-zero-crossings (stream-cdr input-stream) + avpt)))) + + +function make_zero_crossings(input_stream, last_value) { + const avpt = (head(input_stream) + last_value) / 2; + return pair(sign_change_detector(avpt, last_value), + () => make_zero_crossings(stream_tail(input_stream), + avpt)); +} + + + This does not correctly implement Alyssas plan. + Find the bug that Louis has installed + and fix it without changing the structure of the program. (Hint: You + will need to increase the number of arguments to + + make-zero-crossings.) + + make_zero_crossings.) + + + + + + + + +
+
+ An RC circuit and the associated + signal-flow diagram + signal-flow diagram. + + +
+
+ + + Eva Lu Ator has a criticism of Louiss approach in + exercise. + signal processingzero crossings of a signal + zero crossings of a signal + signal processingsmoothing a signal + smoothing a signal + The program he wrote is + not modular, because it intermixes the operation of smoothing with the + zero-crossing extraction. For example, the extractor should not have + to be changed if Alyssa finds a better way to condition her input + signal. Help Louis by writing a + + procedure + function + + smooth that takes a stream as input and + produces a stream in which each element is the average of two successive + input stream elements. Then use smooth as a + component to implement the zero-crossing detector in a more modular style. + + + + signal processingstream model of + infinite stream(s)to model signals + +
diff --git a/xml/cn/chapter3/section5/subsection4.xml b/xml/cn/chapter3/section5/subsection4.xml new file mode 100644 index 000000000..1bd136815 --- /dev/null +++ b/xml/cn/chapter3/section5/subsection4.xml @@ -0,0 +1,881 @@ + + + Streams and Delayed Evaluation + + + + + stream(s)delayed evaluation and + delayed evaluationstreams and + + + + The integral + + procedure + function + + at the end of the preceding section shows how we can use streams to model + signal-processing systems that contain + feedback loop, modeled with streams + feedback loops. The feedback loop for the adder shown in + figure is modeled by the fact that + integralneed for delayed evaluation + integrals + internal stream + + + int + + + integ + + + is defined in terms of itself: + + + +(define int + (cons-stream initial-value + (add-streams (scale-stream integrand dt) + int))) + + +const integ = pair(initial_value, + () => add_streams(scale_stream(integrand, dt), + integ)); + + + + + The interpreters ability to deal with such an implicit definition + depends on the delay that is incorporated + into cons-stream. Without this + delay, the interpreter could not construct + int before evaluating both arguments to + cons-stream, which would require that + int already be defined. + In general, delay is crucial for using + streams to model signal-processing systems that contain loops. Without + 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. This would + outlaw loops. + + + The 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. + Without this delay, the interpreter could not + construct integ before evaluating the call + to add_streams, which would require + that integ already be defined. + In general, such a delay is crucial for using streams to model + signal-processing systems that contain loops. Without 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. This would outlaw loops. + + + + + + + Unfortunately, stream models of systems with loops may require uses of + delay beyond the hidden + delay supplied by + cons-stream. For instance, + figure shows a + signal-processing system for solving the + differential equation + differential equation $dy/dt=f(y)$ where + $f$ is a given function. The figure shows a + mapping component, which applies $f$ to its + input signal, linked in a feedback loop to an integrator in a manner + very similar to that of the analog computer circuits that are actually + used to solve such equations. +
+
+ + An + analog computer + analog computer circuit that solves the equation + $dy/dt = f(y)$. + + +
+
+
+ + + Unfortunately, stream models of systems with loops may require uses of a + delay beyond the stream programming pattern seen so far. For instance, + figure shows a + signal-processing system for solving the + differential equation + differential equation $dy/dt=f(y)$ where + $f$ is a given function. The figure shows a + mapping component, which applies $f$ to its + input signal, linked in a feedback loop to an integrator in a manner + very similar to that of the analog computer circuits that are actually + used to solve such equations. +
+
+ + An + analog computer + analog computer circuit that solves the equation + $dy/dt = f(y)$. + + +
+
+
+
+ + + Assuming we are given an initial value $y_0$ for + $y$, we could try to model this system using the + + procedure + function + + + solve differential equation + +(define (solve f y0 dt) + (define y (integral dy y0 dt)) + (define dy (stream-map f y)) + y) + + +function solve(f, y0, dt) { + const y = integral(dy, y0, dt); + const dy = stream_map(f, y); + return y; +} + + + This + + procedure + function + + does not work, because in the first line of + solve the call to + integral requires that the input + dy be defined, which does not happen until the + second line of solve. + + + + On the other hand, the intent of our definition does make sense, because we + can, in principle, begin to generate the y + stream without knowing dy. + + + Indeed, integral and many other stream + operations have properties similar to those of + cons-stream, in that we can generate part + of the answer given only partial information about the arguments. + + + Indeed, integral and many other stream + operations can generate part of the answer given only partial + information about the arguments. + + + For integral, the first element of the output + stream is the specified initial_value. Thus, + we can generate the first element of the output stream without evaluating + the integrand dy. Once we know the first + element of y, the + + + stream-map + + + stream_map + + + in the second line of solve can begin working + to generate the first element of dy, which will + produce the next element of y, and so on. + + + + To take advantage of this idea, we will redefine + integral to expect the integrand stream to be a + delayed argument + argument(s)delayed + delayed expressionexplicit + delayed argument. + + + Integral will + force + + + The function integral will force + + + the integrand to be evaluated only when it is required to generate more than + the first element of the output stream: + + integral_example_4 + +function numbers_starting_from(t, dt) { + return pair(t, + () => numbers_starting_from(t + dt, dt) + ); +} +const dt = 0.01; +const linear = numbers_starting_from(0, dt); +const linear_integral = integral(() => linear, 0, dt); +// computing integral from 0 to 3 of f(x) = x +// (the integral is g(x) = 0.5 x^2, and therefore +// the result is near 0.5 * 3^2 = 4.5) +stream_ref(linear_integral, math_round(3 / dt)); + + + + integraldelayedwith delayed argument + integral + add_streams + scale_stream + integral_example_4 + 4.484999999999992 + +(define (integral delayed-integrand initial-value dt) + (define int + (cons-stream initial-value + (let ((integrand (force delayed-integrand))) + (add-streams (scale-stream integrand dt) + int)))) + int) + + +function integral(delayed_integrand, initial_value, dt) { + const integ = + pair(initial_value, + () => { + const integrand = delayed_integrand(); + return add_streams(scale_stream(integrand, dt), + integ); + }); + return integ; +} + + + Now we can implement our solve + + procedure + function + + by delaying the evaluation of dy in the + + + definition of + + + declaration of + + + y:This + procedure is not guaranteed to work in all Scheme implementations, although + for any implementation there is a simple variation that will work. The + problem has to do with subtle differences in the ways that Scheme + implementations handle internal definitions. (See + section.) + + + solve differential equation + solve + e + +(define (solve f y0 dt) + (define y (integral (delay dy) y0 dt)) + (define dy (stream-map f y)) + y) + + +function solve(f, y0, dt) { + const y = integral(() => dy, y0, dt); + const dy = stream_map(f, y); + return y; +} + + + + + In general, every caller of integral must now + + delay + delay + + the integrand argument. We can demonstrate that the + solve + + procedure + function + + works by approximating + e$e$as solution to differential equation + $e\approx 2.718$ by computing the value at + $y=1$ of the solution to the differential + equation $dy/dt=y$ with initial condition + $y(0)=1$: + To complete in reasonable time, this calculation requires the use of the + memoization optimization from section in + integral and in + the function add_streams used in + integral + (using the function stream_map_2_optimized + as suggested in + exercise). + + + solve_optimized + +function memo(fun) { + let already_run = false; + let result = undefined; + return () => { + if (!already_run) { + result = fun(); + already_run = true; + return result; + } else { + return result; + } + }; +} +// note the use of the memoization optimization +function stream_combine(f, s1, s2) { + return is_null(s1) && is_null(s2) + ? null + : is_null(s1) || is_null(s2) + ? error(null, "unexpected argument -- stream_combine") + : pair(f(head(s1), head(s2)), + memo(() => stream_combine(f, stream_tail(s1), + stream_tail(s2)))); +} +function add_streams(s1, s2) { + return stream_combine((x1, x2) => x1 + x2, s1, s2); +} +function scale_stream(stream, factor) { + return stream_map(x => x * factor, + stream); +} +// note the use of the memoization optimization +function integral(delayed_integrand, initial_value, dt) { + const integ = + pair(initial_value, + memo(() => { + const integrand = delayed_integrand(); + return add_streams(scale_stream(integrand, dt), + integ); + })); + return integ; +} +function solve(f, y0, dt) { + const y = integral(() => dy, y0, dt); + const dy = stream_map(f, y); + return y; +} + + + + e + solve_optimized + 2.716923932235896 + +(stream-ref (solve (lambda (y) y) 1 0.001) 1000) + + +2.716924 + + +stream_ref(solve(y => y, 1, 0.001), 1000); + + +2.716923932235896 + + + + + + The integral + + procedure + function + + used above was analogous to the implicit definition of the + infinite stream of integers in + section. Alternatively, we can + give a definition of integral that is more + like integers-starting-from (also in + section): + + integral + +(define (integral integrand initial-value dt) + (cons-stream initial-value + (if (stream-null? integrand) + the-empty-stream + (integral (stream-cdr integrand) + (+ (* dt (stream-car integrand)) + initial-value) + dt)))) + + +function integral(integrand, initial_value, dt) { + return pair(initial_value, + is_null(integrand) + ? null + : integral(stream_tail(integrand), + dt * head(integrand) + initial_value, + dt)); +} + + + When used in systems with loops, this + + procedure + function + + has the same problem + as does our original version of integral. + Modify the + + procedure + function + + so that it expects the integrand as a + delayed argument and hence can be used in the + solve + + procedure + function + + shown above. + + + + + + Consider the problem of designing a signal-processing system to study + the homogeneous + differential equationsecond-order + second-order linear differential equation + + \[\begin{array}{lll} + \dfrac {d^{2} y}{dt^{2}}-a\dfrac{dy}{dt}-by &=& 0 + \end{array}\] + + The output stream, modeling $y$, is generated by + a network that contains a loop. This is because the value of + $d^{2}y/dt^{2}$ depends upon the values of + $y$ and $dy/dt$ and + both of these are determined by integrating + $d^{2}y/dt^{2}$. The diagram we would like to + encode is shown in figure. Write a + + procedure solve-2nd + function solve_2nd + + + that takes as arguments the constants $a$, + $b$, and $dt$ and the + initial values $y_{0}$ and + $dy_{0}$ for $y$ and + $dy/dt$ and generates the stream of successive + values of $y$. +
+
+ Signal-flow diagram for the solution to a second-order linear + differential equation. + + +
+
+ + + Generalize the + differential equationsecond-order + + solve-2nd procedure + solve_2nd function + + + of exercise so that it can be used to + solve general second-order differential equations + $d^{2} y/dt^{2}=f(dy/dt,\, y)$. + + + +
+
+ A series RLC circuit. + + +
+ + + A series RLC circuit + RLC circuit + circuitmodeled with streams + electrical circuits, modeled with streams + consists of a resistor, a capacitor, and an + inductor connected in series, as shown in + figure. If + $R$, $L$, and + $C$ are the resistance, inductance, and + capacitance, then the relations between voltage + ($v$) and current + ($i$) for the three components are described + by the equations + + \[\begin{array}{lll} + v_{R} &=& i_{R} R\\[9pt] + v_{L} &=& L\dfrac{di_{L}}{dt}\\[11pt] + i_{C} &=& C\dfrac{dv_{C}}{dt} + \end{array}\] + + and the circuit connections dictate the relations + + \[\begin{array}{lll} + i_{R} &=& i_{L}=-i_{C}\\[3pt] + v_{C} &=& v_{L}+v_{R} + \end{array}\] + + Combining these equations shows that the state of the circuit (summarized by + $v_{C}$, the voltage across the capacitor, and + $i_{L}$, the current in the inductor) is + described by the pair of differential equations + + \[\begin{array}{lll} + \dfrac{dv_{C}}{dt} &=& -\dfrac{i_{L}}{C}\\[11pt] + \dfrac {di_{L}}{dt} &=& \dfrac{1}{L}v_{C}-\dfrac{R}{L}i_{L} + \end{array}\] + + The signal-flow diagram representing this system of differential equations + is shown in figure. + + + +
+
+ A signal-flow diagram for the solution to a series RLC circuit. + + +
+
+ + Write a + + procedure + function + + RLC that takes as arguments the parameters + $R$, $L$, and + $C$ of the circuit and the time increment + $dt$. In a manner similar to that of the + RC + + procedure + function + + of exercise, + RLC should produce a + + procedure + function + + that takes the initial values of the state variables, + $v_{C_{0}}$ and + $i_{L_{0}}$, and produces a pair + + (using cons) + (using pair) + + of the streams of states $v_{C}$ and + $i_{L}$. Using RLC, + generate the pair of streams that models the behavior of a series RLC + circuit with $R = 1$ ohm, + $C= 0.2$ farad, + $L = 1$ henry, + $dt = 0.1$ second, and initial values + $i_{L_{0}} = 0$ amps and + $v_{C_{0}} = 10$ volts. + +
+ + stream(s)delayed evaluation and + delayed evaluationstreams and + + + + Normal-order evaluation + + + normal-order evaluationdelayed evaluation and + delayed evaluationnormal-order evaluation and + + + The examples in this section illustrate how + + the explicit use of + delay and force + + delayed evaluation + + provides great programming flexibility, but the same examples also show how + this can make our programs more complex. Our new + integral + + procedure, + function, + + for instance, gives us the power to model systems with loops, but we must + now remember that integral should be called + with a delayed integrand, and every + + procedure + function + + that uses integral must be aware of this. + In effect, we have created two classes of + + procedures: + functions: + + ordinary + + procedures + functions + + and + + procedures + functions + + that take delayed arguments. In general, creating separate classes of + + procedures + functions + + forces us to create separate classes of higher-order + + procedures + functions + + as well.This is a small reflection, in + + Lisp, + JavaScript, + + of the difficulties that + + + higher-order proceduresstrong typing and + data typesin strongly typed languages + Pascal, lack of higher-order procedures + strongly typed language + programming languagestrongly typed + conventional strongly + + + higher-order functionsstatic typing and + data typesin statically typed languages + Pascal, lack of higher-order functions in + statically typed language + programming languagestatically typed + early statically + + + typed languages such as Pascal + + have + had + + in coping with higher-order + + procedures. + functions. + + In + + such + these + + languages, the programmer + + must + had to + + specify the data types of the + arguments and the result of each + + procedure: + function: + + number, logical value, sequence, and so on. Consequently, we could not + express an abstraction such as map a given + + procedure + function + + + proc + fun + + over all the elements in a sequence by a single higher-order + + procedure + function + + such as + + stream-map. + stream_map. + + Rather, we would need a different mapping + + procedure + function + + for each different combination of argument and result data types that might + be specified for a + + proc. + fun. + + Maintaining a practical notion of data type in the presence + of higher-order + + procedures + functions + + raises many difficult issues. One way of dealing with this problem is + illustrated by the language + ML + ML + Gordon, Michael + Milner, Robin + Wadsworth, Christopher + (Gordon, Milner, and Wadsworth 1979), + whose + + polymorphic data types + parametrically polymorphic data types + + include templates for + higher-order transformations between data types. Moreover, data types for + most + + procedures + functions + + in ML are never explicitly declared by the programmer. Instead, ML + includes a + type-inferencing mechanism + type-inferencing mechanism that uses information in the environment + to deduce the data types for newly defined + + procedures. + functions. + + + + Today, statically typed programming languages have evolved to + typically support some form of type inference as well as + parametric polymorphism, with varying degrees of + power. + Haskell + type(s)polymorphic + polymorphic types + Haskell couples an expressive type system with powerful + type inference. + + + + + + + One way to avoid the need for two different classes of + + procedures + functions + + is to make all + + procedures + functions + + take delayed arguments. We could adopt a model of evaluation in which all + arguments to + + procedures + functions + + are automatically delayed and arguments are forced only when they are + actually needed (for example, when they are required by a primitive + operation). This would transform our language to use normal-order + evaluation, which we first described when we introduced the substitution + model for evaluation in section. + Converting to normal-order evaluation provides a uniform and elegant way to + simplify the use of delayed evaluation, and this would be a natural strategy + to adopt if we were concerned only with stream processing. In + section, after we have studied the + evaluator, we will see how to transform our language in just this way. + Unfortunately, including delays in + + procedure + function + + calls wreaks havoc with our ability to design programs that depend on the + order of events, such as programs that use assignment, mutate data, or + perform input or output. + + + Even the single delay in + cons-stream can cause great confusion, as + illustrated by exercises + and. + + + Even a single delay in the tail of a pair can cause great confusion, as + illustrated by exercises + and. + + + As far as anyone knows, mutability and delayed evaluation do not mix well + in programming + + + languages, and devising ways to deal with both of these at + once is an active area of research. + + + languages. + + + + normal-order evaluationdelayed evaluation and + delayed evaluationnormal-order evaluation and + + + + +
+
+ A signal-flow diagram for the solution to a series RLC circuit. + + +
+
+ + +
+
diff --git a/xml/cn/chapter3/section5/subsection5.xml b/xml/cn/chapter3/section5/subsection5.xml new file mode 100644 index 000000000..bdca77cf7 --- /dev/null +++ b/xml/cn/chapter3/section5/subsection5.xml @@ -0,0 +1,601 @@ + + + Modularity of Functional Programs and Modularity of Objects + + + + + modularityfunctional programs vs.objects + functional programming + + + As we saw in section, one of + the major benefits of introducing assignment is that we can increase the + modularity of our systems by encapsulating, or hiding, parts + of the state of a large system within local variables. Stream models can + provide an equivalent modularity without the use of assignment. As an + illustration, we can reimplement the + pi$\pi$ (pi)Dirichlet estimate for + Monte Carlo simulationstream formulation + Monte Carlo estimation + of$\pi$, which we examined in + section, from a + stream-processing point of view. + + + + The key modularity issue was that we wished to hide the internal state + of a random-number generator from programs that used random numbers. + We began with a + + procedure rand-update, + function rand_update, + + + whose successive values furnished our supply of random numbers, and used + this to produce a random-number generator: + + rand_update + random_init + rand_example + 40083849805 + +(define rand + (let ((x random-init)) + (lambda () + (set! x (rand-update x)) + x))) + + +function make_rand() { + let x = random_init; + return () => { + x = rand_update(x); + return x; + }; +} +const rand = make_rand(); + + + + + + + In the stream formulation there is no random-number generator per + se, just a stream of random numbers produced by successive calls to + + rand-update: + + rand_update: + + + + random_numbers (infinite stream) + infinite stream(s)of random numbers + random_numbers + rand_update + random_init + random_numbers_example + 172561279022 + +(define random-numbers + (cons-stream random-init + (stream-map rand-update random-numbers))) + + +const random_numbers = + pair(random_init, + () => stream_map(rand_update, random_numbers)); + + + + random_numbers_example + +eval_stream(random_numbers, 5); + + +stream_ref(random_numbers, 4); + + + We use this to construct the stream of outcomes of the Cesro + experiment performed on consecutive pairs in the + + + random-numbers + + + random_numbers + + + stream: + + dirichlet_stream + map_successive_pairs + cesaro_stream + random_numbers + gcd_definition + cesaro_stream_example + true + +(define cesaro-stream + (map-successive-pairs (lambda (r1 r2) (= (gcd r1 r2) 1)) + random-numbers)) + +(define (map-successive-pairs f s) + (cons-stream + (f (stream-car s) (stream-car (stream-cdr s))) + (map-successive-pairs f (stream-cdr (stream-cdr s))))) + + +function map_successive_pairs(f, s) { + return pair(f(head(s), head(stream_tail(s))), + () => map_successive_pairs( + f, + stream_tail(stream_tail(s)))); +} +const dirichlet_stream = + map_successive_pairs((r1, r2) => gcd(r1, r2) === 1, + random_numbers); + + + + cesaro_stream_example + +eval_stream(dirichlet_stream, 20); + + +stream_ref(dirichlet_stream, 42); + + + The + + cesaro-stream + dirichlet_stream + + is now fed to a + + monte-carlo + monte_carlo + + + procedure, + function, + + which produces a stream of estimates of probabilities. The results are then + converted into a stream of estimates of $\pi$. + This version of the program doesnt need a parameter telling how many + trials to perform. Better estimates of $\pi$ + (from performing more experiments) are obtained by looking farther into the + pi stream: + + monte_carloinfinite stream + monte_carlo_stream + cesaro_stream + display_pi + 3.1780497164141406 + +(define (monte-carlo experiment-stream passed failed) + (define (next passed failed) + (cons-stream + (/ passed (+ passed failed)) + (monte-carlo + (stream-cdr experiment-stream) passed failed))) + (if (stream-car experiment-stream) + (next (+ passed 1) failed) + (next passed (+ failed 1)))) + +(define pi + (stream-map + (lambda (p) (sqrt (/ 6 p))) + (monte-carlo cesaro-stream 0 0))) + + +function monte_carlo(experiment_stream, passed, failed) { + function next(passed, failed) { + return pair(passed / (passed + failed), + () => monte_carlo(stream_tail(experiment_stream), + passed, failed)); + } + return head(experiment_stream) + ? next(passed + 1, failed) + : next(passed, failed + 1); +} +const pi = stream_map(p => math_sqrt(6 / p), + monte_carlo(dirichlet_stream, 0, 0)); + + + + display_pi + +stream_ref(pi, 100); + + + There is considerable + modularitythrough infinite streams + modularity in this approach, because we still + can formulate a general + + monte-carlo procedure + monte_carlo function + + + that can deal with arbitrary experiments. Yet there is no assignment or + local state. + + + + + Exercise discussed generalizing + the random-number generator to allow one to + random-number generatorwith reset, stream version + reset the random-number sequence + so as to produce repeatable sequences of random numbers. + Produce a stream formulation of this same generator that operates on an + input stream of requests to + + + generate + + + "generate" + + + a new + random number or to + + + reset + + + "reset" + + + the sequence to a + specified value and that produces the desired stream of random numbers. + Dont use assignment in your solution. + + + + + Redo exercise on + Monte Carlo integrationstream formulation + pi$\pi$ (pi)approximation with Monte Carlo integration + definite integralestimated with Monte Carlo simulation + Monte Carlo integration in terms of streams. The stream version of + + + estimate-integral + + + estimate_integral + + + will not have an argument telling how many trials to perform. Instead, it + will produce a stream of estimates based on successively more trials. + + + + + A functional-programming view of time + + + timefunctional programming and + functional programmingtime and + + + Let us now return to the issues of objects and state that were raised + at the beginning of this chapter and examine them in a new light. We + introduced assignment and mutable objects to provide a mechanism for + modular construction of programs that model systems with state. + We constructed computational objects with local state variables and used + assignment to modify these variables. We modeled the temporal behavior of + the objects in the world by the temporal behavior of the corresponding + computational objects. + + + + Now we have seen that streams provide an alternative way to model + objects with local state. We can model a changing quantity, such as + the local state of some object, using a stream that represents the + time history of successive states. In essence, we represent time + explicitly, using streams, so that we decouple time in our simulated + world from the sequence of events that take place during evaluation. + Indeed, because of the presence of + + + delay + + + delayed evaluation + + + there may be little relation between simulated time in the model and the + order of events during the evaluation. + + + + In order to contrast these two approaches to modeling, let us + reconsider the implementation of a withdrawal processor that + monitors the balance in a + bank accountstream model + bank account. In + section we implemented a + simplified version of such a processor: + + make_simplified_withdraw + make_simplified_withdraw_example + make_simplified_withdraw_example1 + make_simplified_withdraw_example2 + +(define (make-simplified-withdraw balance) + (lambda (amount) + (set! balance (- balance amount)) + balance)) + + +function make_simplified_withdraw(balance) { + return amount => { + balance = balance - amount; + return balance; + }; +} + + + Calls to + + + make-simplified-withdraw + + + make_simplified_withdraw + + + produce computational objects, each with a local state variable + balance that is decremented by successive calls + to the object. The object takes an amount as + an argument and returns the new balance. We can imagine the user of a bank + account typing a sequence of inputs to such an object and observing the + sequence of returned values shown on a display screen. + + + + Alternatively, we can model a withdrawal processor as a + + procedure + function + + that takes as input a balance and a stream of amounts to withdraw and + produces the stream of successive balances in the account: + + stream_withdraw + stream_withdraw + stream_withdraw_example + 50 + +(define (stream-withdraw balance amount-stream) + (cons-stream + balance + (stream-withdraw (- balance (stream-car amount-stream)) + (stream-cdr amount-stream)))) + + +function stream_withdraw(balance, amount_stream) { + return pair(balance, + () => stream_withdraw(balance - head(amount_stream), + stream_tail(amount_stream))); +} + + + + stream_withdraw_example + +const my_amounts = list_to_stream(list(50, 100, 40)); +const my_account_stream = stream_withdraw(200, my_amounts); +eval_stream(my_account_stream, 3); + + +const my_amounts = list_to_stream(list(50, 100, 40)); +const my_account_stream = stream_withdraw(200, my_amounts); +stream_ref(my_account_stream, 2); + + + + + Stream-withdraw + + + The function stream_withdraw + + + implements a well-defined mathematical function whose output is fully + determined by its input. Suppose, however, that the input + + + amount-stream + + + amount_stream + + + is the stream of successive values typed by the user and that the resulting + stream of balances is displayed. Then, from the perspective of the user who + is typing values and watching results, the stream process has the same + behavior as the object created by + + make-simplified-withdraw. + make_simplified_withdraw. + + + However, with the stream version, there is no assignment, no local state + variable, and consequently none of the theoretical difficulties that we + encountered + statevanishes in stream formulation + in section. Yet the system + has state! + + + + This is really remarkable. Even though + + + stream-withdraw + + + stream_withdraw + + + implements a well-defined mathematical function whose behavior does not + change, the users perception here is one of interacting with a system + that has a changing state. One way to resolve this paradox is to realize + that it is the users temporal existence that imposes state on the + system. If the user could step back from the interaction and think in terms + of streams of balances rather than individual transactions, the system + would appear stateless.Similarly in physics, when we observe a + moving particle, we say that the position (state) of the particle is + changing. However, from the perspective of the particles + world line of a particle + world line in space-time there is no change involved. + + + + From the point of view of one part of a complex process, the other parts + appear to change with time. They have hidden time-varying local state. If + we wish to write programs that model this kind of natural decomposition in + our world (as we see it from our viewpoint as a part of that world) with + structures in our computer, we make computational objects that are not + functionalthey must change with time. We model state with local + state variables, and we model the changes of state with assignments to + those variables. By doing this we make the time of execution of a + computation model time in the world that we are part of, and thus we + get objects in our computer. + + + + Modeling with objects is powerful and intuitive, largely because this + matches the perception of interacting with a world of which we are + part. However, as weve seen repeatedly throughout this chapter, + these models raise thorny problems of constraining the order of events + and of synchronizing multiple processes. The possibility of avoiding + these problems has stimulated the development of + programming languagefunctional + functional programmingfunctional programming languages + functional programming languages, which do not include any + provision for assignment or mutable data. In such a language, all + + procedures + functions + + implement well-defined mathematical functions of their arguments, + whose behavior does not change. The functional approach is extremely + attractive for dealing with + concurrencyfunctional programming and + functional programmingconcurrency and + concurrent systems.John Backus, the + Fortraninventor of + inventor of Fortran, gave high + visibility to functional programming when he was awarded the ACM Turing + award in 1978. His acceptance speech + Backus, John + (Backus 1978) + strongly advocated the functional approach. A good overview of functional + programming is given in + Henderson, Peter + Henderson 1980 and in + Darlington, John + Turner, David + Darlington, Henderson, and Turner 1982. + + + + On the other hand, if we look closely, we can see time-related problems + creeping into functional models as well. One particularly troublesome area + arises when we wish to design interactive systems, especially ones that + model interactions between independent entities. For instance, consider once + more the implementation of a banking system that permits joint bank accounts. + In a conventional system using assignment and objects, we would model the + fact that Peter and Paul share an account by having both Peter and Paul send + their transaction requests to the same bank-account object, as we saw in + section. From the stream point + of view, where there are no objects per se, we have + already indicated that a bank account can be modeled as a process that + operates on a stream of transaction requests to produce a stream of + responses. Accordingly, we could model the fact that Peter and Paul have a + joint bank account by merging Peters stream of transaction requests + with Pauls stream of requests and feeding the result to the + bank-account stream process, as shown in + figure. +
+
+ + A joint + bank accountjoint, modeled with streams + bank account, modeled by merging two streams of transaction + requests. + + +
+
+ + + The trouble with this formulation is in the notion of merge. It + will not do to merge the two streams by simply taking alternately one + request from Peter and one request from Paul. Suppose Paul accesses + the account only very rarely. We could hardly force Peter to wait for + Paul to access the account before he could issue a second transaction. + infinite stream(s)merging + However such a merge is implemented, it must interleave the two + transaction streams in some way that is constrained by real + time as perceived by Peter and Paul, in the sense that, if Peter and + Paul meet, they can agree that certain transactions were processed + before the meeting, and other transactions were processed after the + meeting.Observe that, for any two streams, there is in general + more than one + acceptable order of interleaving. Thus, technically, merge + is a + nondeterminism, in behavior of concurrent programs + infinite stream(s)merging as a relation + relation rather than a functionthe answer is not a + deterministic function of the inputs. We already mentioned + (footnote) that nondeterminism + is essential when dealing with concurrency. The merge relation illustrates + the same essential nondeterminism, from the functional perspective. + In section, we + will look at nondeterminism from yet another point of view. + This is precisely the same constraint that we had to deal with in + section, where we found the need to + introduce explicit synchronization to ensure a correct order + of events in concurrent processing of objects with state. Thus, in an + attempt to support the functional style, the need to merge inputs from + different agents reintroduces the same problems that the functional style + was meant to eliminate. + + + + We began this chapter with the goal of building computational models + whose structure matches our perception of the real world we are trying + to model. We can model the world as a collection of separate, + time-bound, interacting objects with state, or we can model the world + as a single, timeless, stateless unity. Each view has powerful + advantages, but neither view alone is completely satisfactory. A + grand unification has yet to emerge.The object model approximates + the world by dividing it into separate pieces. The functional model does + not + modularityalong object boundaries + modularize along object boundaries. The object model is useful when + the unshared state of the objects is much larger than the + state that they share. An example of a place where the object viewpoint + fails is + quantum mechanics + quantum mechanics, where thinking of things as individual particles leads + to paradoxes and confusions. Unifying the object view with the + functional view may have little to do with programming, but rather + with fundamental epistemological issues. + + stream(s) + modularityfunctional programs vs.objects + functional programming + timefunctional programming and + functional programmingtime and + + +
diff --git a/xml/cn/chapter4/chapter4.xml b/xml/cn/chapter4/chapter4.xml new file mode 100644 index 000000000..81b3d3178 --- /dev/null +++ b/xml/cn/chapter4/chapter4.xml @@ -0,0 +1,330 @@ + + Metalinguistic Abstraction + + + % ** \label{ex:env-program-abstraction} should be at end of ex, not start? + % 2/13/98 FIX TYPO: Extra period at end of ex 4.3 + % winter '97 Add tex '\label's for manual to reference. + % 8/26/97 FIX TYPESETTING ERROR FOR THIRD PRINTING - - p.393 + % FIX misplaced index command on p.416. (This edit won't have + % direct effect now, as will edit index by hand.) + + % 4/15 'fix' a a pagebreak not wrapped in pagekludge + % 4/15 fix multiple entries that landed on same page + % 4/13-15 Indexing [after final printout of chapters] + % 4/12 Pagination adjustments from proofreading + % 4/11 Correction based on spell-checking + % 4/10 trivial spacing changes based on comparison to before-indexing + % 4/7-... Julie index fixes (and 4/8 change 'ns') + % 4/5/96 Hal indexing + % 4/4/96 paginated rest of chapter + % 3/29/96 Julie: indexing fixes + % 3/19/96 paginate the beginning! + % 3/16/96 Minor edits + % 3/14/96 (acually 3/15 00:xx) - start indexing + % 3/9/96 fix spacing in some exercise parts + % 3/5/96 fix typesetting/spacing + % 3/4/96 reword to 'fix' most triple hyphenation + % 2/26-3/3/96 fix some bad line breaks + % 2/24/96 flush \noindent after {lisp} by closing up space with + % 2/24/96 fix a reference (caught by editor) + % 2/22/96 new spec for epigraph + % 2/19/96 fixed () in a reference + % 2/19/96 fixed et al. + + + + + Its in words that the magic + isAbracadabra, Open Sesame, and the restbut the magic + words in one story arent magical in the next. The real magic is to + understand which words work, and when, and for what; the trick is to learn + the trick. +

+ And those words are made from the letters of our + alphabet: a couple-dozen squiggles we can draw with the pen. This is + the key! And the treasure, too, if we can only get our hands on it! + Its as ifas if the key to the treasure is + the treasure! + Barth, John + + John Barth<EM>Chimera</EM> + +
+ + + + + + 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.The same idea + is pervasive throughout all of engineering. For example, electrical + engineers use many different languages for describing circuits. Two + of these are the language of electrical networks and the + language of electrical systems. The network language emphasizes + the physical modeling of devices in terms of discrete electrical + elements. The primitive objects of the network language are primitive + electrical components such as resistors, capacitors, inductors, and + transistors, which are characterized in terms of physical variables + called voltage and current. When describing circuits in the network + language, the engineer is concerned with the physical characteristics + of a design. In contrast, the primitive objects of the system + language are signal-processing modules such as filters and amplifiers. + Only the functional behavior of the modules is relevant, and signals + are manipulated without concern for their physical realization as + voltages and currents. The system language is erected on the network + language, in the sense that the elements of signal-processing systems + are constructed from electrical networks. Here, however, the concerns + are with the large-scale organization of electrical devices to solve a + given application problem; the physical feasibility of the parts is + assumed. This layered collection of languages is another example of + the stratified design technique illustrated by the picture + language of section. + + + + Programming is endowed with a multitude of languages. There are + physical languages, such as the + high-level language, machine language vs. + machine languagehigh-level language vs. + machine languages for particular + computers. These languages are concerned with the representation of + data and control in terms of individual bits of storage and primitive + machine instructions. The machine-language programmer is concerned + with using the given hardware to erect systems and utilities for the + efficient implementation of resource-limited computations. High-level + languages, erected on a machine-language substrate, hide concerns + about the representation of data as collections of bits and the + representation of programs as sequences of primitive instructions. + These languages have means of combination and abstraction, such as + + procedure definition, + function declaration, + + that are appropriate to the larger-scale organization of systems. + + + + Metalinguistic abstractionestablishing + metalinguistic abstraction + abstractionmetalinguistic + new languagesplays 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 + 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 + computer science + computer science itself becomes no more (and no less) than the discipline + of constructing appropriate descriptive languages. + + + + + 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. + + + + Lispsuitability for writing evaluators + Lisp is particularly well suited to this task, because of its ability + to represent and manipulate symbolic expressions. + + + We will take the first step in understanding how languages are implemented + by building an evaluator for + + Lisp + 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.The most important features that our + evaluator leaves out are mechanisms for handling errors and supporting + debugging. For a more extensive discussion of evaluators, see + Friedman, Daniel P. + Wand, Mitchell + Haynes, Christopher T. + Friedman, Wand, and Haynes + 1992, which gives an exposition of programming languages that proceeds + via a sequence of evaluators written in + Schemeevaluators written in + + Scheme. + the Scheme dialect of Lisp. + + + + + 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, well 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. + + + + + + &section4.1; + + + &section4.2; + + + &section4.3; + + + &section4.4; + +
diff --git a/xml/cn/chapter4/section1/section1.xml b/xml/cn/chapter4/section1/section1.xml new file mode 100644 index 000000000..ef8ec9fd3 --- /dev/null +++ b/xml/cn/chapter4/section1/section1.xml @@ -0,0 +1,414 @@ +
+ The Metacircular Evaluator + + + + + + metacircular evaluator for JavaScript + + + Our evaluator for + + Lisp + JavaScript + + will be implemented as a + + Lisp + JavaScript + + program. It may + seem circular to think about evaluating + + Lisp + JavaScript + + programs using an evaluator that is itself implemented in + + Lisp. + JavaScript. + + However, evaluation is a process, so it is appropriate to describe the + evaluation process using + + Lisp, + JavaScript, + + which, after all, is our tool for describing processes.Even so, + there will remain important aspects of the evaluation process that are not + elucidated by our evaluator. The most important of these are the detailed + mechanisms by which + + procedures + functions + + call other + + procedures + functions + + and return values to their callers. We will address these issues in + chapter, where we take a closer look at the evaluation process by + implementing the evaluator as a simple register machine. + An evaluator that is written in the same language + that it evaluates is said to be + metacircular evaluator + evaluatormetacircular + metacircular. + + + + The metacircular evaluator is essentially a + + Scheme + JavaScript + + formulation of the + environment model of evaluationmetacircular evaluator and + metacircular evaluator for JavaScriptenvironment model of evaluation in + environment model of evaluation described in + section. + + Recall that the model has two basic parts: + + Recall that the model specifies the + evaluation of function application in two basic steps: + + + + + +
    +
  1. + + + To evaluate a combination (a compound expression other than a + special form), evaluate the subexpressions and then apply the value + of the operator subexpression to the values of the operand + subexpressions. + + + To evaluate a function application, evaluate the + subexpressions and then apply the + value of the function subexpression to the values of the argument + subexpressions. + + +
  2. +
  3. + To apply a compound + + procedure + function + + to a set of arguments, evaluate the + body of the + + procedure + function + + in a new environment. To construct this + environment, extend the environment part of the + + procedure + function + + object by a + frame in which the + + formal + + parameters of the + + procedure + function + + are bound to + the arguments to which the + + procedure + function + + is applied. +
  4. +
+
+ + + These two rules describe the essence of the evaluation process, a basic + + + metacircular evaluator for Schemeevalevalapply cycle + + + metacircular evaluator for JavaScriptevaluateevaluateapply cycle + + + cycle in which + statements and + expressions to be evaluated in environments are reduced to + + procedures + functions + + to be applied to arguments, which in turn are reduced to new + + statements and + + expressions to be evaluated in new environments, and so on, until we get + down to + + + symbols, + + + names, + + + whose values are looked up in the environment, and to + + primitive procedures, + operators and primitive functions, + + which are applied directly (see + figure).If we grant ourselves + the ability to apply primitives, + then what remains for us to implement in the evaluator? The + metacircular evaluator for JavaScriptjob of + job of the + evaluator is not to specify the primitives of the language, but rather to + provide the connective tissuethe means of combination and the means + of abstractionthat binds a collection of primitives to form a + language. Specifically: +
    +
  • + The evaluator enables us to deal with nested expressions. For example, + although simply applying primitives would suffice for evaluating the + expression + + (+ 1 6), + 2 * 6, + + it is not adequate for handling + + (+ 1 (* 2 3)). + 2 * (1 + 5). + + + As far as the + + + primitive procedure + + + + + operator + * + + + is concerned, its arguments must be numbers, and it would choke if we + passed it the expression + + (* 2 3) + 1 + 5 + + as an argument. One important role of the evaluator is to choreograph + + procedure + + + composition so that + + (* 2 3) + 1 + 5 + + is reduced to 6 before being passed as an + + + argument to+. + + + argument to*. + + +
  • +
  • + The evaluator allows us to use + + variables. + names. + + For example, the + + primitive procedure for addition + addition operator + + has no way to deal with expressions such as + + (+ x 1). + x + 1. + + We need an evaluator to keep track of + + variables + names + + and obtain their values before invoking the + + primitive procedures. + operators. + +
  • +
  • + The evaluator allows us to define compound + + procedures. + functions. + + + + This involves keeping track of procedure definitions, knowing + how to use these definitions in evaluating + expressions, and providing a mechanism that enables + procedures to accept arguments. + + + This involves knowing how to use these functions in evaluating + expressions and providing a mechanism that enables functions + to accept arguments. + + +
  • +
  • + + The evaluator provides the special forms, which must be + evaluated differently from procedure calls. + The evaluator provides the other syntactic forms of + the language such as conditionals and blocks. + +
  • +
+ This evaluation cycle will be embodied by the interplay between the two + critical + + procedures + functions + + in the evaluator, + + eval + evaluate + + and apply, which are described in + section + (see figure). +
+ + + + + The implementation of the evaluator will depend upon procedures + that define the syntax of the expressions to be evaluated. + We will use + metacircular evaluator for Schemedata abstraction in + data abstraction to make the evaluator independent of the representation of + the language. For example, rather than committing to a choice that an + assignment is to be represented by a list beginning with the + symbol set! we use an abstract predicate + assignment? + to test for an assignment, and we use abstract selectors + assignment-variable and + assignment-value + to access the parts of an assignment. + Implementation of expressions will be described in detail in + section. + + + The implementation of the evaluator will depend upon functions that + define the syntax of the statements and + expressions to be evaluated. + We will use + metacircular evaluator for JavaScriptdata abstraction in + data abstraction to make the evaluator independent of the + representation of the language. + For 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. + The 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. + + + There are also + operations, described in + section, that specify the + representation of + + procedures + functions + + and environments. For example, + + make-procedure + make_function + + + constructs compound + + procedures, + functions, + + + lookup-variable-value + lookup_symbol_value + + + accesses the values of + + variables, + names, + + and + + apply-primitive-procedure + apply_primitive_function + + + applies a primitive + + procedure + function + to a given list of arguments. + + + + &subsection4.1.1; + + + &subsection4.1.2; + + + &subsection4.1.3; + + + &subsection4.1.4; + + + &subsection4.1.5; + + + &subsection4.1.6; + + + &subsection4.1.7; + +
diff --git a/xml/cn/chapter4/section1/subsection1.xml b/xml/cn/chapter4/section1/subsection1.xml new file mode 100644 index 000000000..f31b64eca --- /dev/null +++ b/xml/cn/chapter4/section1/subsection1.xml @@ -0,0 +1,1467 @@ + + + The Core of the Evaluator + + + + + + + + metacircular evaluator for Schemeevaleval and apply + +
+
+ + + The + evalapply + cycle exposes the essence of a computer language. + +
+
+ +
+
+ + + The + metacircular evaluator for JavaScriptevaluateevaluateapply cycle + evaluateapply + cycle exposes the essence of a computer language. + +
+
+
+ The evaluation + metacircular evaluator for JavaScriptevaluateevaluate and apply + process can be described as the interplay between two + + procedures: + functions: + + + eval + evaluate + + + and apply. +
+ + + + Eval + The function + evaluate + + + + + + In JavaScript's strict modeour preferred way to run JavaScript + programsthe name eval cannot + be declared in user programs. It is reserved for a related but quite + different pre-declared function. We opt for the complete word + evaluate as a replacement. + + + + + + Eval + The function evaluate + + + takes as arguments + + + a program componenta statement or + expressionThere is no + need to distinguish between statements and expressions in our evaluator. + For example, we do not differentiate between expressions and + expression statements; we represent them identically and consequently + they are handled in the same way by the + evaluate + function. Similarly, our evaluator does not enforce JavaScript's syntactic + restriction that statements + cannot appear inside expressions other than + lambda expressions.and an environment. + + + and an environment. + + + It classifies the + + expression + component + + and directs its evaluation. + + Eval + The function evaluate + + + is structured as a case analysis of the syntactic type of the + + expression + component + + to be evaluated. In order to keep the + procedure + function + general, we express + the determination of the type of + an expression + a component + abstractly, making no + commitment to any particular + metacircular evaluator for JavaScriptcomponent representation + representation for the various types of + expressions. + components. + Each type of + expression + component + has a + + predicate + syntax predicate + + that tests for it and an abstract means for selecting its parts. This + metacircular evaluator for JavaScriptdata abstraction in + abstract syntaxin metacircular evaluator + abstract syntax + makes it easy to see how we can change the syntax of the language by + using the same evaluator, but with a different collection of syntax + + + procedures. + + + functions. + + + + + + Primitive expressions + + + + + + +
    +
  • + + + For + expressionself-evaluating + self-evaluating expression + self-evaluating expressions, such as numbers, + eval + returns the expression itself. + + + For + expressionliteral + literal expression + literal expressions, such as numbers, + evaluate + returns their value. + + +
  • +
  • + + Eval + The function + evaluate + + must look up + + variables + names + + in the environment to find their values. +
  • +
+ + + Special forms + + +
    +
  • + For quoted expressions, + eval + returns the expression that was + quoted. +
  • +
  • + An assignment to (or a definition of) a variable + must recursively call + eval + to compute the new value to be associated with the variable. + The environment must be modified to change (or create) the + binding of the variable. +
  • +
  • + An if expression requires special processing of its parts, so as to + evaluate the consequent if the predicate is true, and otherwise to + evaluate the alternative. +
  • +
  • + A lambda expression + must be transformed into an applicable + procedure by packaging together the parameters and body + specified by the + lambda + expression with the environment of the evaluation. +
  • +
  • + A begin expression + requires evaluating its sequence of + expressions in the order in which they appear. +
  • +
  • + A case analysis (cond) is transformed + into a nest of if + expressions and then evaluated. +
  • +
+ + + Combinations + + +
    +
  • + For a procedure application, eval + must recursively evaluate the operator part and the operands of + the combination. The resulting procedure + and arguments are passed to apply, + which handles the actual procedure application. +
  • +
+ Here is the definition of eval: + + evaluate (metacircular) + eval_scheme + +(define (eval exp env) + (cond ((self-evaluating? exp) exp) + ((variable? exp) (lookup-variable-value exp env)) + ((quoted? exp) (text-of-quotation exp)) + ((assignment? exp) (eval-assignment exp env)) + ((definition? exp) (eval-definition exp env)) + ((if? exp) (eval-if exp env)) + ((lambda? exp) + (make-procedure (lambda-parameters exp) + (lambda-body exp) + env)) + ((begin? exp) + (eval-sequence (begin-actions exp) env)) + ((cond? exp) (eval (cond->if exp) env)) + ((application? exp) + (apply (eval (operator exp) env) + (list-of-values (operands exp) env))) + (else + (error "Unknown expression type - - EVAL" exp)))) + + + +
+ + + +
    +
  • + For + expressionliteral + literal expression + literal expressions, such as numbers, + evaluate + returns their value. +
  • +
  • + The function + evaluate + must look up names in the environment to find their values. +
  • +
+ + + + Combinations + + +
    +
  • + For a function application, + evaluate must recursively + evaluate the function expression and the argument expressions of the + application. The resulting function and arguments are passed to + apply, which handles the actual + function application. +
  • +
  • + An operator combination is transformed into a function application + and then evaluated. +
  • +
+ + + Syntactic 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. +
  • +
  • + A 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. +
  • +
  • + A sequence of statements requires evaluating its + components in the order in which they appear. +
  • +
  • + A block requires evaluating its body in a new environment + that reflects all names declared within the block. +
  • +
  • + A return statement must produce a value that becomes the + result of the function call that gave rise to the + evaluation of the return statement. +
  • +
  • + A function declaration is transformed into a + constant declaration and then evaluated. +
  • +
  • + A 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. + The environment must be + modified to reflect the new value of the name. +
  • +
+ + Here is the declaration of + evaluate: + + headline_4_1_1 + +// functions from SICP JS 4.1.1 + + + + evaluate (metacircular) + 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); + + + +
+
+
+ + + For clarity, + + eval + evaluate + + has been implemented as a + data-directed programmingcase analysis vs. + case analysisdata-directed programming vs. + case analysis using + cond. + conditional expressions. + The disadvantage of this is that our + + procedure + function + + handles only a few distinguishable types of + + statements and + + expressions, and no new ones can be defined without editing the + + + definition of + eval. + + + declaration of + evaluate. + + + In most + + Lisp + interpreter + + implementations, dispatching on the type of + + an expression + a component + + is done in a data-directed style. This allows a user to add new types of + + expressions that eval + components that + evaluate + + + can distinguish, without modifying the + + + definition of eval + + + declaration of + evaluate + + + itself. (See exercise.) + + + The representation of names is handled by the syntax abstractions. Internally, + the evaluator uses strings to represent names, and we refer to such strings as + symbol(s)in representingrepresenting names in metacircular evaluator + symbols. The function + symbol_of_name used in + evaluate extracts from a + name the symbol by which it is represented. + + + + + Apply + + + + + Apply + The function apply + + + takes two arguments, a + + procedure + function + + and a list of arguments to which the + + procedure + function + + should be applied. + + Apply + The function apply + + + classifies + + procedures + functions + + into two kinds: It calls + apply_primitive_function + + + apply-primitive-procedure + + + apply_primitive_function + + + to apply primitives; it applies compound + + procedures + functions + + + + by sequentially evaluating the + expressions that make up the body of the procedure. + + + by evaluating the block that makes up the body + of the function. + + + The environment for the evaluation of the body of a compound + + procedure + function + + is constructed by extending the base environment carried by the + + procedure + function + + to include a frame that binds the parameters of the + + procedure + function + + to the arguments to which the + + procedure + function + + is to be applied. + Here is the + + + definition + + + declaration + + + of apply: + + apply (metacircular) + apply + apply_example + 3 + +(define (apply procedure arguments) + (cond ((primitive-procedure? procedure) + (apply-primitive-procedure procedure arguments)) + ((compound-procedure? procedure) + (eval-sequence + (procedure-body procedure) + (extend-environment + (procedure-parameters procedure) + arguments + (procedure-environment procedure)))) + (else + (error + "Unknown procedure type - - APPLY" procedure)))) + + +function apply(fun, args) { + if (is_primitive_function(fun)) { + return apply_primitive_function(fun, args); + } else if (is_compound_function(fun)) { + const result = evaluate(function_body(fun), + extend_environment( + function_parameters(fun), + args, + function_environment(fun))); + return is_return_value(result) + ? return_value_content(result) + : undefined; + } else { + error(fun, "unknown function type -- apply"); + } +} + + + + The name arguments + is reserved in JavaScript's strict mode. We chose + args instead. + + + apply_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const plus = list("primitive", (x, y) => x + y); +apply(plus, list(1, 2)); + + + + + + In order to return a value, a JavaScript function needs to evaluate a + metacircular evaluator for JavaScriptreturn value + return valuerepresentation in metacircular evaluator + return statement. If a function terminates without evaluating a return + statement, the value + return valueundefined as + undefined is returned. + To distinguish the two cases, the evaluation of a return statement + will wrap the result of evaluating its return expression into a + return value. If + 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. + + This test is a deferred operation, and thus our evaluator will give rise to a + recursive process even if the interpreted program should give rise to an + iterative process according to the description in + section. In other + words, our metacircular evaluator implementation of JavaScript is + apply (metacircular)tail recursion and + metacircular evaluator for JavaScripttail recursion and + tail recursionmetacircular evaluator and + not tail-recursive. + Sections + and + show how to achieve tail recursion using a register machine. + + + + + + + + + Procedure + Function + + arguments + + + + + + When + + eval + evaluate + + processes a + + procedure + function + + application, it uses + + list-of-values + list_of_values + + + to produce the list of arguments to which the + + procedure + function + + is to be applied. + + List-of-values + The function + list_of_values + + + takes as an argument the + + operands of the combination. + argument expressions of the application. + + It evaluates each + + operand + argument expression + + and returns a + list of the corresponding values:We could + have simplified the + application? + clause in + eval + by using + map (and stipulating that + operands + returns a list) rather than writing + an explicit + list-of-values procedure. + We chose not to use map here to emphasize the + fact that the + metacircular evaluator for Schemehigher-order procedures in + higher-order proceduresin metacircular evaluator + evaluator can be implemented without any use of higher-order + procedures + (and thus could be written in a language that doesnt + have higher-order + procedures), + even though the language that it supports + will include higher-order + procedures. + We + chose to implement list_of_values + using the + metacircular evaluator for JavaScripthigher-order functions in + higher-order functionsmetacircularin metacircular evaluator + higher-order function map, and we will use + map in other places as well. + However, the evaluator can be implemented without any use of higher-order + functions (and thus could be written in a language that doesn't have + higher-order functions), even though the language that it supports + will include higher-order functions. For example, + list_of_values can be + written without map as follows: + + list_of_valueshigherorderwithout higher-order functions + list_of_values_without_map + list_of_values_example + +function list_of_values(exps, env) { + return is_null(exps) + ? null + : pair(evaluate(head(exps), env), + list_of_values(tail(exps), env)); +} + + + + + list_of_values + list_of_values + list_of_values_example + +(define (list-of-values exps env) + (if (no-operands? exps) + '() + (cons (eval (first-operand exps) env) + (list-of-values (rest-operands exps) env)))) + + +function list_of_values(exps, env) { + return map(arg => evaluate(arg, env), exps); +} + + + + list_of_values_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +(define the-global-environment (setup-environment)) +(eval (read) the-global-environment) + + +const my_addition_expression = parse("1 + 2;"); +list_of_values(list(parse("1;"), my_addition_expression, parse("7;")), + the_global_environment); + + + + + + + Conditionals + + + + + + Eval-if + The function + eval_conditional + + + evaluates the predicate part of + + an if expression + a conditional component + + in the given environment. If the result is true, + + eval-if + evaluates the consequent, otherwise it evaluates the alternative: + + + the consequent is evaluated, otherwise the alternative is evaluated: + + + + eval_conditional (metacircular) + eval_if + eval_if_example + 1 + +(define (eval-if exp env) + (if (true? (eval (if-predicate exp) env)) + (eval (if-consequent exp) env) + (eval (if-alternative exp) env))) + + +function eval_conditional(component, env) { + return is_truthy(evaluate(conditional_predicate(component), env)) + ? evaluate(conditional_consequent(component), env) + : evaluate(conditional_alternative(component), env); +} + + + + eval_if_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_cond_expr = parse("true ? 1 : 2;"); +eval_conditional(my_cond_expr, the_empty_environment); + + + + + Note that the evaluator does not need to distinguish between + conditional expressions and conditional statements. + + + + + + The use of + is_truthy + truthiness + + true? + is_truthy + + in + + eval-if + + eval_conditional + + + metacircular evaluator for JavaScriptimplemented language vs.implementation language + highlights the issue of the connection between an implemented language and + an implementation language. The + + if-predicate + conditional_predicate + + is evaluated in the language being implemented and thus yields a value in + that language. The interpreter predicate + + true? + is_truthy + + translates that value into a value that can be tested by the + if + conditional expression + in the implementation language: The metacircular representation of truth + might not be the same as that of the underlying + SchemeJavaScript.In this case, the language being implemented and the + implementation language are the same. Contemplation of the meaning of + true? + is_truthy + + here yields + consciousness, expansion of + expansion of consciousness without the abuse of + substance. + + + + + Sequences + + + + + + + + Eval-sequence is used by + apply to evaluate the sequence of + expressions in a procedure body and by eval + to evaluate the sequence of expressions in a + begin expression. It takes as arguments a + sequence of expressions and an environment, and evaluates the + expressions in the order in which they occur. The value returned is the + value of the final expression. + + eval_sequence + +(define (eval-sequence exps env) + (cond ((last-exp? exps) (eval (first-exp exps) env)) + (else (eval (first-exp exps) env) + (eval-sequence (rest-exps exps) env)))) + + + + + + + The function eval_sequence + is used by evaluate + to evaluate a sequence of statements at the top level or in a block. + It takes as arguments a sequence of statements and an + environment, and evaluates the statements in the order in which they + occur. The value returned is the value of the final statement, except + that if the evaluation of any statement in the sequence yields + a return value, that value is returned and the subsequent statements are + ignored.The treatment of return statements in + eval_sequence + reflects the proper + result of evaluating function applications in JavaScript, but the + evaluator presented here does not comply with the ECMAScript + specification for the value of a program that consists + of a sequence of statements outside of any function body. + Exercise addresses this issue. + + + + eval_sequence + eval_sequence + eval_sequence_example + 3 + +function eval_sequence(stmts, env) { + if (is_empty_sequence(stmts)) { + return undefined; + } else if (is_last_statement(stmts)) { + return evaluate(first_statement(stmts), env); + } else { + const first_stmt_value = + evaluate(first_statement(stmts), env); + if (is_return_value(first_stmt_value)) { + return first_stmt_value; + } else { + return eval_sequence(rest_statements(stmts), env); + } + } +} + + + + eval_sequence_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_sequence = head(tail(parse("1; true; 3;"))); +eval_sequence(my_sequence, the_empty_environment); + + + + + + + + + + + + Blocks + + + + The function eval_block handles + blocks. The variables and constants (including functions) + declared in the block have the whole block as their scope and thus + are scanned out before the body of the block is + evaluated. + The body of the block is evaluated with respect to an environment + that extends the current + environment by a frame that binds each local name + to a special value, + "*unassigned*". + This string serves as a placeholder, before + the evaluation of the declaration assigns the name + its proper value. An attempt to access the value of the name before its + declaration is evaluated leads to an error at run time (see + exercise), as stated in + footnote in chapter. + + eval_block + eval_block + scan_out_declarations + eval_block_example + 42 + +function eval_block(component, env) { + const body = block_body(component); + const locals = scan_out_declarations(body); + const unassigneds = list_of_unassigned(locals); + return evaluate(body, extend_environment(locals, + unassigneds, + env)); +} +function list_of_unassigned(symbols) { + return map(symbol => "*unassigned*", symbols); +} + + + + + list_of_unassigned + +function list_of_unassigned(symbols) { + return map(symbol => "*unassigned*", symbols); +} + + + The function scan_out_declarations + scanning out declarationsin metacircular evaluator + collects a list of all symbols representing names declared in the body. + It uses + declaration_symbol + to retrieve the symbol that represents the name + from the declaration statements it finds. + + scan_out_declarations + scan_out_declarations + scan_out_declarations_example + [ 'x', [ 'y', null ] ] + +function scan_out_declarations(component) { + return is_sequence(component) + ? accumulate(append, + null, + map(scan_out_declarations, + sequence_statements(component))) + : is_declaration(component) + ? list(declaration_symbol(component)) + : null; +} + + + + scan_out_declarations_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +scan_out_declarations(parse("const x = 1; let y = 2;")); + + + + eval_block_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_block = parse("{ const x = 1; 3; 42; }"); +eval_block(my_block, the_empty_environment); + + + We ignore declarations that are nested in another block, + because the evaluation of that block will take care of them. + The function scan_out_declarations + looks for declarations only in sequences because + declarations in conditional statements, function declarations, and + lambda expressions are always in a nested block. + + + + Return statements + + + + The function eval_return_statement + is used to evaluate + return statementhandling in metacircular evaluator + return statements. As seen in + apply and + the evaluation + of sequences, the result of evaluation of a return statement + needs to be identifiable so that the evaluation of a function + body can return immediately, even if there are statements + after the return statement. For this purpose, + the evaluation of a return statement wraps the result of + evaluating the return expression in a return value object. + metacircular evaluator for JavaScripttail recursion and + tail recursionmetacircular evaluator and + The application of the function + make_return_value to the result + of evaluating the return expression creates a deferred operation, in + addition to the deferred operation created by + apply. See + footnote for details. + + eval_return_statement + eval_return + eval_return_example + 1 + +function eval_return_statement(component, env) { + return make_return_value(evaluate(return_expression(component), + env)); +} + + + + eval_return_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_program = parse("{ function f() { return 1; } f(); }"); +evaluate(my_program, the_global_environment); + + + + + + + + Assignments and + + definitions + declarations + + + + + The + + following procedure + + function eval_assignment + + + handles assignments to + + variables. + + names. + (To simplify the presentation of our evaluator, + we are allowing assignment not just to variables but + alsoerroneouslyto constants. + Exercise + explains how we could + distinguish constants from variables and prevent + assignment to constants.) + + + + + It calls + eval + to find the value to be assigned and transmits the variable + and the resulting value to + set-variable-value! + to be installed in the designated environment. + + + The function + eval_assignment + calls + evaluate + 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. The function + eval_assignment + transmits the symbol and the value to + assign_symbol_value + to be installed in the designated environment. + The evaluation of an assignment returns the value + that was assigned. + + + + eval_assignment + eval_assignment + eval_assignment_example + 2 + +(define (eval-assignment exp env) + (set-variable-value! (assignment-variable exp) + (eval (assignment-value exp) env) + env) + 'ok) + + +function eval_assignment(component, env) { + const value = evaluate(assignment_value_expression(component), + env); + assign_symbol_value(assignment_symbol(component), value, env); + return value; +} + + + + eval_assignment_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_program = parse("{ let x = 1; x = 2; }"); +evaluate(my_program, the_global_environment); + + + + + + + + Definitions of variables are handled in a similar manner.This + implementation of define ignores a subtle + issue in the handling of internal definitions, although it works + correctly in most cases. We will see what the problem is and how to + solve it in + section. + + eval_definition + define_variable + definition + eval + +(define (eval-definition exp env) + (define-variable! (definition-variable exp) + (eval (definition-value exp) env) + env) + 'ok) + + + + + + We have chosen here to return the symbol ok + as the value of an assignment or a definition.As we said when + we introduced define and + set!, these values + are implementation-dependent in Schemethat is, the implementor + can choose what value to return. + metacircular evaluator for Schemeevaleval and apply + + + + + Constant and variable declarations are both recognized by the + is_declaration syntax predicate. + They are treated in a manner similar to + assignments, because eval_block + has already bound their symbols to "*unassigned*" + in the current environment. + Their evaluation replaces "*unassigned*" + with the result of evaluating the value expression. + + eval_declaration + eval_definition + eval_definition_example + 3 + +function eval_declaration(component, env) { + assign_symbol_value( + declaration_symbol(component), + evaluate(declaration_value_expression(component), env), + env); + return undefined; +} + + + The result of evaluating the body of a function is determined by + return statements, and therefore the return value + undefined in + eval_declaration only + matters when the declaration occurs at the top level, + outside of any function body. Here we use the return value + undefined to simplify + the presentation; exercise + describes the real result of evaluating top-level components + in JavaScript. + + + + + eval_definition_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_program = parse("{ let x = 1; const y = 2; x + y; }"); +evaluate(my_program, the_global_environment); + + + + + metacircular evaluator for JavaScriptevaluateevaluate and apply + + + + + functions_4_1_1 + headline_4_1_1 + eval + apply + list_of_values + eval_if + eval_sequence + eval_block + eval_return + eval_assignment + eval_definition + + + + + + + Notice that we cannot tell whether the metacircular evaluator + order of evaluationmetacircularin metacircular evaluator + metacircular evaluator for Schemeorder of operand evaluation + evaluates operands from left to right or from right to left. Its + evaluation order is inherited from the underlying Lisp: If the + arguments to cons in + list-of-values are evaluated from left to + right, then list-of-values will evaluate + operands from left to right; and if the arguments to + cons are evaluated from right to left, then + list-of-values will evaluate operands + from right to left. +

+ Write a version of list-of-values that + evaluates operands from left to right regardless of the + order of evaluation in the underlying Lisp. Also write a version of + list-of-values that evaluates + operands from right to left. + +
+
+ + + Notice that we cannot tell whether the metacircular evaluator + order of evaluationmetacircularin metacircular evaluator + metacircular evaluator for JavaScriptorder of argument evaluation + evaluates argument expressions from left to right or from right to left. + Its evaluation order is inherited from the underlying JavaScript: + If the arguments to pair in + map are evaluated from + left to right, then list_of_values + will evaluate argument expressions from left to right; and if the + arguments to pair are + evaluated from right to left, then + list_of_values will evaluate + argument expressions from right to left. +

+ Write a version of list_of_values + that evaluates argument expressions from left to right regardless of the + order of evaluation in the underlying JavaScript. Also write a version of + list_of_values that evaluates + argument expressions from right to left. + +
+
+
+ + + + + parse_and_evaluate + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +function parse_and_evaluate(input) { + const program = parse(input); + const implicit_top_level_block = make_block(program); + return evaluate(implicit_top_level_block, + the_global_environment); +} + + + + + parse_and_evaluate_test_factorial + parse_and_evaluate + 120 + +parse_and_evaluate(" \ +function factorial(n) { \ + return n === 1 \ + ? 1 \ + : n * factorial(n - 1); \ +} \ +factorial(5); "); + + + + parse_and_evaluate_test_append + parse_and_evaluate + [ 'b', [ 'c', [ 'd', null ] ] ] + +parse_and_evaluate(" \ +function append(xs, ys) { \ + return is_null(xs) \ + ? ys \ + : pair(head(xs), append(tail(xs), ys)); \ +} \ +tail(append(list('a', 'b'), list('c', 'd'))); "); + + + + parse_and_evaluate_test_map + parse_and_evaluate + [ 3, [ 4, [ 5, null ] ] ] + +parse_and_evaluate(" \ +function map(f, xs) { \ + return is_null(xs) \ + ? null \ + : pair(f(head(xs)), map(f, tail(xs))); \ +} \ +tail(map(x => x + 1, list(1, 2, 3, 4))); "); + + +
+ diff --git a/xml/cn/chapter4/section1/subsection2.xml b/xml/cn/chapter4/section1/subsection2.xml new file mode 100644 index 000000000..91cd4aacc --- /dev/null +++ b/xml/cn/chapter4/section1/subsection2.xml @@ -0,0 +1,2735 @@ + + + Representing + + Expressions + Components + + + + + + metacircular evaluator for JavaScriptcomponent representation + metacircular evaluator for JavaScriptsyntax of evaluated language + parsing JavaScript + + + + + + Programmers write programs as text, i.e. sequences of characters, entered + in a programming environment or a text editor. To run our evaluator, we need + to start with a representation of this program text as a JavaScript value. + In section we introduced strings to represent + text. We would like to evaluate programs such as + "const size = 2; 5 * size;" + from section. + Unfortunately, such program text does not provide enough structure to + the evaluator. In this example, the program parts + "size = 2" and + "5 * size" look similar, but carry + very different meanings. Abstract syntax functions such as + declaration_@value_@expression would be + difficult + and error-prone to implement by examining the program text. + In this section, we therefore + introduce a function + parse + parse that translates program text + to a tagged-list representation, reminiscent of + the tagged data of section. + For example, the application of + parse to the program string + above produces a data structure that + reflects the structure of the program: a sequence consisting + of a constant declaration associating the name + size with the value 2 + and a multiplication. + + parse_declaration + +parse("const size = 2; 5 * size;"); + + +list("sequence", + list(list("constant_declaration", + list("name", "size"), list("literal", 2)), + list("binary_operator_combination", "*", + list("literal", 5), list("name", "size")))) + + + The syntax functions used by the evaluator access the tagged-list + representation produced by + parse. + + + + + The evaluator is reminiscent of the + metacircular evaluator for JavaScriptsymbolic differentiation and + symbolic differentiation program + discussed in section. + Both programs operate on symbolic + + expressions. + data. + + In both programs, the + result of operating on + + a compound expression + an object + + is determined by + operating recursively on the pieces of the + + expression + object + + and combining + the results in a way that depends on the type of the + + expression. + object. + + In both programs we used + data abstraction + data abstraction to decouple the general rules + of operation from the details of how + + expressions + the objects + + are represented. In + the differentiation program this meant that the same differentiation + procedure + function + could deal with algebraic expressions in prefix form, in + infix form, or in some other form. For the evaluator, this means that + + + the syntax of the language being evaluated is determined solely by the + procedures that classify and extract pieces of expressions. + + + the 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. + + + + + + + +
+
+ Syntax abstraction in the evaluator. + +
+ Figure depicts the + abstraction barriersin representing JavaScript syntax + abstraction barrier + formed by the syntax predicates and selectors, + which interface the evaluator to the tagged-list representation of programs, + which in turn is separated from the string representation by + parse. Below we + describe the parsing of program components and list the + corresponding syntax predicates and selectors, as well as + constructors if they are needed. +
+
+
+ + + + + Here is the specification of the syntax of our language: +
    +
  • + The only self-evaluating items are numbers and + strings: + + is_literal + +(define (self-evaluating? exp) + (cond ((number? exp) true) + ((string? exp) true) + ((null? exp) true) + (else false))) + + +
  • + +
  • + Variables are represented by symbols: + + variable + +(define (variable? exp) (symbol? exp)) + + +
  • + +
  • + Quotations have the form + (quote + text-of-quotation) + :As mentioned in + section, the evaluator sees a + quoted expression as a list beginning with + quote, even if the expression is typed + with the quotation mark. For example, the expression + 'a would be seen by the evaluator as + (quote a). + See exercise. + + quoted + +(define (quoted? exp) + (tagged-list? exp 'quote)) + +(define (text-of-quotation exp) (cadr exp)) + + + Quoted? is defined in terms of the + procedure tagged-list?, which + identifies lists beginning with a designated symbol: + + tagged_list + +(define (tagged-list? exp tag) + (if (pair? exp) + (eq? (car exp) tag) + false)) + + +
  • + +
  • + Assignments have the form + (set! + var value): + + assignment + +(define (assignment? exp) + (tagged-list? exp 'set!)) + +(define (assignment-variable exp) (cadr exp)) + +(define (assignment-value exp) (caddr exp)) + + +
  • + +
  • + Definitions have the form + + +(define var value) + + + or the form + + +(define (var parameter$_{1}$ $\ldots$ parameter$_{n}$) + body) + + + The latter form (standard + procedure definition) is + syntactic sugardefinedefine + definesyntactic sugar + syntactic sugar for + + +(define var + (lambda (parameter$_{1}$ $\ldots$ parameter$_{n}$) + body)) + + + The corresponding syntax + procedures + are the following: + + definition_scheme + +(define (definition? exp) + (tagged-list? exp 'define)) + +(define (definition-variable exp) + (if (symbol? (cadr exp)) + (cadr exp) + (caadr exp))) + +(define (definition-value exp) + (if (symbol? (cadr exp)) + (caddr exp) + (make-lambda (cdadr exp) + (cddr exp)))) + + +
  • + +
  • + Lambda expressions are lists that begin + with the symbol lambda: + + lambda + +(define (lambda? exp) (tagged-list? exp 'lambda)) + +(define (lambda-parameters exp) (cadr exp)) +(define (lambda-body exp) (cddr exp)) + +(define (make-lambda parameters body) + (cons 'lambda (cons parameters body))) + + + We also provide a constructor for + lambda expressions, which is used by + definition-value, above: + + make-lambda + +(define (make-lambda parameters body) + (cons 'lambda (cons parameters body))) + + + +
  • + +
  • + Conditionals begin with if and have a + predicate, a consequent, and an (optional) alternative. If the + expression has no alternative part, we provide + false as the alternative.The + value of an if expression when the + predicate is false and there is no alternative is unspecified in + Scheme; we have chosen here to make it false. We will support the + use of the variables true and + false in expressions to be evaluated + by binding them in the global environment. See + section. + + + + if + +(define (if? exp) (tagged-list? exp 'if)) + +(define (if-predicate exp) (cadr exp)) + +(define (if-consequent exp) (caddr exp)) + +(define (if-alternative exp) + (if (not (null? (cdddr exp))) + (cadddr exp) + 'false)) + +(define (make-if predicate consequent alternative) + (list 'if predicate consequent alternative)) + + + We also provide a constructor for if expressions, + to be used by cond->if to transform cond expressions + into if expressions: + + make-if + +(define (make-if predicate consequent alternative) + (list 'if predicate consequent alternative)) + + +
  • + +
  • + Begin packages a sequence of + expressions into a single expression. We include syntax operations + on begin expressions to extract the + actual sequence from the begin + expression, as well as selectors that return the first expression + and the rest of the expressions in the sequence.These + selectors for a list of expressionsand the corresponding + ones for a list of operandsare not intended as a data + abstraction. They are introduced as mnemonic names for the basic list + operations in order to make it easier to understand the + explicit-control evaluator in + section. + + + begin + +(define (begin? exp) (tagged-list? exp 'begin)) + +(define (begin-actions exp) (cdr exp)) + +(define (last-exp? seq) (null? (cdr seq))) +(define (first-exp seq) (car seq)) +(define (rest-exps seq) (cdr seq)) + + + We also include a constructor + sequence->exp (for use by + cond->if) + that transforms a sequence into a single expression, + using begin if necessary: + + sequence_exp + +(define (sequence->exp seq) + (cond ((null? seq) seq) + ((last-exp? seq) (first-exp seq)) + (else (make-begin seq)))) + +(define (make-begin seq) (cons 'begin seq)) + + +
  • + +
  • + A procedure application is any compound expression that is not one + of the above expression types. The car + of the expression is the operator, and the + cdr is the list of operands: + + application + +(define (application? exp) (pair? exp)) +(define (operator exp) (car exp)) +(define (operands exp) (cdr exp)) + +(define (no-operands? ops) (null? ops)) +(define (first-operand ops) (car ops)) +(define (rest-operands ops) (cdr ops)) + + +
  • +
+
+
+ + + parsing JavaScript + + + + Literal expression + + + + headline_4_1_2 + +// functions from SICP JS 4.1.2 + + + Literal expressions + literal expressionliteral expression + literal expressionparsing of + are parsed into tagged lists with + tag "literal" and + the actual value. + + + \[ + \begin{align*} + \ll\ \mathit{literal}\textit{-}\mathit{expression}\ \gg & = + \texttt{list("literal", }\mathit{value}\texttt{)} + \end{align*} + \] + + where value is + the JavaScript value represented by the + literal-expression string. + Here $\ll\ $literal-expression$\ \gg$ denotes the + result of parsing the string literal-expression. + + + + \begin{Parsing} + \ll\ \mathit{literal}\mhyphen\mathit{expression}\ \gg & = & + \texttt{list("literal", }\mathit{value}\texttt{)} + \end{Parsing}% + + where value is + the JavaScript value represented by the + literal-expression string. + Here $\ll\ \mathit{literal}\mhyphen\mathit{expression}\ \gg$ denotes the + result of parsing the string literal-expression. + + + parse_literal_example + +parse("1;"); + + +list("literal", 1) + + + + parse_literal_example_2 + +parse("'hello world';"); + + +list("literal", "hello world") + + + + parse_literal_example_3 + +parse("null;"); + + +list("literal", null) + + + The syntax predicate for literal expressions is + is_literal. + + is_literal + is_literal + tagged_list + is_literal_example + true + +function is_literal(component) { + return is_tagged_list(component, "literal"); +} + + +function is_literal(component) { + return is_tagged_list(component, "literal"); +} +function literal_value(component) { + return head(tail(component)); +} + + + + is_literal_example + +const my_program = parse("true; 1;"); +const my_true_statement = list_ref(list_ref(my_program, 1), 0); +is_literal(my_true_statement); + + + It is defined in terms of the function + is_tagged_list, + which identifies lists that begin with a designated + string: + + is_tagged_list + tagged_list + tagged_list_example + true + +function is_tagged_list(component, the_tag) { + return is_pair(component) && head(component) === the_tag; +} + + + + tagged_list_example + +is_tagged_list(list("name", "x"), "name"); + + + The second element of the list that results from parsing a literal expression + is its actual JavaScript value. + The selector for retrieving the value is + literal_value. + + literal_value + literal_value + literal_value_example + null + +function literal_value(component) { + return head(tail(component)); +} + + + + literal_value_example + +literal_value(parse("null;")); + + +null + + + In the rest of this section, we just list the syntax predicates and selectors, + and omit their declarations if they just access the obvious list elements. + + + We provide a constructor for literals, which will come in handy: + + make_literal + make_literal + +function make_literal(value) { + return list("literal", value); +} + + + + + + Names + + + + The tagged-list representation for + namename + nameparsing of + symbol(s)in parsing of names + names includes the tag "name" as first + element and the string representing the name as second element. + + + \[ + \begin{align*} + \ll\ \mathit{name}\ \gg & = + \texttt{list("name", }\mathit{symbol}\texttt{)} + \end{align*} + \] + + where symbol is a string + that contains the characters that make up the + name as written in the program. + + + + \begin{Parsing} + \ll\ \mathit{name}\ \gg & = & + \texttt{list("name", }\mathit{symbol}\texttt{)} + \end{Parsing}% + + where symbol is a string + that contains the characters that make up the + name as written in the program. + + The syntax predicate for names is + is_name + is_name. + + variable + variable_example + tagged_list + +function is_name(component) { + return is_tagged_list(component, "name"); +} + + + The symbol is accessed using the selector + symbol_of_name + symbol_of_name. + + symbol_of_name + variable_example + tagged_list + variable + +function symbol_of_name(component) { + return head(tail(component)); +} + + + + variable_example + +const my_name_statement = parse("x;"); +display(is_name(my_name_statement)); +display(symbol_of_name(my_name_statement)); + + + We provide a constructor for names, to be used by + operator_combination_to_application: + + make_name + make_name + variable_example + tagged_list + +function make_name(symbol) { + return list("name", symbol); +} + + + + + + + Expression statements + + + We do not need to distinguish between expressions and + expression statementexpression statement + expression statementparsing of + expression statements. + Consequently, + parse can ignore the difference + between the two kinds of components: + + + \[ + \ll\ \mathit{expression}\texttt{;}\ \gg {=} \ll\ \mathit{expression}\ \gg + \] + + + + + \begin{ParsingNoPostPadding} + \ll\ \mathit{expression}\texttt{;}\ \gg & = & + \ll\ \mathit{expression}\ \gg + \end{ParsingNoPostPadding} + + + + + + + Function applications + + + + Function applications + function applicationfunction application + function applicationparsing of + are parsed as follows: + + + \[ + \ll\ \mathit{fun}\textit{-}\mathit{expr}\texttt{(}\mathit{arg}\textit{-}\mathit{expr}_1\texttt{, }\ldots\texttt{, } \mathit{arg}\textit{-}\mathit{expr}_n \texttt{)}\ \gg \\ + = \\ + \texttt{list("application",} \ll\ \mathit{fun}\textit{-}\mathit{expr}\gg\texttt{, list(} \ll\ \mathit{arg}\textit{-}\mathit{expr}_1\;\gg \texttt{, }\ldots\texttt{, } \ll\ \mathit{arg}\textit{-}\mathit{expr}_n\;\gg \texttt{))} + \] + + + + + +$\ll\ $fun-expr(arg-expr$_1$, $\ldots$, arg-expr$_n$)$\ \gg$ = + list("application", + $\ll\ $fun-expr$\ \gg$, + list($\ll\ $arg-expr$_1\;\gg$, $\ldots$, $\ll\ $arg-expr$_n\;\gg$)) + + + + + + + + + + + We declare + is_application + is_application + as the syntax predicate and + function_expression + arg_expressions + function_expression and + arg_expressions as the selectors. + + application + tagged_list + application_example + +function is_application(component) { + return is_tagged_list(component, "application"); +} +function function_expression(component) { + return head(tail(component)); +} +function arg_expressions(component) { + return head(tail(tail(component))); +} + + + + application_example + +const my_application = parse("math_pow(3, 4);"); +display(is_application(my_application)); +display(function_expression(my_application)); +const my_expressions = arg_expressions(my_application); +display(no_arg_expressions(my_expressions)); +display(first_arg_expression(my_expressions)); +display(rest_arg_expressions(my_expressions)); + + + We add a constructor for function applications, to be used by + operator_combination_to_application: + + make_application + make_application + variable + variable_example + tagged_list + +function make_application(function_expression, argument_expressions) { + return list("application", + function_expression, argument_expressions); +} + + + + + + Conditionals + + + + Conditional expressions + conditional expressionconditional expression + conditional expressionparsing of + conditional statementconditional statement + conditional statementparsing of + are parsed as follows: + +
+ +$\ll\ \mathit{predicate}\ \texttt{?}\ \mathit{consequent}\textit{-}\mathit{expression}\ \texttt{:}\ \mathit{alternative}\textit{-}\mathit{expression}\ \gg\ \ = $ + + + +list("conditional_expression", + $\ll\ $predicate$\ \gg$, + $\ll\ $consequent-expression$\ \gg$, + $\ll\ $alternative-expression$\ \gg$) + + +
+
+ + + + $\ll\ $predicate ? consequent-expression : alternative-expression$\ \gg$ = + list("conditional_expression", + $\ll\ $predicate$\ \gg$, + $\ll\ $consequent-expression$\ \gg$, + $\ll\ $alternative-expression$\ \gg$) + + + + Similarly, conditional statements are parsed as follows: + +
+ +$\ll\ \textbf{if}\ \texttt{(} \mathit{predicate} \texttt{)}\ \mathit{consequent}\textit{-}\mathit{block}\ \textbf{else}\ \mathit{alternative}\textit{-}\mathit{block}\ \gg\ \ =$ + + + +list("conditional_statement", + $\ll\ $predicate$\ \gg$, + $\ll\ $consequent-block$\ \gg$, + $\ll\ $alternative-block$\ \gg$) + + +
+
+ + + + $\ll\ $if (predicate) consequent-block else alternative-block$\ \gg$ = + list("conditional_statement", + $\ll\ $predicate$\ \gg$, + $\ll\ $consequent-block$\ \gg$, + $\ll\ $alternative-block$\ \gg$) + + + + The syntax predicate + is_conditional + is_conditional + returns true for both kinds of conditionals, and the selectors + conditional_predicate + conditional_predicate, + conditional_consequent + conditional_consequent, and + conditional_alternative + conditional_alternative + can be applied to both kinds. + + if + tagged_list + if_example + [ 'literal', [ 2, null ] ] + +function is_conditional(component) { + return is_tagged_list(component, "conditional_expression") || + is_tagged_list(component, "conditional_statement"); +} +function conditional_predicate(component) { + return list_ref(component, 1); +} +function conditional_consequent(component) { + return list_ref(component, 2); +} +function conditional_alternative(component) { + return list_ref(component, 3); +} + + + + if_example + +const my_cond_expr = + parse("true ? 1 : 2;"); +display(is_conditional(my_cond_expr)); +display(conditional_predicate(my_cond_expr)); +display(conditional_consequent(my_cond_expr)); +display(conditional_alternative(my_cond_expr)); + + +const my_cond_expr = + parse("true ? 1 : 2;"); +is_conditional(my_cond_expr); +conditional_predicate(my_cond_expr); +conditional_consequent(my_cond_expr); +conditional_alternative(my_cond_expr); + + +
+ + + Lambda expressions + + + + A lambda expression + lambda expressionlambda expression + lambda expressionparsing of + whose body is an expression is parsed as if the + body consisted of a block containing a single return statement whose + return expression is the body of the lambda expression. + + +\[ + \ll\ \texttt{(}\mathit{name}_1\texttt{, }\ldots\texttt{, } \mathit{name}_n \texttt{) =>}\ \mathit{expression}\ \gg \\ + = \\ + \ll\ \texttt{(}\mathit{name}_1\texttt{, }\ldots\texttt{, } \mathit{name}_n \texttt{) => \{}\ \textbf{return} \ \mathit{expression}\texttt{;}\ \texttt{\}}\ \gg +\] + + + + + +$\ll\ $(name$_1$, $\ldots$, name$_n$) => expression$\ \gg$ = + $\ll\ $(name$_1$, $\ldots$, name$_n$) => { return expression; }$\ \gg$ + + + + + + + + + + A lambda expression whose body is a block is parsed as follows: + +
+ +$\ll\ \texttt{(}\mathit{name}_1\texttt{, }\ldots\texttt{, } \mathit{name}_n \texttt{) =>}\ \mathit{block}\ \gg\ \ =$ + + + +list("lambda_expression", + list($\ll\ name_1\;\gg$, $\ldots$, $\ll\ name_n\;\gg$), + $\ll\ block\ \gg$) + + +
+
+ + + +$\ll\ $(name$_1$, $\ldots$, name$_n$) => block$\ \gg$ = + list("lambda_expression", + list($\ll\ $name$_1\;\gg$, $\ldots$, $\ll\ $name$_n\;\gg$), + $\ll\ $block$\ \gg$) + + + + + + + + + + +The syntax predicate is +is_lambda_expression + is_lambda_expression + and the selector for the body of the lambda expression is + lambda_body + lambda_body. + The selector for the parameters, called + lambda_parameter_symbols, + additionally extracts the symbols from the names. + + lambda_parameter_symbols + lambda + variable + symbol_of_name + tagged_list + lambda_example + +function lambda_parameter_symbols(component) { + return map(symbol_of_name, head(tail(component))); +} + + +function is_lambda_expression(component) { + return is_tagged_list(component, "lambda_expression"); +} +function lambda_parameter_symbols(component) { + return map(symbol_of_name, head(tail(component))); +} +function lambda_body(component) { + return head(tail(tail(component))); +} + + + + lambda_example + +const my_lambda = parse("x => x"); +display(is_lambda_expression(my_lambda)); +display(lambda_parameter_symbols(my_lambda)); +display(lambda_body(my_lambda)); + + + The function + function_decl_to_constant_decl + needs a constructor for lambda expressions: + + make_lambda_expression + make_lambda_expression + variable_example + tagged_list + variable + +function make_lambda_expression(parameters, body) { + return list("lambda_expression", parameters, body); +} + + + +
+ + + Sequences + + + + A sequence statement + sequence of statementssequence of statements + sequence of statementsparsing of + packages a sequence of statements into a + single statement. Asequence of statements is parsed as follows: + + +\[ +\ll\ \mathit{statement}_1 \cdots \mathit{statement}_n\;\gg \\ += \\ +\texttt{list("sequence", list(} +\ll\ \mathit{statement}_1\;\gg \texttt{, } \ldots \texttt{, } +\ll\ \mathit{statement}_n\;\gg \texttt{))} +\] + + + + + +$\ll\ $statement$_1$ $\cdots$ statement$_n\;\gg$ = + list("sequence", list($\ll\ $statement$_1\;\gg$, $\ldots$, $\ll\ $statement$_n\;\gg$)) + + + + + + + + + + + + + The syntax predicate is + is_sequence + is_sequence and + the selector is sequence_statements. + We retrieve the first of a list of statements using + first_statement and + the remaining statements using + rest_statements. We test + whether the list is empty using the predicate + is_empty_sequence and + whether it contains only one element + using the predicate + is_last_statement.These + selectors for a list of statements are not intended + as a data abstraction. + They are introduced as mnemonic names for the + basic list operations in order to make it easier to understand the + explicit-control evaluator in + section. + + + + first_statement + rest_statements + is_empty_sequence + is_last_statement + begin + tagged_list + begin_example + [ 'literal', [ 45, null ] ] + +function first_statement(stmts) { return head(stmts); } + +function rest_statements(stmts) { return tail(stmts); } + +function is_empty_sequence(stmts) { return is_null(stmts); } + +function is_last_statement(stmts) { return is_null(tail(stmts)); } + + +function is_sequence(stmt) { + return is_tagged_list(stmt, "sequence"); +} +function sequence_statements(stmt) { + return head(tail(stmt)); +} +function first_statement(stmts) { + return head(stmts); +} +function rest_statements(stmts) { + return tail(stmts); +} +function is_empty_sequence(stmts) { + return is_null(stmts); +} +function is_last_statement(stmts) { + return is_null(tail(stmts)); +} + + + + begin_example + +const my_sequence = parse("1; true; 45;"); +display(is_sequence(my_sequence)); +const my_actions = sequence_statements(my_sequence); +display(is_empty_sequence(my_actions)); +display(is_last_statement(my_actions)); +display(first_statement(my_actions)); +display(rest_statements(my_actions)); + + +const my_sequence = parse("1; true; 45;"); +is_sequence(my_sequence); +const my_actions = sequence_statements(my_sequence); +is_empty_sequence(my_actions); +is_last_statement(my_actions); +first_statement(my_actions); +rest_statements(my_actions); +list_ref(rest_statements(my_actions), 1); + + + + + + Blocks + + + + Blocks + blockblock + blockparsing of + are parsed as follows:A parser implementation may + decide to represent a block by just its statement sequence if none + of the statements of the sequence are declarations, or to represent + a sequence with only one statement by just that statement. The language + processors in this chapter and in chapter 5 do not depend on + these decisions. + + + \[ + \begin{align*} + \ll\ \texttt{\{}\ \mathit{statements}\ \texttt{\}}\ \gg + & = + \texttt{list("block",}\ \ll\ \mathit{statements}\ \gg \texttt{)} + \end{align*} + \] + + + + + \begin{Parsing} + \ll\ \texttt{\{}\ \mathit{statements}\ \texttt{\}}\ \gg + & = & + \texttt{list("block",}\ \ll\ \mathit{statements}\ \gg \texttt{)} + \end{Parsing}% + + + Here statements refers to a sequence of + statements, as shown above. + The syntax predicate is + is_block + is_block + and the selector is + block_body + block_body. + + block + tagged_list + block_example + +function is_block(component) { + return is_tagged_list(component, "block"); +} +function block_body(component) { + return head(tail(component)); +} + + +function is_block(component) { + return is_tagged_list(component, "block"); +} +function block_body(component) { + return head(tail(component)); +} +function make_block(statement) { + return list("block", statement); +} + + + + block_example + +const my_block = parse("{ 1; true; 45; }"); +display(is_block(my_block)); +display(block_body(my_block)); + + + + + + Return statements + + + + Return statements + returnreturn statement + return statementparsing of + are parsed as follows: + + + \[ + \begin{align*} + \ll\ \textbf{return}\ \mathit{expression} \texttt{;}\ \gg + & = + \texttt{list("return\_statement",}\ \ll\ \mathit{expression}\ \gg \texttt{)} + \end{align*} + \] + + + + + \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 + is_return_statement + and + return_expression + return_expression. + + return + tagged_list + return_example + [ 'name', [ 'x', null ] ] + +function is_return_statement(component) { + return is_tagged_list(component, "return_statement"); +} +function return_expression(component) { + return head(tail(component)); +} + + + + return_example + +const my_function_declaration = parse("function f(x) { return x; }"); +const my_return = list_ref(my_function_declaration, 3); +list_ref(my_return, 1); + + + + + + Assignments + + + + Assignments + assignmentassignment + assignmentparsing of + are parsed as follows: + + + \[ + \begin{align*} + \ll\ \mathit{name}\ \ \texttt{=}\ \ \mathit{expression}\ \gg & = + \texttt{list("assignment", }\ll\ \mathit{name}\gg \texttt{, }\ll\ \mathit{expression}\ \gg \texttt{)} + \end{align*} + \] + + + + + \begin{Parsing} + \ll\;\mathit{name} \ \texttt{=}\ \mathit{expression}\;\gg & = & + \texttt{list("assignment",}\ \ll\;\mathit{name}\;\gg \texttt{, }\ll\;\mathit{expression}\;\gg \texttt{)} + \end{Parsing}% + + + The syntax predicate is + is_assignment + is_assignment + and the selectors are + assignment_symbol + and + assignment_value_expression + assignment_value_expression. + The symbol is wrapped in a tagged list representing the name, and thus + assignment_symbol needs to + unwrap it. + + assignment_symbol + assignment + tagged_list + assignment_example + [ 'literal', [ 1, null ] ] + +function assignment_symbol(component) { + return symbol_of_name(head(tail(component)))); +} + + +function is_assignment(component) { + return is_tagged_list(component, "assignment"); +} +function assignment_symbol(component) { + return head(tail(head(tail(component)))); +} +function assignment_value_expression(component) { + return head(tail(tail(component))); +} + + + + assignment_example + +const my_assignment_statement = parse("x = 1;"); +display(assignment_symbol(my_assignment_statement)); +display(assignment_value_expression(my_assignment_statement)); + + +const my_assignment_statement = parse("x = 1;"); +assignment_symbol(my_assignment_statement); +assignment_value_expression(my_assignment_statement); + + + + + + Constant, variable, and function declarations + + + + Constant and variable declarations + constantconstant declaration + constant declarationparsing of + variablevariable declaration + variabledeclaration, parsing of + are parsed as follows: + + +\[ + \ll\ \textbf{const}\ \mathit{name}\ \ \texttt{=}\ \ \mathit{expression}\texttt{;}\ \gg \\ + = \\ + \texttt{list("constant\_declaration", } \ll\ \mathit{name}\ \gg \texttt{, }\ll\ \mathit{expression}\ \gg \texttt{)}\\[3mm] + \ll\ \textbf{let}\ \mathit{name} \ \ \texttt{=}\ \ \mathit{expression}\texttt{;}\ \gg \\ + = \\ + \texttt{list("variable\_declaration", } \ll\ \mathit{name}\ \gg \texttt{, }\ll\ \mathit{expression}\ \gg \texttt{)} +\] + + + + + +$\ll\ $const name = expression;$\ \gg$ = + list("constant_declaration", $\ll\ $name$\ \gg$, $\ll\ $expression$\ \gg$) + +$\ll\ $let name = expression;$\ \gg$ = + list("variable_declaration", $\ll\ $name$\ \gg$, $\ll\ $expression$\ \gg$) + + + + + + + + + + + + + + The selectors + declaration_symbol and + declaration_value_expression apply to both + kinds. + + declaration_symbol + declaration_value_expression + declaration_symbol + tagged_list + definition_example + +function declaration_symbol(component) { + return symbol_of_name(head(tail(component))); +} +function declaration_value_expression(component) { + return head(tail(tail(component))); +} + + + The function + function_decl_to_constant_decl + needs a constructor for constant declarations: + + make_constant_declaration + make_constant_declaration + tagged_list + definition + definition_example + +function make_constant_declaration(name, value_expression) { + return list("constant_declaration", name, value_expression); +} + + + + + Function declarations + function declarationfunction declaration + function declarationparsing of + are parsed as follows: + +
+ +$\ll\ \textbf{function}\ \mathit{name} \texttt{(}\mathit{name}_1\texttt{, }\ldots\texttt{, } \mathit{name}_n \texttt{)} \ \mathit{block}\ \gg\ \ =$ + + + +list("function_declaration", + $\ll\ $name$\ \gg$, + list($\ll\ $name$_1\;\gg$, $\ldots$, $\ll\ $name$_n\;\gg$), + $\ll\ $block$\ \gg$) + + +
+
+ + + +$\ll\ $function name(name$_1$, $\ldots$ name$_n$) block$\ \gg$ = + list("function_declaration", + $\ll\ $name$\ \gg$, + list($\ll\ $name$_1\;\gg$, $\ldots$, $\ll\ $name$_n\;\gg$), + $\ll\ $block$\ \gg$) + + + + The syntax predicate + is_function_declaration + is_function_declaration + recognizes these. + The selectors are + function_declaration_name + function_declaration_name, + function_declaration_parameters + function_declaration_parameters, and + function_declaration_body + function_declaration_body. + + function_declaration_syntax + tagged_list + +function is_function_declaration(component) { + return is_tagged_list(component, "function_declaration"); +} +function function_declaration_name(component) { + return list_ref(component, 1); +} +function function_declaration_parameters(component) { + return list_ref(component, 2); +} +function function_declaration_body(component) { + return list_ref(component, 3); +} + + +
+ + The syntax predicate + is_declaration + returns true for all three kinds of declarations. + + is_declaration + definition + tagged_list + definition_example + +function is_declaration(component) { + return is_tagged_list(component, "constant_declaration") || + is_tagged_list(component, "variable_declaration") || + is_tagged_list(component, "function_declaration"); +} + + + + definition_example + +const my_declaration_statement = parse("let x = 1;"); +display(is_declaration(my_declaration_statement)); +display(declaration_symbol(my_declaration_statement)); +display(declaration_value_expression(my_declaration_statement)); + + + +
+
+ + + + + + Derived expressions + + + + Some special forms in our language can be defined in terms of + expressions involving + metacircular evaluator for Schemederived expressions + metacircular evaluator for Schemespecial forms as derived expressions + derived expressions in evaluator + special formderivedas derived expression in evaluator + other special forms, rather than being + implemented directly. One example is cond, + which can be implemented as a nest of if + expressions. For example, we can reduce the problem of evaluating the + expression + + +(cond ((> x 0) x) + ((= x 0) (display 'zero) 0) + (else (- x))) + + + to the problem of evaluating the following expression involving + if and begin + expressions: + + +(if (> x 0) + x + (if (= x 0) + (begin (display 'zero) + 0) + (- x))) + + + Implementing the evaluation of cond in this + way simplifies the evaluator because it reduces the number of special + forms for which the evaluation process must be explicitly specified. + + + + We include syntax procedures that extract the parts of a + condexpression, and a procedure + cond->if that transforms + cond expressions into + if expressions. A case analysis begins with + cond and has a list of predicate-action + clauses. A clause is an else clause if its + predicate is the symbol else.The + value of a cond expression when all the + predicates are false and there is no else + clause is unspecified in Scheme; we have chosen here to make it + false. + + cond + sequence_exp + +(define (cond? exp) (tagged-list? exp 'cond)) + +(define (cond-clauses exp) (cdr exp)) + +(define (cond-else-clause? clause) + (eq? (cond-predicate clause) 'else)) + +(define (cond-predicate clause) (car clause)) + +(define (cond-actions clause) (cdr clause)) + +(define (cond->if exp) + (expand-clauses (cond-clauses exp))) + +(define (expand-clauses clauses) + (if (null? clauses) + 'false ; no else clause + (let ((first (car clauses)) + (rest (cdr clauses))) + (if (cond-else-clause? first) + (if (null? rest) + (sequence->exp (cond-actions first)) + (error "ELSE clause isn't last - - COND->IF" + clauses)) + (make-if (cond-predicate first) + (sequence->exp (cond-actions first)) + (expand-clauses rest)))))) + + + + + + Expressions (such as cond) that we choose + to implement as syntactic transformations are called derived + expressions. Let expressions are also + derived expressions (see + exercise).Practical Lisp + systems provide a mechanism that allows a user to add new derived + expressions and specify their implementation as syntactic + transformations without modifying the evaluator. Such a user-defined + transformation is called a + macro + macro. Although it is easy to add an elementary mechanism for + defining macros, the resulting language has subtle name-conflict + problems. There has been much research on mechanisms for macro definition + that do not cause these difficulties. See, + Kohlbecker, Eugene Edmund, Jr. + for example, Kohlbecker 1986, + Clinger, William + Rees, Jonathan A. + Clinger and Rees 1991, and + Hanson, Christopher P. + Hanson 1991. + metacircular evaluator for Schemeexpression representation + metacircular evaluator for Schemesyntax of evaluated language + metacircular evaluator for Schemederived expressions + metacircular evaluator for Schemespecial forms as derived expressions + derived expressions in evaluator + + + + + + Derived components + + + derived components in evaluator + syntactic formderivedas derived component + metacircular evaluator for JavaScriptderived components + metacircular evaluator for JavaScriptsyntactic forms as derived components + + + Some + syntactic formderivedas derived component + syntactic forms in our language can be defined in terms of + components involving other syntactic forms, rather than being + implemented directly. + One example is + function declarationderivedas derived component + derived components in evaluatorfunction declaration + function declaration, which + evaluate + transforms into a constant declaration whose + value expression is a lambda expression.In + actual JavaScript, there are subtle differences between the two + forms; see footnote + in chapter 1. + Exercise addresses these differences. + + + function_decl_to_constant_decl + function_declaration + tagged_list + +function function_decl_to_constant_decl(component) { + return make_constant_declaration( + function_declaration_name(component), + make_lambda_expression( + function_declaration_parameters(component), + function_declaration_body(component))); +} + + +function is_function_declaration(component) { + return is_tagged_list(component, "function_declaration"); +} +function function_declaration_name(component) { + return list_ref(component, 1); +} +function function_declaration_parameters(component) { + return list_ref(component, 2); +} +function function_declaration_body(component) { + return list_ref(component, 3); +} +function function_decl_to_constant_decl(component) { + return make_constant_declaration( + function_declaration_name(component), + make_lambda_expression( + function_declaration_parameters(component), + function_declaration_body(component))); +} + + + Implementing the evaluation of function declarations + in this way simplifies the evaluator because it reduces the number of + syntactic forms for which the evaluation process must be explicitly specified. + + + + Similarly, we define + operator combinationoperator combination + operator combinationparsing of + operator combinations in terms of + function applications. + Operator combinations are unary or binary and carry their operator symbol as second element + in the tagged-list representation: + + + \[ + \ll\ \mathit{unary}\textit{-}\mathit{operator}\ \ \mathit{expression}\ \gg \\ + = \\ + \texttt{list("unary\_operator\_combination", "}\mathit{unary}\textit{-}\mathit{operator}\texttt{"},\ + \texttt{list(}\ll\ \mathit{expression}\ \gg \texttt{))} + \] + + + + + +$\ll\ $unary-operator expression$\ \gg$ = + list("unary_operator_combination", + "unary-operator", + list($\ll\ $expression$\ \gg$)) + + + + + + + + + + + + where unary-operator is + ! (for logical negation) or + -unary (for numeric negation), and + + + \[ + \ll\ \mathit{expression}_1\ \mathit{binary}\textit{-}\mathit{operator}\ \ \mathit{expression}_2\;\gg \\ + = \\ + \texttt{list("binary\_operator\_combination", "}\mathit{binary}\textit{-}\mathit{operator}\texttt{"},\\ + \texttt{list(}\ll\ \mathit{expression}_1\;\gg\texttt{,}\ \ll\ \mathit{expression}_2\;\gg \texttt{))} + \] + + + + + +$\ll\ $expression$_1$ binary-operator expression$_2\;\gg$ = + list("binary_operator_combination", + "binary-operator", + list($\ll\ $expression$_1\;\gg$, $\ll\ $expression$_2\;\gg$)) + + + + + + + + + + + + where binary-operator is + +, + -, + *, + /, + %, + ===, + !==, + >, + <, + >= or + <=. + The syntax predicates are + is_operator_combination, + is_unary_operator_combination, and + is_binary_operator_combination, + and the selectors are + operator_symbol, + first_operand, and + second_operand. + + operator_combination + +function is_operator_combination(component) { + return is_unary_operator_combination(component) || + is_binary_operator_combination(component); +} +function is_unary_operator_combination(component) { + return is_tagged_list(component, "unary_operator_combination"); +} +function is_binary_operator_combination(component) { + return is_tagged_list(component, "binary_operator_combination"); +} +function operator_symbol(component) { + return list_ref(component, 1); +} +function first_operand(component) { + return list_ref(component, 2); +} +function second_operand(component) { + return list_ref(component, 3); +} + + + + + The evaluator uses + operator_combination_to_application + to transform an + operator combinationas function application + operator combinationderivedas derived component + derived components in evaluatoroperator combination + operator combination into a function application whose + function expression is the name of the operator: + + operator_combination_to_ application + operator_combination_to_application + operator_combination + make_application + +function operator_combination_to_application(component) { + const operator = operator_symbol(component); + return is_unary_operator_combination(component) + ? make_application(make_name(operator), + list(first_operand(component))) + : make_application(make_name(operator), + list(first_operand(component), + second_operand(component))); +} + + + + + Components (such as function declarations and operator combinations) that we + choose + to implement as syntactic transformations are called + derived component + derived components. Logical composition operations are also + derived components (see exercise). + + metacircular evaluator for JavaScriptcomponent representation + metacircular evaluator for JavaScriptsyntax of evaluated language + metacircular evaluator for JavaScriptderived components + metacircular evaluator for JavaScriptsyntactic forms as derived components + derived components in evaluator + syntactic formderivedas derived component + + + + + + + + functions_4_1_2 + headline_4_1_2 + is_literal + make_literal + variable + make_name + symbol_of_name + tagged_list + assignment + definition + declaration_symbol + make_constant_declaration + lambda + make_lambda_expression + function_declaration + return + if + begin + block + operator_combination + operator_combination_to_application + make_application + application + + + + + + + + Louis Reasoner plans to reorder the cond + clauses in eval so that the clause for + metacircular evaluator for Schemecombinations (procedure applications) procedure applications appears before the clause for assignments. He + argues that this will make the interpreter more efficient: Since + programs usually contain more applications than assignments, + definitions, and so on, his modified eval + will usually check fewer clauses than the original + eval before identifying the type of an + expression. +
    +
  1. + What is wrong with Louiss plan? (Hint: What will + Louiss evaluator do with the expression + (define x 3)?) +
  2. +
  3. + Louis is upset that his plan didnt work. He is willing to go + to any lengths to make his evaluator recognize procedure + applications before it checks for most other kinds of expressions. + Help him by changing the + metacircular evaluator for Schemesyntax of evaluated language + syntax of the evaluated language so that + procedure applications start with call. + For example, instead of (factorial 3) + we will now have to write + (call factorial 3) and instead of + (+ 1 2) we will have to write + (call + 1 2). +
  4. +
+
+
+ + + The inverse of parse + is called + unparseas inverse of parse + unparse. It takes + as argument a + tagged list as produced by parse + and returns a string that adheres to JavaScript notation. +
    +
  1. + Write a function unparse by + following the structure of evaluate + (without the environment parameter), but producing a string that represents + the given component, rather than evaluating it. Recall + from section that + the operator + can be applied + to two strings to concatenate them and that the primitive function + stringify turns values such + as 1.5, true, null and + undefined into strings. + Take care to respect operator precedences by surrounding the strings + that result from unparsing operator combinations with parentheses + (always or whenever necessary). +
  2. +
  3. + Your unparse function will come + in handy when solving later exercises in this section. Improve + unparse by adding + "" + (space) + and "\n" (newline) characters + to the result string, to follow the + indentation + indentation style used in the JavaScript + programs of this book. Adding such + whitespace characters + whitespace characters to (or removing + them from) a program text in order to make the text easier to read + is called + pretty-printing + pretty-printing. +
  4. +
+ +
+
+
+ + + + + Rewrite + data-directed programmingin metacircular evaluator + metacircular evaluator for Schemedata-directed eval + eval (metacircular)data-directed + eval so that the dispatch is done in + data-directed style. Compare this with the data-directed differentiation + procedure of + exercise. (You may + use the car of a compound expression as the + type of the expression, as is appropriate for the syntax implemented in + this section.) + + + + + + Rewrite + evaluate so that the + dispatch is done in + data-directed programmingin metacircular evaluator + metacircular evaluator for JavaScriptdata-directed evaluate + evaluate (metacircular)data-directed + data-directed style. Compare this with the + data-directed differentiation function of + exercise. (You may + use the tag of the tagged-list representation as the type of the components.) + + + + + + + + + + Recall + metacircular evaluator for Schemespecial forms (additional) + andwith no subexpressions + orwith no subexpressions + special forms + and + and + or + from section: + + + Recall from section that the + metacircular evaluator for JavaScriptsyntactic forms (additional) + metacircular evaluator for JavaScript&& (logical conjunction) + metacircular evaluator for JavaScript{\tt "|"|} (logical disjunction) + && (logical conjunction);1implementing in metacircular evaluator + && (logical conjunction);1derivedas derived component + {\tt "|"|} (logical disjunction);2implementing in metacircular evaluator + {\tt "|"|} (logical disjunction);2derivedas derived component + logical composition operations + && + and + || + are syntactic sugar for conditional expressions: + The logical conjunction + $\mathit{expression}_1$ && + $\mathit{expression}_2$ + is syntactic sugar for + $ \mathit{expression}_1$ ? + $\mathit{expression}_2$ : + false, and + the logical disjunction + $\mathit{expression}_1$ + || + $\mathit{expression}_2$ + is syntactic sugar for + $ \mathit{expression}_1$ ? + true : + $\mathit{expression}_2$. + + + + +
    +
  • + and: The expressions are evaluated from + left to right. If any expression evaluates to false, false is + returned; any remaining expressions are not evaluated. If all the + expressions evaluate to true values, the value of the last + expression is returned. If there are no expressions then true is + returned. +
  • +
  • + or: The expressions are evaluated from + left to right. If any expression evaluates to a true value, that + value is returned; any remaining expressions are not evaluated. If + all expressions evaluate to false, or if there are no expressions, + then false is returned. +
  • +
+ Install and and + or as new special forms for the evaluator + by defining appropriate syntax procedures and evaluation procedures + eval-and and + eval-or. Alternatively, show how to + implement and and + or as derived expressions. +
+ + They are + && (logical conjunction);1parsing of + ;2{\tt "|"|} (logical disjunction) + ;1&& (logical conjunction) + {\tt "|"|} (logical disjunction);2parsing of + parsed as follows: + + + + $\ll\ $expression$_1$ logical-operation expression$_2\;\gg$ = + list("logical_composition", + "$logical$-$operation$", + list($\ll\ $expression$_1\;\gg$, $\ll\ $expression$_2\;\gg$)) + + + + +
+ + $\ll\ \mathit{expression}_1\ \ \mathit{logical}\textit{-}\mathit{operation}\ \ \mathit{expression}_2\;\gg \ \ = $ + + + +list("logical_composition", + "$logical$-$operation$", + list($\ll\ $expression$_1\;\gg$, $\ll\ $expression$_2\;\gg$)) + + +
+
+ where logical-operation is + && or + ||. + Install + && and + || + as new syntactic forms for the evaluator + by declaring appropriate syntax functions and evaluation functions + eval_and and + eval_or. Alternatively, show how to + implement + && and + || + as derived components. +
+
+ + GitHub user EmilyOng provides a solution for the alternative: + how to implement + && and + || + as derived components. + + +function make_conditional_expr_decl(predicate, consequent_expression, alternative_expression) { + return list("conditional_expression", predicate, consequent_expression, alternative_expression); +} +function make_literal(value) { + return list("literal", value); +} + +// Syntax selectors +function logical_operation(component) { + return head(tail(component)); +} +function first_expression(component) { + return head(tail(tail(component))); +} +function second_expression(component) { + return head(tail(tail(tail(component)))); +} + +function logical_comp_decl_to_conditional_expr_decl(component) { + const operation = logical_operation(component); + + return operation === "&&" + ? make_conditional_expr_decl( + first_expression(component), + second_expression(component), + false + ) + : operation === "||" + ? make_conditional_expr_decl( + first_expression(component), + true, + second_expression(component) + ) + : error(component, "unknown operation -- logical_comp_decl_to_conditional_expr_decl"); +} + +display(logical_comp_decl_to_conditional_expr_decl(parse("a && b;"))); +display(logical_comp_decl_to_conditional_expr_decl(parse("a || b;"))); +display(logical_comp_decl_to_conditional_expr_decl(parse("(a && !b) || (!a && b);"))); + + + +
+ + + + + + Scheme allows an additional syntax for + condadditional clause syntax + clause, of acondadditional syntax + metacircular evaluator for Schemespecial forms (additional) + cond clauses, + (test + => + recipient) + If test evaluates to a true value, then + recipient is evaluated. Its value must be a + procedure of one argument; this procedure is then invoked on the value + of the test, and the result is returned as + the value of the cond expression. For + example + + +(cond ((assoc 'b '((a 1) (b 2))) => cadr) + (else false)) + + + returns 2. Modify the handling of cond + so that it supports this extended syntax. + + + +
    +
  1. + In JavaScript, lambda expressions must not have + parametersduplicate + duplicate parameters + metacircular evaluator for JavaScriptpreventing duplicate parameters + duplicate parameters. The evaluator in + section does not check for this. +
      +
    • + Modify the evaluator so + that any attempt to apply a function with duplicate parameters + signals an error. +
    • +
    • + Implement a verify function that + checks whether any lambda expression in a given program contains + duplicate parameters. With such a function, we could check the entire + program before we pass it to + evaluate. +
    • +
    + In order to implement this check in an evaluator for JavaScript, which + of these two approaches would you prefer? Why? +
  2. +
  3. + In JavaScript, the parameters of a lambda expression must be distinct from + metacircular evaluator for JavaScriptparameters distinct from local names + parametersdistinct from local names + internal declarationnames distinct from parameters + the names declared directly in the body block of the lambda expression + (as opposed to in an inner block). + Use your preferred approach above to check for this as well. +
  4. +
+
+
+
+ + + + + Let expressions are derived expressions, + metacircular evaluator for Schemespecial forms (additional) + because + + +(let ((var$_{1}$ exp$_{1}$) $\ldots$ (var$_{n}$ exp$_{n}$)) + body) + + + is equivalent to + + +((lambda (var$_{1}$ $\ldots$ var$_{n}$) + body) + exp$_{1}$ + $\vdots$ + exp$_{n}$) + + + Implement a syntactic transformation + let->combination that reduces evaluating + let expressions to evaluating combinations + of the type shown above, and add the appropriate clause to + eval to handle + let expressions. + + + + + + + + + Let* is similar to + let* + special forms (those marked ns are not in the IEEE Scheme standard)let*let* + metacircular evaluator for Schemespecial forms (additional) + let, except that the bindings of the + let* variables are performed sequentially + from left to right, and each binding is made in an environment in which + all of the preceding bindings are visible. For example + + +(let* ((x 3) + (y (+ x 2)) + (z (+ x y 5))) + (* x z)) + + + returns 39. Explain how a let* expression + can be rewritten as a set of nested let + expressions, and write a procedure + let*->nested-lets that performs this + transformation. If we have already implemented + let + (exercise) + and we want to extend the evaluator to handle + let*, is it sufficient to add a clause to + eval whose action is + + +(eval (let*->nested-lets exp) env) + + + or must we + explicitly expand let* in terms of + non-derived expressions? + + + + The language Scheme includes a variant of + metacircular evaluator for JavaScriptsyntactic forms (additional) + metacircular evaluator for JavaScriptletlet* (Scheme variant of let) + let* (Scheme variant of let) + Schemelet* in + let called + let*. We could approximate + the behavior of + let* in JavaScript by stipulating + that a + let* declaration implicitly + introduces a new block whose body includes the declaration and all + subsequent statements of the statement sequence in which the + declaration occurs. For example, the program + + +let* x = 3; +let* y = x + 2; +let* z = x + y + 5; +display(x * z); + + + displays 39 and could be seen as a shorthand for + + +{ + let x = 3; + { + let y = x + 2; + { + let z = x + y + 5; + display(x * z); + } + } +} + + +
    +
  1. + Write a program in such an extended JavaScript language + that behaves differently when some occurrences of the keyword + let are replaced + with let*. +
  2. +
  3. + Introduce + let* as + a new syntactic form by designing a suitable tagged-list representation and + writing a parse rule. + Declare a syntax predicate and selectors for the tagged-list + representation. +
  4. +
  5. + Assuming that + parse implements your + new rule, write a + let_star_to_nested_let + function that transforms any occurrence of + let* in a given program as + described above. We could + then evaluate a program p in the + extended language by running + evaluate(let_star_to_nested_let(p)). +
  6. +
  7. + As an alternative, consider implementing + let* by + adding to + evaluate a clause that + recognizes the new syntactic form and calls a function + eval_let_star_declaration. + Why does this approach not work? +
  8. +
+
+
+
+ + + + + + Named let is a variant of + letnamed + named letnamed let (special form) + special forms (those marked ns are not in the IEEE Scheme standard)named letnamed let + + metacircular evaluator for Schemespecial forms (additional) + let that has the form + + +(let var bindings body) + + + The bindings and + body are just as in ordinary + let, except that + var is bound within + body to a procedure whose body is + body and whose parameters are the variables + in the bindings. Thus, one can repeatedly + execute the body by invoking the procedure + named var. For example, the iterative + Fibonacci procedure (section) + can be rewritten using named let as follows: + + fibwith named let + +(define (fib n) + (let fib-iter ((a 1) + (b 0) + (count n)) + (if (= count 0) + b + (fib-iter (+ a b) a (- count 1))))) + + + Modify let->combination of + exercise to also support named + let. + + + + JavaScript supports + metacircular evaluator for JavaScriptsyntactic forms (additional) + while loopimplementing in metacircular evaluator + metacircular evaluator for JavaScriptwhile loop + syntactic formswhile loop + while (keyword) + keywordswhilewhile + while loops that execute a given + statement repeatedly. Specifically, + + +while (predicate) { body } + + + evaluates the predicate, and + if the result is true, evaluates the + body + and then evaluates + the whole while loop again. + Once the predicate evaluates + to false, the + while loop terminates. + + For example, recall the imperative-style version of the iterative + factorial function from section: + + factorial_imperative_2 + factorial_example + 120 + +function factorial(n) { + let product = 1; + let counter = 1; + function iter() { + if (counter > n) { + return product; + } else { + product = counter * product; + counter = counter + 1; + return iter(); + } + } + return iter(); +} + + + \newpage\noindent + We can formulate the same algorithm using a while loop as follows: + + factorialwith while loop + factorial_with_loop + +function factorial(n) { + let product = 1; + let counter = 1; + while (counter <= n) { + product = counter * product; + counter = counter + 1; + } + return product; +} + + + While loops are parsed as follows: + +
+ + $\ll\ \textbf{while} \ \texttt{(}\ \mathit{predicate}\ \texttt{)}\ \mathit{block}\ \gg \ \ =$ + + + + $\ll\ $while (predicate) block$\ \gg$ = + list("while_loop", $\ll\ $predicate$\ \gg$, $\ll\ $block$\ \gg$) + + +
+
+ + + + $\ll\ $while (predicate) block$\ \gg$ = + list("while_loop", $\ll\ $predicate$\ \gg$, $\ll\ $block$\ \gg$) + + + +
+
    +
  1. + Declare a syntax predicate and selectors to handle + while loops. +
  2. +
  3. + Declare a function while_loop + that takes as arguments a predicate and a + bodyeach represented by a + function of no argumentsand simulates the behavior of the + while loop. The + factorial function would then + look as follows: + + factorial_with_while_loop_function + +function factorial(n) { + let product = 1; + let counter = 1; + while_loop(() => counter <= n, + () => { + product = counter * product; + counter = counter + 1; + }); + return product; +} + + + Your function + while_loop should generate + an iterative process (see + section). +
  4. +
  5. + Install while loops as + a derived component by defining a transformation function + while_@to_@application + that makes use of your function + while_loop. +
  6. +
  7. + What problem arises with this approach for implementing + while loops, when + the programmer decides within the body of the loop to + return from the function that contains the loop? +
  8. +
  9. + Change your approach to address the problem. How about directly + installing while loops for the + evaluator, using a function + eval_while? +
  10. +
  11. + Following this direct approach, implement a + syntactic formsbreak statement + break (keyword) + keywordsbreakbreak + break; statement that immediately + terminates the loop in which it is evaluated. +
  12. +
  13. + Implement a + syntactic formscontinue statement + continue\textbf{\texttt{continue}} (keyword) + keywordscontinue\textbf{\texttt{continue}} + continue;\textbf{\texttt{continue}}; statement that terminates + only the loop iteration in which it is evaluated, and continues with + evaluating the + while loop predicate. +
  14. +
+
+
+
+ + + + + Many languages support a variety of iteration constructs, such as + metacircular evaluator for Schemespecial forms (additional) + looping constructsimplementing in metacircular evaluator + do, for, + while, and + until. In Scheme, iterative processes can + be expressed in terms of ordinary procedure calls, so special iteration + constructs provide no essential gain in computational power. On the + other hand, such constructs are often convenient. Design some iteration + constructs, give examples of their use, and show how to implement them + as derived expressions. + + + + + + + + + + By using data abstraction, we were able to write an + eval procedure that is independent of the + particular syntax of the language to be evaluated. To illustrate this, + design and implement a new + metacircular evaluator for Schemesyntax of evaluated language + metacircular evaluator for Schemedata abstraction in + syntax for Scheme by modifying the procedures + in this section, without changing eval or + apply. + + + The result of evaluating the body of a function is determined by + its return statements. + Following up on footnote + and the evaluation of declarations in + section, + this exercise addresses the question of what should be the result of + valueof a program + programvalue of + statementvalue-producing and non-value-producing + metacircular evaluator for JavaScriptvalue of program at top level + evaluating a JavaScript program that consists of a sequence of + statements (declarations, blocks, expression statements, and conditional + statements) outside of any function body. + + For such a program, JavaScript + statically + distinguishes between value-producing and + non-value-producing statements. (Here + statically means that + we can make the distinction by inspecting the program + rather than by running it.) + All declarations are + non-value-producing, and all + expression statements and conditional statements are + value-producing. + The value of an expression statement is the value of the expression. + The value of a conditional statement is the value of the branch that + gets executed, or the value + undefined if that branch is + not value-producing. + A block is value-producing if its body (sequence of statements) + is value-producing, and then its value is the value of its body. + A sequence is value-producing if any of + its component statements is value-producing, and then its value is + the value of its last value-producing component statement. + Finally, if the whole + program is not value-producing, its value is the value + undefined. +
    +
  1. + According to this specification, what are the values of the + following four programs? + + +1; 2; 3; + +1; { if (true) {} else { 2; } } + +1; const x = 2; + +1; { let x = 2; { x = x + 3; } } + + +
  2. +
  3. + Modify the evaluator to adhere to this + specification. +
  4. +
+
+
+
+
+ + +
+ + + diff --git a/xml/cn/chapter4/section1/subsection3.xml b/xml/cn/chapter4/section1/subsection3.xml new file mode 100644 index 000000000..c3306012f --- /dev/null +++ b/xml/cn/chapter4/section1/subsection3.xml @@ -0,0 +1,1110 @@ + + + Evaluator Data Structures + + + + + In addition to defining the + + external syntax of expressions, + representation of components, + + the evaluator implementation must also define the data structures that the + evaluator manipulates internally, as part of the execution of a + program, such as the representation of + + procedures + functions + + and environments and the representation of true and false. + + + + Testing of predicates + + + metacircular evaluator for JavaScriptrepresentation of true and false + + + + + + For conditionals, we accept anything to be true that is not the explicit + false object. + + + In 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 + metacircular evaluator for JavaScriptrepresentation of true and false + is_truthy gets applied only to + boolean values, and we accept only the boolean value + true to be truthy. + The opposite of + is_truthy is called + is_falsy.Conditionals + + in full JavaScript accept any value, not just a boolean, + as the result of evaluating the + predicate expression. JavaScript's notion of + truthiness and falsiness is captured by the following variants of + is_truthy and + is_falsy: + truthiness + falsiness + + is_boolean + is_truthyfull JavaScript version + is_falsyfull JavaScript version + true_2 + +function is_truthy(x) { return ! is_falsy(x); } + +function is_falsy(x) { + return (is_boolean(x) && !x ) || + (is_number(x) && (x === 0 || x !== x )) || + (is_string(x) && x === "") || + is_null(x) || + is_undefined(x); +} + + + The test x !== x is not a typo; + the only JavaScript value for which + x !== x yields true is the value + NaN (Not a Number), + NaN, not a typo + which is considered to be a falsy number (also not a typo), along with0. + The numerical value + NaN is the result of certain + arithmetic border cases such as + 0 / 0. + + The terms truthy and falsy were coined + by + good parts of JavaScript + JavaScriptgood parts + Crockford, Douglas + Douglas Crockford, one of whose books + (Crockford 2008) inspired this JavaScript adaptation. + + + + + + headline_4_1_3 + +// functions from SICP JS 4.1.3 + + + + is_truthy + is_falsy + true + true_example + false + +(define (true? x) + (not (eq? x false))) + +(define (false? x) + (eq? x false)) + + +function is_truthy(x) { + return is_boolean(x) + ? x + : error(x, "boolean expected, received"); +} +function is_falsy(x) { return ! is_truthy(x); } + + + + true_example + +is_truthy(false); // should return false because only true is truthy + + + + + + Representing + + procedures + functions + + + + + To handle primitives, we assume that we have available the following + metacircular evaluator for JavaScriptrepresentation of proceduresfunctions + + procedures: + functions: + +
    +
  • + + + (apply-primitive-procedure + proc args) + + + apply_primitive_function(fun, args) + + + apply_primitive_function + + applies the given primitive + + procedure + function + + to the argument values in the list args and returns the result of + the application. +
  • +
  • + + (primitive-procedure? + proc) + + is_primitive_function(fun) + + + is_primitive_function + + tests whether + + proc + fun + + is a primitive + + procedure. + function. + +
  • +
+ + These mechanisms for handling primitives are further described in + section. +
+ + + Compound + + procedures + functions + + are constructed from parameters, + + procedure + function + + bodies, and environments using the constructor + + make-procedure: + make_function: + + + + make_function + is_compound_function + function_parameters + function_body + function_environment + make_procedure + tagged_list + make_procedure_example + [ 'x', [ 'y', null ] ] + +(define (make-procedure parameters body env) + (list 'procedure parameters body env)) + +(define (compound-procedure? p) + (tagged-list? p 'procedure)) + +(define (procedure-parameters p) (cadr p)) +(define (procedure-body p) (caddr p)) +(define (procedure-environment p) (cadddr p)) + + +function make_function(parameters, body, env) { + return list("compound_function", parameters, body, env); +} +function is_compound_function(f) { + return is_tagged_list(f, "compound_function"); +} +function function_parameters(f) { return list_ref(f, 1); } + +function function_body(f) { return list_ref(f, 2); } + +function function_environment(f) { return list_ref(f, 3); } + + + + make_procedure_example + enclosing_environment + +const my_function = + make_function( + list("x", "y"), + list("return_statement", parse("x + y;")), + the_empty_environment); +display(is_compound_function(my_function)); +display(function_parameters(my_function)); +display(function_body(my_function)); +display(function_environment(my_function)); + + +const my_function = + make_function( + list("x", "y"), + list("return_statement", parse("x + y;")), + the_empty_environment); +is_compound_function(my_function); +function_body(my_function); +function_environment(my_function); +function_parameters(my_function); + + + + + + + + + + + + Representing return values + + + + We saw in section that the + evaluation of a sequence terminates when a return statement + is encountered, and that the evaluation of a function application needs + to return the value undefined if + the evaluation of the function body does not encounter a + return statement. In order to recognize that a value resulted from a + return valuerepresentation in metacircular evaluator + return statement, we introduce return values as evaluator data + structures. + + make_return_value + is_return_value + return_value_content + return_value + tagged_list + return_value_example + 42 + +function make_return_value(content) { + return list("return_value", content); +} +function is_return_value(value) { + return is_tagged_list(value, "return_value"); +} +function return_value_content(value) { + return head(tail(value)); +} + + + + return_value_example + enclosing_environment + +const my_return_value = make_return_value(42); +display(is_return_value(my_return_value)); +display(return_value_content(my_return_value)); + + +const my_return_value = make_return_value(42); +is_return_value(my_return_value); +return_value_content(my_return_value); + + + + + + + + Operations on Environments + + + + + + The evaluator needs operations for + metacircular evaluator for JavaScriptenvironment operations + symbol(s)in environment operations + manipulating environments. As explained + in section, an environment is a + sequence of frames, where each frame is a table of bindings that associate + + variables + symbols + + with their corresponding values. We use the following operations for + manipulating environments: + + +
    +
  • + (lookup-variable-value + var env) + lookup-variable-value +

    + returns the value that is bound to the symbol + var in the environment + env, or signals an error if the variable + is unbound. +

    +
  • +
  • + (extend-environment + variables values base-env + ) + extend-environment +

    + returns a new environment, consisting of a new frame in which the + symbols in the list variables are bound + to the corresponding elements in the list + values, where the enclosing environment + is the environment base-env. +

    +
  • +
  • + (define-variable! + var value env + ) + define-variable! +

    + adds to the first frame in the environment + env a new binding that associates the + variable var with the value + value. +

    +
  • +
  • + (set-variable-value! + var value env + ) + set-variable-value! +

    + changes the binding of the variable var + in the environment env so that the + variable is now bound to the value + value, or signals an error if the + variable is unbound. +

    +
  • + +
+
+ +
    +
  • + lookup_symbol_value(symbol, env) + lookup_symbol_value + + 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) + extend_environment + + 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) + assign_symbol_value + + 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. +
  • +
+
+
+
+ + To implement these operations we + metacircular evaluator for JavaScriptrepresentation of environments + represent an environment as a list of + frames. The enclosing environment of an environment is the + + cdr + tail + + of the list. The empty environment is simply the empty list. + + enclosing_environment + first_frame + the_empty_environment + enclosing_environment + enclosing_environment_example + null + +(define (enclosing-environment env) (cdr env)) + +(define (first-frame env) (car env)) + +(define the-empty-environment '()) + + +function enclosing_environment(env) { return tail(env); } + +function first_frame(env) { return head(env); } + +const the_empty_environment = null; + + + + enclosing_environment_example + +the_empty_environment; + + + Each frame of an environment is represented as a pair of lists: a list + of the + + variables + names + + bound in that frame and a list of the associated + values.Frames are not really a data + + + abstraction in the following code: + + + abstraction: + + + + + set-variable-value! + and + define-variable! + use set-car! + to directly modify the values in a frame. + + + The function + assign_symbol_value + below uses + set_head + to directly modify the values in a frame. + + + The purpose of the frame + procedures + functions + is to make the environment-manipulation + procedures + functions + easy to read. + + make_frame_example + +const my_frame = make_frame(list("x", "y"), list(1, 2)); +frame_symbols(my_frame); + + + + make_frame + frame_symbols + frame_values + make_frame + make_frame_example + [ 'x', [ 'y', null ] ] + +(define (make-frame variables values) + (cons variables values)) + +(define (frame-variables frame) (car frame)) +(define (frame-values frame) (cdr frame)) + +(define (add-binding-to-frame! var val frame) + (set-car! frame (cons var (car frame))) + (set-cdr! frame (cons val (cdr frame)))) + + +function make_frame(symbols, values) { return pair(symbols, values); } + +function frame_symbols(frame) { return head(frame); } + +function frame_values(frame) { return tail(frame); } + + + + + + To extend an environment by a new frame that associates + + variables + symbols + + with values, we make a frame consisting of the list of + + variables + symbols + + and the list of values, and we adjoin this to the environment. We signal + an error if the number of + + variables + symbols + + does not match the number of values. + + extend_environment + extend_environment + extend_environment_example + [ 1, [ 2, [ 3, null ] ] ] + +(define (extend-environment vars vals base-env) + (if (= (length vars) (length vals)) + (cons (make-frame vars vals) base-env) + (if (< (length vars) (length vals)) + (error "Too many arguments supplied" vars vals) + (error "Too few arguments supplied" vars vals)))) + + +function extend_environment(symbols, vals, base_env) { + return length(symbols) === length(vals) + ? pair(make_frame(symbols, vals), base_env) + : error(pair(symbols, vals), + length(symbols) < length(vals) + ? "too many arguments supplied" + : "too few arguments supplied"); +} + + +function extend_environment(symbols, vals, base_env) { + return length(symbols) === length(vals) + ? pair(make_frame(symbols, vals), base_env) + : length(symbols) < length(vals) + ? error("too many arguments supplied: " + + stringify(symbols) + ", " + + stringify(vals)) + : error("too few arguments supplied: " + + stringify(symbols) + ", " + + stringify(vals)); +} + + + + extend_environment_example + enclosing_environment + make_frame + + + +extend_environment(list("x", "y", "z"), + list(1, 2, 3), + the_empty_environment); + + +tail(head(extend_environment(list("x", "y", "z"), + list(1, 2, 3), + the_empty_environment))); + + + + + This is used by apply in + section to bind the + parameters of a function to its arguments. + + + + + + To look up a + + variable + symbol + + in an environment, we scan the list of + + variables + symbols + + in the first frame. If we find the desired + + variable, + symbol, + + we return the corresponding element in the list of values. If we do not + find the + + variable + symbol + + in the current frame, we search the enclosing environment, and so on. + If we reach the empty environment, we signal an + + + unbound + variable + + + "unbound name" + + + error. + + lookup_symbol_value + lookup_variable_value + lookup_variable_value_example + 1 + +(define (lookup-variable-value var env) + (define (env-loop env) + (define (scan vars vals) + (cond ((null? vars) + (env-loop (enclosing-environment env))) + ((eq? var (car vars)) + (car vals)) + (else (scan (cdr vars) (cdr vals))))) + (if (eq? env the-empty-environment) + (error "Unbound variable" var) + (let ((frame (first-frame env))) + (scan (frame-variables frame) + (frame-values frame))))) + (env-loop env)) + + +function lookup_symbol_value(symbol, env) { + function env_loop(env) { + function scan(symbols, vals) { + return is_null(symbols) + ? env_loop(enclosing_environment(env)) + : symbol === head(symbols) + ? head(vals) + : scan(tail(symbols), tail(vals)); + } + if (env === the_empty_environment) { + error(symbol, "unbound name"); + } else { + const frame = first_frame(env); + return scan(frame_symbols(frame), frame_values(frame)); + } + } + return env_loop(env); +} + + + + lookup_variable_value_example + enclosing_environment + extend_environment + make_frame + + + +const my_environment = + extend_environment(list("x", "y", "z"), + list(1, 2, 3), + the_empty_environment); + +lookup_symbol_value("x", my_environment); + + + + + + + To set a variable + to a new value in a specified environment, we scan + for the variable, just as in + lookup-variable-value, + and change the corresponding value when we find it. + + To 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 value when we find it. + + + + assign_symbol_value + assign_name_value + assign_name_value_example + 2 + +(define (set-variable-value! var val env) + (define (env-loop env) + (define (scan vars vals) + (cond ((null? vars) + (env-loop (enclosing-environment env))) + ((eq? var (car vars)) + (set-car! vals val)) + (else (scan (cdr vars) (cdr vals))))) + (if (eq? env the-empty-environment) + (error "Unbound variable - - SET!" var) + (let ((frame (first-frame env))) + (scan (frame-variables frame) + (frame-values frame))))) + (env-loop env)) + + +function assign_symbol_value(symbol, val, env) { + function env_loop(env) { + function scan(symbols, vals) { + return is_null(symbols) + ? env_loop(enclosing_environment(env)) + : symbol === head(symbols) + ? set_head(vals, val) + : scan(tail(symbols), tail(vals)); + } + if (env === the_empty_environment) { + error(symbol, "unbound name -- assignment"); + } else { + const frame = first_frame(env); + return scan(frame_symbols(frame), frame_values(frame)); + } + } + return env_loop(env); +} + + + + assign_name_value_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + + + +const my_block = parse("{ let x = 1; x = 2; }"); +evaluate(my_block, the_global_environment); + + + + + + + + To define a variable, + we search the first frame for a binding for + the variable, and change the binding if it exists + (just as in + set-variable-value!. + If no such binding exists, we adjoin one to the first frame. + + define_variable + +(define (define-variable! var val env) + (let ((frame (first-frame env))) + (define (scan vars vals) + (cond ((null? vars) + (add-binding-to-frame! var val frame)) + ((eq? var (car vars)) + (set-car! vals val)) + (else (scan (cdr vars) (cdr vals))))) + (scan (frame-variables frame) + (frame-values frame)))) + + + + + + + + + + + The method described here is only one of many plausible ways to represent + environments. Since we used + metacircular evaluator for JavaScriptdata abstraction in + data abstraction to isolate the rest of the + evaluator from the detailed choice of representation, we could change the + environment representation if we wanted to. (See + exercise.) In a + production-quality + + Lisp + JavaScript + + system, the speed of the evaluators environment + operationsespecially that of + + variable + symbol + + lookuphas a major + impact on the performance of the system. The representation described here, + although conceptually simple, is not efficient and would not ordinarily be + used in a production system.The drawback of this representation (as + well as the variant in + exercise) is that the + evaluator may have to search through many frames in order to find the binding + for a given variable. + (Such an approach is referred to as + deep binding + bindingdeep + deep binding.) One way to avoid + this inefficiency is to make use of a strategy called + lexical addressing, which will be discussed in + section. + + + metacircular evaluator for JavaScriptrepresentation of environments + + + + + functions_4_1_3 + headline_4_1_3 + true + make_procedure + return_value + enclosing_environment + make_frame + extend_environment + lookup_variable_value + assign_name_value + + + + + + Instead of representing a frame as a pair of lists, we can represent a frame + as a list of bindings, where each binding is a symbol-value pair. Rewrite the + environment operations to use this alternative representation. + + + + The + + procedures + functions + + + + set-variable-value!, + define-variable!, and + lookup-variable-value + + + lookup_symbol_value and + assign_symbol_value + + + can be expressed in terms of + + more abstract procedures + a more abstract function + + for traversing the environment structure. + + + Define abstractions that capture the common patterns and redefine the + three procedures in terms of these abstractions. + + + Define an abstraction that captures the common pattern and redefine the + two functions in terms of this abstraction. + + + + + + + + + + Scheme allows us to create new bindings for variables by means of + define, but provides no way to get rid of + bindings. Implement for the evaluator a special form + make-unbound! that removes the binding of a + given symbol from the environment in which the + make-unbound! expression is evaluated. This + problem is not completely specified. For example, should we remove only + the binding in the first frame of the environment? Complete the + specification and justify any choices you make. + + + + Our language distinguishes constants from variables by using + different keywordsconst + and letand prevents + assignment to constants. However, our interpreter + does not make use of this distinction; the function + assign_symbol_value will happily + assign a new value to a given symbol, regardless whether it is declared + as a constant or a variable. + constant (in JavaScript)detecting assignment to + Correct this flaw by calling the function + error whenever an attempt is + made to use a constant on the left-hand side of an assignment. + You may proceed as follows: +
    +
  • + Introduce predicates + is_constant_declaration + and + is_variable_declaration + that allow you to distinguish the two kinds. As shown in + section, + parse + distinguishes them by using the tags + "constant_declaration" + and + "variable_declaration". +
  • +
  • + Change + scan_out_declarations + and (if necessary) + extend_environment + such that constants are distinguishable from variables in + the frames in which they are bound. +
  • +
  • + Change + assign_symbol_value such + that it checks whether the given symbol has been declared as + a variable or as a constant, and in the latter case signals + an error that assignment operations are not allowed on constants. +
  • +
  • + Change + eval_declaration + such that when it encounters a constant declaration, + it calls a new function, + assign_constant_value, which + does not perform the check that you introduced in + assign_symbol_value. +
  • +
  • + If necessary, change + apply to ensure that + assignment to function parameters remains possible. +
  • +
+
+
+
+ + + +
    +
  1. + JavaScript's specification requires an implementation to + signal a runtime error upon an attempt to access the + value of a name before its declaration is evaluated (see + the end of section). + To achieve this behavior in the evaluator, + lookup_symbol_valuefor scanned-out declarations + change lookup_symbol_value + to signal an error if the value it finds is + "*unassigned*". +
  2. +
  3. + Similarly, we must not assign a new value to a variable if + we have not evaluated its let + declaration yet. Change the evaluation of assignment + such that assignment to a variable declared with + let signals an error + in this case. +
  4. + + +
+
+ + + + + + Prior to ECMAScript 2015's strict mode that we are using in this book, + JavaScript variables + worked quite differently from Scheme variables, which would have made + this adaptation to JavaScript considerably less compelling. +
    +
  1. + Before ECMAScript 2015, the only way to declare a local variable in + JavaScript was using the keyword + var + instead of the keyword let. + The scope of variables declared with + var is the entire body of the + immediately surrounding function declaration or lambda expression, + rather than just the immediately enclosing block. Modify + scan_out_declarations and + eval_block such that + names declared with const and + let follow the scoping rules + of var. +
  2. +
  3. + When not in strict mode, JavaScript permits undeclared + names to appear to the left of the + = in assignments. + Such an assignment adds the new binding to the global + environment. Modify the function + assign_symbol_value + to make assignment behave this way. The strict mode, which forbids + such assignments, was introduced + in JavaScript in order to make programs more secure. What + security issue is addressed by preventing assignment from + adding bindings to the global environment? +
  4. +
+
+
+
+ + +
+ diff --git a/xml/cn/chapter4/section1/subsection4.xml b/xml/cn/chapter4/section1/subsection4.xml new file mode 100644 index 000000000..9b8b0a92b --- /dev/null +++ b/xml/cn/chapter4/section1/subsection4.xml @@ -0,0 +1,906 @@ + + + Running the Evaluator as a Program + + + + + metacircular evaluator for JavaScriptrunning + + + Given the evaluator, we have in our hands a description + + (expressed in Lisp) + (expressed in JavaScript) + + of the process by which + + Lisp expressions + JavaScript statements and expressions + + are evaluated. One advantage of expressing the evaluator as a program is + that we can run the program. This gives us, running within + + Lisp, + JavaScript, + + a working model of how + + Lisp + JavaScript + + itself evaluates expressions. This can serve as a framework for + experimenting with evaluation rules, as we shall do later in this chapter. + + + + Our evaluator program reduces expressions ultimately to the application of + + + metacircular evaluator for Schemeprimitive procedures + + + metacircular evaluator for JavaScriptprimitive functions + + + primitive + + procedures. + functions. + + Therefore, all that we need to run the evaluator is to create a mechanism + that calls on the underlying + + Lisp + JavaScript + + system to model the application of primitive + + procedures. + functions. + + + + + There must be a binding for each primitive + + procedure + function + + name and operator, so that when + + eval + evaluate + + evaluates the + + operator + function expression + + of an application of a primitive, it will find an + object to pass to apply. We thus set up a + metacircular evaluator for JavaScriptglobal environment + global environmentmetacircularin metacircular evaluator + global environment that associates unique objects with the names of the + primitive + + procedures + functions and operators + + that can appear in the expressions we will be evaluating. + symbol(s)in global environment + + + The global environment also includes bindings for the symbols + metacircular evaluator for Schemetrue and falsetrue and false + true and false, + + + The global environment also includes bindings for + metacircular evaluator for JavaScriptundefinedundefined + undefined + and other names, + + + so that they can be used as constants in expressions to be evaluated. + + headline_4_1_4 + +// functions from SICP JS 4.1.4 + + + + setup_environment + setup_environment + setup_environment_example + tagged_list + extend_environment + enclosing_environment + make_frame + primitive_constants + primitive_procedures + primitive_procedure + '/' + +(define (setup-environment) + (let ((initial-env + (extend-environment (primitive-procedure-names) + (primitive-procedure-objects) + the-empty-environment))) + (define-variable! 'true true initial-env) + (define-variable! 'false false initial-env) + initial-env)) + + +function setup_environment() { + return extend_environment(append(primitive_function_symbols, + primitive_constant_symbols), + append(primitive_function_objects, + primitive_constant_values), + the_empty_environment); +} + + + + setup_environment_example + +const the_global_environment = setup_environment(); + +the_global_environment; + + +const the_global_environment = setup_environment(); + +list_ref(head(head(the_global_environment)), 12); + + + + the_global_environment + the_global_environment + setup_environment + the_global_environment_example + +(define the-global-environment (setup-environment)) + + +const the_global_environment = setup_environment(); + + + + the_global_environment_example + +the_global_environment; + + + + + + + + It does not matter how we represent the primitive procedure objects, so + long as apply can identify and apply them + by using the procedures primitive-procedure? + and apply-primitive-procedure. We have + chosen to represent a primitive procedure as a list beginning with the + symbol primitive and containing a procedure + in the underlying Lisp that implements that primitive. + + primitive_procedure + +(define (primitive-procedure? proc) + (tagged-list? proc 'primitive)) + +(define (primitive-implementation proc) (cadr proc)) + + + + + + + It does not matter how we represent primitive function objects, so long + as apply can identify and apply them using + the functions is_primitive_function + and apply_primitive_function. We + have chosen to represent a primitive function as a list beginning with + the string "primitive" and + containing a function in the underlying JavaScript that implements that + primitive. + + is_primitive_function + primitive_implementation + primitive_procedure + tagged_list + primitive_procedure_example + true + +function is_primitive_function(fun) { + return is_tagged_list(fun, "primitive"); +} + +function primitive_implementation(fun) { return head(tail(fun)); } + + + + primitive_procedure_example + +const my_primitive_plus = + list("primitive", (x, y) => x + y ); +display(is_primitive_function(my_primitive_plus)); +display(primitive_implementation(my_primitive_plus)); + + +const my_primitive_plus = + list("primitive", (x, y) => x + y ); +primitive_implementation(my_primitive_plus); +is_primitive_function(my_primitive_plus); + + + + + + + + + Setup-environment + The function + setup_environment + + + will get the primitive names and implementation + + procedures + functions + + from a list:Any + + procedure + function + + defined in the underlying + + Lisp + JavaScript + + can be used as a primitive for the metacircular evaluator. The name of a + primitive installed in the evaluator need not be the same as the name of its + implementation in the underlying + + Lisp; + JavaScript; + + the names are the same here because the metacircular evaluator implements + + Scheme + JavaScript + + itself. + Thus, for example, we could put + + (list 'first car) + list("first", head) + + + or + + (list 'square (lambda (x) (* x x))) + + + list("square", x => x * x) + + + in the list of + + primitive-procedures. + primitive_functions. + + + + primitive_function_symbols + primitive_function_objects + primitive_procedures + primitive_procedures_example + 20 + +(define primitive-procedures + (list (list 'car car) + (list 'cdr cdr) + (list 'cons cons) + (list 'null? null?) + (list 'display display) + (list 'read read) + (list '+ +) + (list '- -) + (list '* *) +;; more primitives + )) + +(define (primitive-procedure-names) + (map car + primitive-procedures)) + +(define (primitive-procedure-objects) + (map (lambda (proc) (list 'primitive (cadr proc))) + primitive-procedures)) + + +const primitive_functions = list(list("head", head ), + list("tail", tail ), + list("pair", pair ), + list("is_null", is_null ), + list("+", (x, y) => x + y ), + more primitive functions + ); + +const primitive_function_symbols = + map(f => head(f), primitive_functions); + +const primitive_function_objects = + map(f => list("primitive", head(tail(f))), + primitive_functions); + + +const primitive_functions = list( + list("head", head ), + list("tail", tail ), + list("pair", pair ), + list("list", list ), + list("is_null", is_null ), + list("display", display ), + list("error", error ), + list("math_abs",math_abs ), + list("+", (x, y) => x + y ), + list("-", (x, y) => x - y ), + list("-unary", x => - x ), + list("*", (x, y) => x * y ), + list("/", (x, y) => x / y ), + list("%", (x, y) => x % y ), + list("===", (x, y) => x === y), + list("!==", (x, y) => x !== y), + list("<", (x, y) => x < y), + list("<=", (x, y) => x <= y), + list(">", (x, y) => x > y), + list(">=", (x, y) => x >= y), + list("!", x => ! x) + ); +const primitive_function_symbols = + map(head, primitive_functions); +const primitive_function_objects = + map(fun => list("primitive", head(tail(fun))), + primitive_functions); + + + + primitive_procedures_example + +primitive_functions; + + +length(primitive_functions); + + + + + + + + + + Similar to primitive functions, we define other primitive constants that are + installed in the global environment by the function + setup_environment. + + primitive_constants + primitive_constants_example + 5 + +const primitive_constants = list(list("undefined", undefined), + list("math_PI", math_PI) + more primitive constants + ); + +const primitive_constant_symbols = + map(c => head(c), primitive_constants); + +const primitive_constant_values = + map(c => head(tail(c)), primitive_constants); + + +const primitive_constants = list(list("undefined", undefined), + list("Infinity", Infinity), + list("math_PI", math_PI), + list("math_E", math_E), + list("NaN", NaN) + ); +const primitive_constant_symbols = + map(c => head(c), primitive_constants); +const primitive_constant_values = + map(c => head(tail(c)), primitive_constants); + + + + + primitive_constants_example + +primitive_constants; + + +length(primitive_constants); + + + + + + + + To apply a + + primitive procedure, + primitive function, + + we simply apply the implementation + + procedure + function + + to the arguments, using the underlying + + Lisp + JavaScript + + system: + + + Apply-in-underlying-scheme is the + apply procedure we have used in earlier + chapters. The metacircular evaluators + apply procedure + (section) models the working + of this primitive. Having two different things called + apply leads to a technical problem in + running the metacircular evaluator, because defining the metacircular + evaluators apply will mask the + definition of the primitive. One way around this is to rename the + metacircular apply to avoid conflict with + the name of the primitive procedure. We have assumed instead that we + have saved a reference to the underlying + apply by doing + + apply_in_underlying + +(define apply-in-underlying-scheme apply) + + + before defining the metacircular apply. + This allows us to access the original version of + apply under a different name. + + + + JavaScripts apply method + expects the function arguments in a vector. (Vectors + are called arrays in JavaScript.) + Thus, the arglist is transformed into + a + vector (data structure)argumentsfor arguments of apply + vectorhere using a while + loop (see exercise): + + apply_in_underlying_javascript + apply (primitive method) + applyapply + apply_in_underlying + +function apply_in_underlying_javascript(prim, arglist) { + const arg_vector = []; // empty vector + let i = 0; + while (!is_null(arglist)) { + arg_vector[i] = head(arglist); // store value at index $\texttt{i}$ + i = i + 1; + arglist = tail(arglist); + } + return prim.apply(prim, arg_vector); // $\texttt{apply}$ is accessed via $\texttt{prim}$ +} + + + We also made use of + apply_in_underlying_javascript + to declare the function + apply_@generic + in section. + + + + apply_primitive_function + apply_primitive_procedure + apply_primitive_procedure_example + 3 + +(define (apply-primitive-procedure proc args) + (apply-in-underlying-scheme + (primitive-implementation proc) args)) + + +function apply_primitive_function(fun, arglist) { + return apply_in_underlying_javascript( + primitive_implementation(fun), arglist); +} + + + + apply_primitive_procedure_example + primitive_procedure + primitive_procedure_example + +apply_primitive_function(my_primitive_plus, list(1, 2)); + + + + metacircular evaluator for JavaScriptprimitive proceduresfunctions + + + + + functions_4_1_4 + headline_4_1_4 + true + primitive_procedure + primitive_procedures + primitive_constants + apply_primitive_procedure + setup_environment + the_global_environment + + + + + + + For convenience in running the metacircular evaluator, we provide a + metacircular evaluator for Schemedriver loop + driver loopmetacircularin metacircular evaluator + driver loop that models the read-eval-print loop of the + underlying Lisp system. It prints a + prompts + prompt, reads an input expression, evaluates this expression in + the global environment, and prints the result. We precede each printed + result by an output prompt so as to distinguish the value of + the expression from other output that may be printed.The + primitive procedure + read + primitive proceduresfunctions (those marked ns are not in the IEEE Scheme standard)readread + read waits for input from the user, and + returns the next complete expression that is typed. For example, if the + user types (+ 23 x), + read returns a three-element list + containing the symbol +, the number 23, and + the symbol x. + ' (single quote)readread and + quotereadread and + If the user types 'x, + read returns a two-element list containing + the symbol quote and the symbol + x. + + driver_loop + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + user_print + driver_loop_example + +(define input-prompt ";;; M-Eval input:\n") +(define output-prompt ";;; M-Eval value:\n") + +(define (driver-loop) + (prompt-for-input input-prompt) + (let ((input (read))) + (if (null? input) + 'EVALUATOR-TERMINATED + (let ((output (eval input the-global-environment))) + (announce-output output-prompt) + (user-print output) + (driver-loop))))) + +(define (prompt-for-input string) + (newline) (display string)) + +(define (announce-output string) + (newline) (display string)) + + + + + For convenience in running the metacircular evaluator, we provide a + metacircular evaluator for JavaScriptdriver loop + driver loopmetacircularin metacircular evaluator + driver loop that models the read-evaluate-print loop of + the underlying JavaScript system. It prints a + prompts + promptsmetacircular evaluator + prompt and reads an input program as a string. + It 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. + We precede each printed result by + an output prompt so as to distinguish the value of the + program from other output that may be printed. The driver loop gets + the program environment of the previous program as argument. + As described at the end of section, the + driver loop treats the program as if it were in a block: It + scans out the declarations, extends the given environment by a frame + containing a binding of each name to + "*unassigned*", and evaluates + the program with respect to the extended environment, which + is then passed as argument to the next iteration of the driver loop. + + driver_loopfor metacircular evaluator + driver_loop + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + user_print + user_read + driver_loop_example + +const input_prompt = "M-evaluate input: "; +const output_prompt = "M-evaluate value: "; + +function driver_loop(env) { + const input = user_read(input_prompt); + if (is_null(input)) { + display("evaluator terminated"); + } else { + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment(locals, unassigneds, env); + const output = evaluate(program, program_env); + user_print(output_prompt, output); + return driver_loop(program_env); + } +} + + +const input_prompt = "\nM-evaluate input:\n"; +const output_prompt = "\nM-evaluate value:\n"; + +function driver_loop(env, history) { + const input = user_read(history); + if (is_null(input)) { + display("", history + "\n--- session end ---\n"); + } else { + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment( + locals, unassigneds, env); + const output = evaluate(program, program_env); + const new_history = history + + input_prompt + + input + + output_prompt + + to_string(output); + return driver_loop(program_env, new_history); + } +} + +"metacircular evaluator loaded"; + + + + + + + + + We use JavaScript's prompt function + to request and read the input string from the user: + + user_read + user_read + +function user_read(prompt_string) { + return prompt(prompt_string); +} + + + + + The function + prompt (primitive function) + prompt (\textit{ns}) + prompt returns + null when the user cancels the + input. We use a special printing + + procedure user-print, + function user_print, + + + to avoid printing the environment part of a compound + + procedure, + function, + + which may be a very long list (or may even contain cycles). + + user_print + user_print + user_print_example + +(define (user-print object) + (if (compound-procedure? object) + (display (list 'compound-procedure + (procedure-parameters object) + (procedure-body object) + '<-procedure-env->)) + (display object))) + + +function user_print(string, object) { + function prepare(object) { + return is_compound_function(object) + ? "< compound-function >" + : is_primitive_function(object) + ? "< primitive-function >" + : is_pair(object) + ? pair(prepare(head(object)), + prepare(tail(object))) + : object; + } + display(string + " " + stringify(prepare(object))); +} + + +function to_string(object) { + return is_compound_function(object) + ? "<compound-function>" + : is_primitive_function(object) + ? "<primitive-function>" + : is_pair(object) + ? "[" + to_string(head(object)) + ", " + + to_string(tail(object)) + "]" + : stringify(object); +} + +function user_print(prompt_string, object) { + display("----------------------------", + prompt_string + "\n" + to_string(object) + "\n"); +} + + + + user_print_example + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +user_print("output: ", + evaluate(parse("1 + 2;"), + the_global_environment)); + + + + + Now all we need to do to run the evaluator is to initialize the global + environment and start the driver loop. Here is a sample interaction: + + driver_loop_example + driver_loop + +(define the-global-environment (setup-environment)) +(driver-loop) + + +const the_global_environment = setup_environment(); +driver_loop(the_global_environment); + + +driver_loop(the_global_environment, "--- session start ---"); + + + + + meta_append + driver_loop + driver_loop_example + +;;; M-Eval input: + + +(define (append x y) + (if (null? x) + y + (cons (car x) + (append (cdr x) y)))) + + +;;; M-Eval value: +ok + + +M-evaluate input: + + +function append(xs, ys) { + return is_null(xs) + ? ys + : pair(head(xs), append(tail(xs), ys)); +} + + +// press "Run" to start the driver loop +// M-evaluate input: +// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); } +// M-evaluate value: +// undefined + +// M-evaluate input: +// append(list("a", "b", "c"), list("d", "e", "f")); +// M-evaluate value: +// ["a", ["b", ["c", ["d", ["e", ["f", null]]]]]] + + +M-evaluate value: +undefined + + + + meta_append_example + driver_loop + driver_loop_example + +;;; M-Eval input: + + +(append '(a b c) '(d e f)) + + +;;; M-Eval value: +(a b c d e f) + + +M-evaluate input: + + +append(list("a", "b", "c"), list("d", "e", "f")); + + +// press "Run" to start the driver loop +// M-evaluate input: +// function append(xs, ys) { return is_null(xs) ? ys : pair(head(xs), append(tail(xs), ys)); } +// M-evaluate value: +// undefined + +// M-evaluate input: +// append(list("a", "b", "c"), list("d", "e", "f")); +// M-evaluate value: +// ["a", ["b", ["c", ["d", ["e", ["f", null]]]]]] + + +M-evaluate value: +["a", ["b", ["c", ["d", ["e", ["f", null]]]]]] + + + + + + Eva Lu Ator and Louis Reasoner are each experimenting with the + metacircular evaluator. Eva types in the definition of + map, and runs some test programs that use it. + They work fine. Louis, in contrast, has installed the system version of + map as a primitive for the metacircular + evaluator. When he tries it, things go terribly wrong. Explain why + Louiss map fails even though + Evas works. + + + + metacircular evaluator for JavaScriptrunning + + diff --git a/xml/cn/chapter4/section1/subsection5.xml b/xml/cn/chapter4/section1/subsection5.xml new file mode 100644 index 000000000..9766dc0f7 --- /dev/null +++ b/xml/cn/chapter4/section1/subsection5.xml @@ -0,0 +1,487 @@ + + + Data as Programs + + + + + programdataas data + dataprogramas program + + + + +
+
+ + The factorial program, viewed as an abstract machine. + +
+
+ + + + In thinking about a + + Lisp + JavaScript + + program that evaluates + + Lisp + JavaScript statements and + + expressions, an analogy might be helpful. One operational view of the + meaning of a program is that a + programabstractas abstract machine + program is a description of an abstract (perhaps infinitely large) machine. + For example, consider the familiar program to compute factorials: + + factorial_4_1_5 + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n))) + + +function factorial(n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + + + We may regard this program as the description of a + factorialas an abstract machine + machine containing + parts that decrement, multiply, and test for equality, together with a + two-position switch and another factorial machine. (The factorial + machine is infinite because it contains another factorial machine + within it.) Figure is a flow + diagram for the factorial machine, showing how the parts are wired together. + + + +
+
+ + The factorial program, viewed as an abstract machine. + +
+
+
+ + + In a similar way, we can regard the evaluator as a very special + evaluatorabstractas abstract machine + machine that takes as input a description of a machine. Given this + input, the evaluator configures itself to emulate the machine + described. For example, if we feed our evaluator the definition of + factorial, as shown in + + figure, + figure, + + the evaluator will be able to compute factorials. + + +
+
+ + The evaluator emulating a factorial machine. + +
+
+ +
+
+ + The evaluator emulating a factorial machine. + +
+
+
+
+ + + From this perspective, our evaluator is seen to be a + evaluatoruniversalas universal machine + universal machine + universal machine. + It mimics other machines when these are described as + + Lisp + JavaScript + + programs.The fact that the machines are described in + + Lisp + JavaScript + + is inessential. If we give our evaluator a + + Lisp + JavaScript + + program that behaves as an evaluator for some other language, say C, the + + Lisp + JavaScript + + evaluator will emulate the C evaluator, which in turn can emulate any + machine described as a C program. Similarly, writing a + + Lisp + JavaScript + + evaluator in C produces a C program that can execute any + + Lisp + JavaScript + + program. The deep idea here is that any evaluator can emulate any other. + Thus, the notion of what can in principle be computed + (ignoring practicalities of time and memory required) is independent of the + language or the computer, and instead reflects an underlying notion of + computability + computability. This was first demonstrated in a clear way by + Turing, Alan M. + Alan M. Turing (19121954), whose 1936 paper laid the foundations + for theoretical + computer science + computer science. In the paper, Turing presented a simple computational + modelnow known as a + Turing machine + Turing machineand argued that any effective + process can be formulated as a program for such a machine. (This + argument is known as the + ChurchTuring thesis + ChurchTuring thesis.) Turing then implemented a universal machine, + i.e., a Turing machine that behaves as an evaluator for Turing-machine + programs. He used this framework to demonstrate that there are well-posed + problems that cannot be computed by Turing machines (see + exercise), and so by implication + cannot be formulated as effective processes. Turing went on + to make fundamental contributions to practical computer science as well. + For example, he invented the idea of + programstructured with subroutines + structuring programs using general-purpose subroutines. See + Hodges, Andrew + Hodges 1983 for a biography of Turing. + This is striking. Try to imagine an analogous evaluator for electrical + circuits. This would be a circuit that takes as input a signal encoding the + plans for some other circuit, such as a filter. Given this input, the + circuit evaluator would then behave like a filter with the same description. + Such a universal electrical circuit is almost unimaginably complex. It is + remarkable that the program evaluator is a rather simple + program.Some people find it counterintuitive that an evaluator, + which is implemented by a relatively simple + + procedure, + function, + + can emulate programs that are more complex than the evaluator itself. The + existence of a universal evaluator machine is a deep and wonderful property + of computation. + recursion theory + Recursion theory, a branch of mathematical logic, is concerned with + logical limits of computation. + Hofstadter, Douglas R. + Douglas Hofstadters beautiful book Gdel, Escher, + Bach (1979) explores some of these ideas. + + + + Another striking aspect of the evaluator is that it acts as a bridge between + the data objects that are manipulated by our programming language and the + programming language itself. Imagine that the evaluator program + + (implemented in Lisp) + (implemented in JavaScript) + + is running, and that a user is typing + + expressions + programs + + to the evaluator and + observing the results. From the perspective of the user, an input + + expression + program + + such as + + (* x x) + x * x; + + is + + an expression + a program + + in the programming language, which the evaluator should + execute. + + + From the perspective of the evaluator, however, the expression is simply + a list (in this case, a list of three symbols: + *, x, + and x) that is to be manipulated according + to a well-defined set of rules. + + + From 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. + + + + + + + + That the users programs are the evaluators data need not + be a source of confusion. In fact, it is sometimes convenient to ignore + this distinction, and to give the user the ability to explicitly + evaluate a data object as a Lisp expression, by making + eval available for use in programs. Many + Lisp dialects provide a + eval + primitive procedures (those marked ns are not in the IEEE Scheme standard)evaleval (ns) + primitive eval procedure that takes as + arguments an expression and an environment and evaluates the expression + relative to the environment.Warning: + eval (metacircular)primitive eval vs. + This eval primitive is not + identical to the eval procedure we + implemented in section, + because it uses actual Scheme environments rather than the + sample environment structures we built in + section. These actual + environments cannot be manipulated by the user as ordinary lists; they + must be accessed via eval or other special + operations. + Similarly, the + apply (metacircular)primitive apply vs. + apply primitive we saw + earlier is not identical to the metacircular + apply, because it uses actual Scheme + procedures rather than the procedure objects we constructed in + sections + and. + Thus, + + +(eval '(* 5 5) user-initial-environment) + + + and + + +(eval (cons '* (list 5 5)) user-initial-environment) + + + will both return 25.The MIT + MIT Schemeevaleval + MIT Schemeuser-initial-environmentuser-initial-environment + evalMIT Scheme + user-initial-environment (MIT Scheme) + implementation of Scheme includes eval, as + well as a symbol user-initial-environment + that is bound to the initial environment in which the users input + expressions are evaluated. + + + programdataas data + dataprogramas program + + + + + That the + JavaScripteval in + eval (primitive function in JavaScript) + eval + users programs are the evaluators data need not + be a source of confusion. In fact, it is sometimes convenient to ignore + this distinction, and to give the user the ability to explicitly + evaluate a string as a JavaScript statement, using JavaScript's + primitive function eval + that takes as argument a string. It parses the string + andprovided that it is syntactically correctevaluates the + resulting representation in the environment in which + eval is applied. Thus, + + +eval("5 * 5;"); + + + and + + evaluate_example_4_1_5 + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + 25 + +evaluate(parse("5 * 5;"), the_global_environment); + + + will both return 25.Note that + eval + may not be available in the JavaScript environment that you are + using, or its use may be restricted for security reasons. + + programdataas data + dataprogramas program + + + + + + Given a one-argument + + procedure + function + + + + p + + + f + + + and an object a, + + + p + + + f + + + is said to halt on + a if evaluating the expression + + (p a) + f(a) + + returns a value (as opposed to terminating with an error message or running + forever). + halting problem + Show that it is impossible to write a + + procedure + function + + + halts? + halts + + that correctly determines whether + + + p + + + f + + + halts on + a for any + + procedure + function + + + + p + + + f + + + and object a. + Use the following reasoning: If you had such a + + procedure + function + + + halts?, + halts, + + you could implement the following program: + + +(define (run-forever) (run-forever)) + +(define (try p) + (if (halts? p p) + (run-forever) + 'halted)) + + +function run_forever() { return run_forever(); } + +function strange(f) { + return halts(f, f) + ? run_forever() + : "halted"; +} + + + Now consider evaluating the expression + + (try try) + + strange(strange) + + + and show that any possible outcome (either halting or running forever) + violates the intended behavior of + halts?halts.Although + we stipulated that + + halts? + halts + + is given a + + procedure + function + + object, notice that this reasoning still applies even if + + halts? + halts + + can gain access to the + + procedures + functions + + text and its environment. + Turing, Alan M. + This is Turings celebrated + Halting Theorem + Halting Theorem, which gave the + first clear example of a + noncomputablecomputability + noncomputable problem, i.e., a well-posed + task that cannot be carried out as a computational + + procedure. + function. + + + + + metacircular evaluator for JavaScript +
diff --git a/xml/cn/chapter4/section1/subsection6.xml b/xml/cn/chapter4/section1/subsection6.xml new file mode 100644 index 000000000..1e2d28655 --- /dev/null +++ b/xml/cn/chapter4/section1/subsection6.xml @@ -0,0 +1,885 @@ + + + Internal + + Definitions + Declarations + + + + + + block structure + + + + internal definitionscope of name + scope of a variableinternal define + + + Our environment model of evaluation and our metacircular evaluator + execute definitions in sequence, extending the environment frame one + definition at a time. This is particularly convenient for interactive + program development, in which the programmer needs to freely mix the + application of procedures definition procedures. However, if we think + carefully about the internal definitions used to implement block + structure (introduced in section), + we will find that name-by-name extension of the environment may not be + the best way to define local variables. + + + + Consider a procedure with internal definitions, such as + + +(define (f x) + (define (even? n) + (if (= n 0) + true + (odd? (- n 1)))) + (define (odd? n) + (if (= n 0) + false + (even? (- n 1)))) + $\langle$ rest of body of$\rangle$ f) + + + + + + Our intention here is that the name odd? + in the body of the procedure even? should + refer to the procedure odd? that is + defined after even?. + The scope of the name odd? is the entire + body of f, not just the portion of the body + of f starting at the point where the + define for odd? + occurs. Indeed, when we consider that odd? + is itself defined in terms of + even?so that + even? and odd? + are mutually recursive procedureswe see that the only + satisfactory interpretation of the two + defines is to regard them as if the names + even? and odd? + were being added to the environment simultaneously. More generally, in + block structure, the scope of a local name is the entire procedure + body in which the define is evaluated. + + + + As it happens, our interpreter will evaluate calls to + f correctly, but for an + accidental reason: Since the definitions + of the internal procedures come first, no calls to these procedures + will be evaluated until all of them have been defined. Hence, + odd? will have been defined by the time + even? is executed. In fact, our sequential + evaluation mechanism will give the same result as a mechanism that + directly implements simultaneous definition for any + procedure in which the + internal definitionrestrictions on + internal definitions come first in a body and evaluation of the value + expressions for the defined variables doesnt actually use any of + the defined variables. (For an example of a procedure that doesnt + obey these restrictions, so that sequential definition isnt + equivalent to simultaneous definition, see + exercise.)Wanting + programs to not depend on this evaluation mechanism is the reason for the + management is not responsible remark in + footnote of chapter. + By insisting that internal definitions come first and do not use each + other while the definitions are being evaluated, the IEEE standard + for Scheme leaves implementors some choice in the mechanism used to + evaluate these definitions. The choice of one evaluation rule rather + than another here may seem like a small issue, affecting only the + interpretation of badly formed programs. However, we will + see in section that moving + to a model of simultaneous scoping for internal definitions avoids some + nasty difficulties that would otherwise arise in implementing a compiler. + + + + + There is, however, a simple way to treat definitions + so that internally defined names have truly simultaneous + scopejust create all local variables that will be in the + current environment before evaluating any of the value expressions. + One way to do this is by a syntax transformation on + lambda expressions. Before evaluating the + body of a lambda expression, we + scanning out internal definitions + internal definitionscanning out + scan out and eliminate all the internal definitions in + the body. The internally defined variables will be created with a + let and then set to their values by + assignment. For example, the procedure + + +(lambda vars + (define u e1) + (define v e2) + e3) + + + would be transformed into + + +(lambda vars + (let ((u '*unassigned*) + (v '*unassigned*)) + (set! u e1) + (set! v e2) + e3)) + + + where *unassigned* is a special symbol that + causes looking up a variable to signal an error if an attempt is made to + use the value of the not-yet-assigned variable. + + + + An alternative strategy for scanning out internal definitions is shown + in exercise. Unlike the + transformation shown above, this enforces the restriction that the + defined variables values can be evaluated without using any of the + variables values.The IEEE standard for Scheme allows + for different implementation strategies by specifying that it is up to + the programmer to obey this restriction, not up to the implementation to + enforce it. Some Scheme implementations, including + MIT Schemeinternal definitions + MIT Scheme, use the transformation shown above. Thus, some programs that + dont obey this restriction will in fact run in such + implementations. + internal definitionscope of name + + + + + internal declarationscope of name + scope of a nameinternal declaration + + + In JavaScript, the scope of a declaration + is the entire block that immediately surrounds the declaration, + not just the portion of the block starting at the point where + the declaration occurs. + This section takes a closer look at this design choice. + + + Let us revisit the pair of mutually recursive functions + is_even and + is_odd from + Section, + declared locally + in the body of a functionf. + + f_is_even_is_odd_2 + +function f(x) { + function is_even(n) { + return n === 0 + ? true + : is_odd(n - 1); + } + function is_odd(n) { + return n === 0 + ? false + : is_even(n - 1); + } + return is_even(x); +} + + + Our intention here is that the name + is_odd + in the body of the function is_even + should refer to the function is_odd + that is declared after is_even. + The scope of the name is_odd is the + entire body block of f, not just the portion of + the body off starting at the point where + the declaration of is_odd + occurs. Indeed, when we consider that + is_odd is itself defined in terms of + is_evenso that + is_even and + is_odd are mutually recursive + functionswe see that the only satisfactory interpretation of + the two declarations is to regard them as if the names + is_even and + is_odd + were being added to the environment simultaneously. More generally, in + block structure, the scope of a local name is the entire block + in which the declaration is evaluated. + + + + The evaluation of blocks in the metacircular evaluator of + section achieves such + a simultaneous scope for local names by + scanning out declarationsin metacircular evaluator + internal declarationscanning out + scanning out the declarations in the block and extending the current + environment with a frame containing bindings for all the declared + names before evaluating the declarations. Thus the new environment + in which the block body is evaluated already contains + bindings for + is_even and + is_odd, and any occurrence + of one of these names refers to the correct binding. Once their + declarations are evaluated, + these names are bound to their declared values, namely function + objects that have the extended environment as their environment + part. Thus, for example, + by the time + is_even + gets applied in the body of + f, its environment + already contains the correct binding for the symbol + is_odd, and + the evaluation of the name + is_odd in the body of + is_even retrieves the correct + value. + + + Consider the function f_3 + of section: + + f_helper_definition3_repeated + +function f_3(x, y) { + const a = 1 + x * y; + const b = 1 - y; + return x * square(a) + y * b + a * b; +} + + +
    +
  1. + Draw a diagram of the environment in effect during evaluation + of the return expression of + f_3. +
  2. +
  3. + When evaluating a function application, the evaluator creates two + frames: one for the parameters and one for the names declared + directly in the function's body + block, as opposed to in an inner block. + Since all these names have + the same scope, an implementation could combine the two frames. + Change the evaluator + such that the evaluation of the body block does not create + a new frame. + You may assume that this will not result in duplicate names + in the frame (exercise justifies this). +
  4. +
+ +
+ + + Eva Lu Ator is writing programs in which + function declarations and other statements are interleaved. + She needs to make sure that the declarations are evaluated before + the functions are applied. She complains: Why can't + the evaluator take care of this chore, and + hoisting of function declarations + function declarationhoisting of + hoist all function + declarations to the beginning of the block in which they appear? Function declarations outside of blocks should be hoisted to the beginning of the program. +
    +
  1. + Modify the evaluator following Eva's suggestion. +
  2. +
  3. + The designers of JavaScript decided to follow Eva's approach. + Discuss this decision. +
  4. +
  5. + In addition, the designers of JavaScript decided to allow the name declared + by a function declaration to be reassigned using assignment. + Modify your solution + accordingly and discuss this decision. +
  6. +
+
+ + + + Recursive functions are obtained in a + recursive functionspecifying without declaration + roundabout way in our + interpreter: First declare the name that will refer to the recursive + function and assign to it the special value + "*unassigned*"; then define the + recursive function in the scope of that name; and finally assign the + defined function to the name. By the time the recursive function gets + applied, any occurrences of the name in the body properly refer to + the recursive function. Amazingly, it is possible to specify recursive + functions without using declarations or assignment. The following program computes + 10 factorial by applying a recursive + factorialdeclarationwithout declaration or assignment + factorial function:This example illustrates a programming + trick for formulating recursive functions without using assignment. The + most general trick of this sort is the + Y$Y$ operator + SchemeY$Y$ operator written in + $Y$ + operator, which can be used to give a pure + $\lambda$-calculus implementation of + recursion. (See + Stoy, Joseph E.Gabriel, Richard P. + Stoy 1977 for details on the lambda + calculus, and Gabriel 1988 for an exposition of the + $Y$ operator in the language + Scheme.) + + lambda_calculus_factorial + 3628800 + +(n => (fact => fact(fact, n)) + ((ft, k) => k === 1 + ? 1 + : k * ft(ft, k - 1)))(10); + + +
    +
  1. + Check (by evaluating the expression) that this really does compute + factorials. Devise an analogous expression for computing Fibonacci + numbers. +
  2. +
  3. + Consider the function f given + above: + + f_is_even_is_odd_3 + +function f(x) { + function is_even(n) { + return n === 0 + ? true + : is_odd(n - 1); + } + function is_odd(n) { + return n === 0 + ? false + : is_even(n - 1); + } + return is_even(x); +} + + + Fill in the missing expressions to complete an alternative + declaration of f, which has + no internal function declarations: + + +function f(x) { + return ((is_even, is_odd) => is_even(is_even, is_odd, x)) + ((is_ev, is_od, n) => n === 0 ? true : is_od(??, ??, ??), + (is_ev, is_od, n) => n === 0 ? false : is_ev(??, ??, ??)); +} + + +
  4. +
+ + Part (a) + + +// solution provided by GitHub user LucasGdosR + +// The Fibonacci function receives n as an argument +// It applies the fib function recursively, passing n as an argument, +// as well as the initial arguments (k = 1, fib1 = 1, fib2 = 1) +(n => (fib => fib(fib, n, 2, 1, 1)) + // The fib function is then defined as ft, + // with parameters n, k, fib1, and fib2 + // Establish the base cases: n === 1 or n === 2 + ((ft, n, k, fib1, fib2) => n === 1 + ? 1 + : n === 2 + ? 1 + : + // Iterate until k equals n. Notice k starts at 2, and gets incremented every iteration + k === n + // When k reaches n, return the accumulated fib2 + ? fib2 + // Otherwise, accumulate the sum as the new fib2 + : ft(ft, n, k + 1, fib2, fib1 + fib2))); + + + Part (b) + + +// solution provided by GitHub user LucasGdosR + +function f(x) { + return ((is_even, is_odd) => is_even(is_even, is_odd, x)) + ((is_ev, is_od, n) => n === 0 ? true : is_od(is_ev, is_od, n - 1), + (is_ev, is_od, n) => n === 0 ? false : is_ev(is_ev, is_od, n - 1)); +} + + + +
+
+
+ + + + + + In this exercise we implement the method just described for interpreting + internal definitions. + We assume that the evaluator supports let + (see exercise). +
    +
  1. + Change + lookup-variable-valuefor scanned-out definitions + lookup-variable-value + (section) + to signal an error if the value it finds is the symbol + *unassigned*. +
  2. +
  3. + Write a procedure + scan-out-defines + scan-out-defines + that takes a procedure body and returns an equivalent one that has no + internal definitions, by making the transformation described above. +
  4. +
  5. + Install scan-out-defines in the + interpreter, either in make-procedure + or in procedure-body (see + section). + Which place is better? Why? +
  6. +
+
+ + + Draw diagrams of the environment in effect when evaluating the + expression e3 in the procedure in the text, + comparing how this will be structured when definitions are interpreted + sequentially with how it will be structured if definitions are scanned + out as described. Why is there an extra frame in the transformed program? + Explain why this difference in environment structure can never make a + difference in the behavior of a correct program. Design a way to make + the interpreter implement the simultaneous scope rule for + internal definitions without constructing the extra frame. + +
+
+ + + + + Consider an alternative strategy for scanning out definitions that + translates the example in the text to + + +(lambda vars + (let ((u '*unassigned*) + (v '*unassigned*)) + (let ((a e1) + (b e2)) + (set! u a) + (set! v b)) + e3)) + + + Here a and b + are meant to represent new variable names, created by the interpreter, + that do not appear in the users program. Consider the + solve differential equationscanwith scanned-out definitions + solve procedure from + section: + + +(define (solve f y0 dt) +(define y (integral (delay dy) y0 dt)) +(define dy (stream-map f y)) + y) + + + Will this procedure work if internal definitions are scanned out as + shown in this exercise? What if they are scanned out as shown in the + text? Explain. + + + + + Sequential Declaration Processing + + + sequential declaration processing vs.scanning out + scanning out declarationssequential declaration processing vs. + + The design of our evaluator of + section imposes a + runtime burden on the evaluation of blocks: It needs to scan + the body of the block for locally declared names, extend the + current environment with a new frame that binds those names, and evaluate the + block body in this extended environment. Alternatively, the evaluation + of a block could extend the current environment with an empty frame. + The evaluation of each declaration in the block body would then add + a new binding to that frame. + To implement this design, we first simplify + eval_block: + + eval_block_simplified + +function eval_block(component, env) { + const body = block_body(component); + return evaluate(body, extend_environment(null, null, env); +} + + + The function + eval_declaration can no + longer assume that the environment already has a binding for + the name. + Instead of using + assign_symbol_value to + change an existing binding, it calls a new function, + add_binding_to_frame, to + add to the first frame of the environment a binding of the name + to the value of the value expression. + + eval_declaration_simplified + +function eval_declaration(component, env) { + add_binding_to_frame( + declaration_symbol(component), + evaluate(declaration_value_expression(component), env), + first_frame(env)); + return undefined; +} +function add_binding_to_frame(symbol, value, frame) { + set_head(frame, pair(symbol, head(frame))); + set_tail(frame, pair(value, tail(frame))); +} + + + + + + + + + Ben Bitdiddle, Alyssa P. Hacker, and Eva Lu Ator are arguing about + the desired result of evaluating the expression + + +(let ((a 1)) + (define (f x) + (define b (+ a x)) + (define a 5) + (+ a b)) + (f 10)) + + + Ben asserts that the result should be obtained using the sequential rule + for define: b + is defined to be 11, then a is defined to + be 5, so the result is 16. Alyssa objects that mutual recursion requires + the simultaneous scope rule for internal procedure definitions, and that + it is unreasonable to treat procedure names differently from other names. + Thus, she argues for the mechanism implemented in + exercise. This would lead to + a being unassigned at the time that the + value for b is to be computed. Hence, in + Alyssas view the procedure should produce an error. Eva has a + third opinion. She says that if the definitions of + a and b are + truly meant to be simultaneous, then the value 5 for + a should be used in evaluating + b. Hence, in Evas view + a should be 5, + b should be 15, and the result should be 20. + Which (if any) of these viewpoints do you support? Can you devise a way + to implement internal definitions so that they behave as Eva + prefers?The MIT implementors of Scheme support Alyssa on the + following grounds: Eva is in principle correctthe definitions + should be regarded as simultaneous. But it seems difficult to implement + a general, efficient mechanism that does what Eva requires. In the + absence of such a mechanism, it is better to generate an error in the + difficult cases of simultaneous definitions (Alyssas notion) than + to produce an incorrect answer (as Ben would have it). + + + + + + + + + Because internal definitions look sequential but are actually + simultaneous, some people prefer to avoid them entirely, and use the + special form + letrec + special forms (those marked ns are not in the IEEE Scheme standard)letrecletrec + letrec instead. + Letrec looks like + let, so it is not surprising that the + variables it binds are bound simultaneously and have the same scope as + each other. The sample procedure f above + can be written without internal definitions, but with exactly the same + meaning, as + + +(define (f x) + (letrec ((even? + (lambda (n) + (if (= n 0) + true + (odd? (- n 1))))) + (odd? + (lambda (n) + (if (= n 0) + false + (even? (- n 1)))))) + $\langle$rest of body of$\rangle$ f)) + + + Letrec expressions, which have the form + + +(letrec ((var$_{1}$ exp$_{1}$) $\ldots$ (var$_{n}$ exp$_{n}$)) + body) + + + are a variation on let in which the + expressions exp$_{k}$ that provide the + initial values for the variables var$_{k}$ + are evaluated in an environment that includes all the + letrec bindings. This permits recursion in + the bindings, such as the mutual recursion of + even? and odd? + in the example above, or the evaluation of 10 + factorialletrecwith letrec + factorial with + + +(letrec ((fact + (lambda (n) + (if (= n 1) + 1 + (* n (fact (- n 1))))))) + (fact 10)) + + +
    +
  1. + Implement letrec as a derived + expression, by transforming a letrec + expression into a let expression as + shown in the text above or in + exercise. That is, + the letrec variables should be created + with a let and then be assigned their + values with set!. +
  2. +
  3. + Louis Reasoner is confused by all this fuss about internal + definitions. The way he sees it, if you dont like to use + define inside a procedure, you can just + use let. Illustrate what is loose about + his reasoning by drawing an environment diagram that shows the + environment in which the + $\langle$rest of body of$\rangle$ + f + is evaluated during + evaluation of the expression (f 5), with + f defined as in this exercise. Draw an + environment diagram for the same evaluation, but with + let in place of + letrec in the definition of + f. +
  4. +
+
+
+ + + With sequential declaration processing, the scope of a + declaration is no longer the entire block that immediately surrounds + the declaration, but rather just the portion of the block starting at + the point where the declaration occurs. + Although we no longer have simultaneous scope, sequential + declaration processing + will evaluate calls to the function + f at the beginning of this section + correctly, but for an + accidental reason: Since the declarations + of the internal functions come first, no calls to these functions + will be evaluated until all of them have been declared. Hence, + is_odd will have been declared by the time + is_even is executed. In fact, + sequential declaration processing + will give the same result as our scanning-out-names evaluator in + section + for any function + in which the + internal declarationrestrictions on + internal declarations come first in a body and evaluation of the value + expressions for the declared names doesnt actually use any of + the declared names. + Exercise shows + an example of a function that doesnt + obey these restrictions, so that the alternative evaluator isnt + equivalent to our scanning-out-names evaluator. + + + Sequential declaration processing is more efficient and easier to + implement than scanning out names. + However, with sequential processing, the + declaration to which a name refers may depend on the order in which + the statements in a block are evaluated. + In exercise, we see that + views may differ as to whether that is desirable. + + sequential declaration processing vs.scanning out + scanning out declarationssequential declaration processing vs. + + + Ben Bitdiddle, Alyssa P. Hacker, and Eva Lu Ator are arguing about + the desired result of evaluating the program + + mystery_f + +const a = 1; +function f(x) { + const b = a + x; + const a = 5; + return a + b; +} +f(10); + + + Ben asserts that the result should be obtained using the sequential + processing of declarations: + b is declared to be 11, then + a is declared to be 5, so the result is 16. + Alyssa objects that mutual recursion requires the simultaneous scope + rule for internal function declarations, and that it is unreasonable to + treat function names differently from other names. Thus, she argues for + the mechanism implemented in + section. This would + lead to a being unassigned at the time that + the value for b is to be computed. Hence, + in Alyssas view the function should produce an error. Eva has a + third opinion. She says that if the declarations of + a and b + are truly meant to be simultaneous, then the value 5 for + a should be used in evaluating + b. Hence, in Evas view + a should be 5, + b should be 15, and the result should be 20. + Which (if any) of these viewpoints do you support? Can you devise a way + to implement internal declarations so that they behave as Eva + prefers?The designers of JavaScript support Alyssa on the + following grounds: Eva is in principle correctthe declarations + should be regarded as simultaneous. But it seems difficult to implement + a general, efficient mechanism that does what Eva requires. In the + absence of such a mechanism, it is better to generate an error in the + difficult cases of simultaneous declarations (Alyssas notion) than + to produce an incorrect answer (as Ben would have it). + + +
+ + + + + + + Amazingly, Louiss intuition in + exercise is correct. It is indeed + recursive procedurespecifying without define + possible to specify recursive procedures without using + letrec (or even + define), although the method for + accomplishing this is much more subtle than Louis imagined. + The following expression computes 10 + factorialletrecwithout letrec or define + factorial by applying a recursive + factorial procedure:This example illustrates a programming + trick for formulating recursive procedures without using + define. The + most general trick of this sort is the + Y$Y$ operator + $Y$ + operator, which can be used to give a pure + $\lambda$-calculus implementation of + recursion. (See + Stoy, Joseph E.Gabriel, Richard P. + Stoy 1977 for details on the lambda + calculus, and Gabriel 1988 for an exposition of the + $Y$ operator in Scheme.) + + +((lambda (n) + ((lambda (fact) + (fact fact n)) + (lambda (ft k) + (if (= k 1) + 1 + (* k (ft ft (- k 1))))))) + 10) + + +
    +
  1. + Check (by evaluating the expression) that this really does compute + factorials. Devise an analogous expression for computing Fibonacci + numbers. +
  2. +
  3. + Consider the following procedure, which includes mutually recursive + internal definitions: + + +(define (f x) + (define (even? n) + (if (= n 0) + true + (odd? (- n 1)))) + (define (odd? n) + (if (= n 0) + false + (even? (- n 1)))) + (even? x)) + + + Fill in the missing expressions to complete an alternative definition + of f, which uses neither internal + definitions nor letrec: + + +(define (f x) + ((lambda (even? odd?) + (even? even? odd? x)) + (lambda (ev? od? n) + (if (= n 0) true (od? ?? ?? ??))) + (lambda (ev? od? n) + (if (= n 0) false (ev? ?? ?? ??))))) + + +
  4. +
+
+
+
+ + block structure + internal declarationscope of name + scope of a nameinternal declaration +
diff --git a/xml/cn/chapter4/section1/subsection7.xml b/xml/cn/chapter4/section1/subsection7.xml new file mode 100644 index 000000000..2071073c3 --- /dev/null +++ b/xml/cn/chapter4/section1/subsection7.xml @@ -0,0 +1,1111 @@ + + + Separating Syntactic Analysis from Execution + + + + + syntactic analysis, separated from executionin metacircular evaluator + analyzing evaluator + metacircular evaluator for JavaScriptanalyzing version + + + The evaluator implemented above is simple, but it is very + metacircular evaluator for JavaScriptefficiency of + efficiencyevaluationof evaluation + inefficient, because the syntactic analysis of + + + expressions + + + components + + + is interleaved + with their execution. Thus if a program is executed many times, its + syntax is analyzed many times. Consider, for example, evaluating + (factorial 4) + factorial(4) + using the following definition of + factorial: + + factorial_4_1_7 + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n))) + + +function factorial(n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + + + + + + Each time factorial is called, the evaluator + must determine that the body is + + an if + + a conditional + + + expression and extract the predicate. Only then can it evaluate the + predicate and dispatch on its value. Each time it evaluates the expression + + (* (factorial (- n 1)) n), + + factorial(n - 1) * n, + + + or the subexpressions + + (factorial (- n 1)) + + factorial(n - 1) + + + and + (- n 1), + n - 1, + + the evaluator must perform the case analysis in + + eval + evaluate + + to determine that the expression is an application, and must extract + + its operator and operands. + its function expression and argument expressions. + + This analysis is expensive. + Performing it repeatedly is wasteful. + + + + We can transform the evaluator to be significantly more efficient by + arranging things so that syntactic analysis is performed only + once.This technique is an integral part of the compilation + process, which we shall discuss in chapter. Jonathan Rees wrote + a Scheme + interpreter like this in about 1982 for the T project + Rees, Jonathan A. + Adams, Norman I., IV + (Rees and Adams 1982). + Feeley, Marc + Marc Feeley 1986 + (see also + Lapalme, Guy + Feeley and Lapalme 1987) + independently invented this technique + in his masters thesis. We split + + eval, + evaluate, + + which takes + + an expression + a component + + and an environment, into two parts. The + procedure + function + analyze takes only the + + expression. + component. + + It performs the syntactic + analysis and returns a new + procedure + function, the + execution procedurefunctionin analyzing evaluator + execution + procedure + function, that + encapsulates the work to be done in executing the analyzed + + expression. + component. + + The execution + procedure + function + takes an environment as its + argument and completes the evaluation. This saves work because + analyze will be called only once on + + an expression, + a component, + + while the execution + procedure + function + may be called many times. + + + + With the separation into analysis and execution, + + eval + evaluate + + now becomes + + analyze_headline + +// functions from SICP JS 4.1.7 + + + + evaluate (metacircular)analyzing version + evaluate_4_1_7 + analyze + evaluate_4_1_7_simple_function + +(define (eval exp env) + ((analyze exp) env)) + + +function evaluate(component, env) { + return analyze(component)(env); +} + + + + evaluate_4_1_7_simple_function + evaluate_4_1_7 + 5 + +evaluate(parse("{ function f(x) { return x + 1; } f(4); }"), + the_global_environment); + + + + + + The result of calling analyze is the execution + + procedure + function + + to be applied to the environment. The analyze + + procedure + function + + is the same case analysis as performed by the original + + eval + evaluate + + of section, except that the + + procedures + functions + + to which we dispatch perform only analysis, not full evaluation: + + analyze_example + +analyze(parse("{ const x = 1; x + 1; }")) + (the_global_environment); + + + + analyzemetacircular + analyze + headline_4_1_1 + list_of_unassigned + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + analyze_headline + analyze_literal + analyze_variable + analyze_assignment + analyze_if + scan_out_declarations + analyze_lambda + analyze_sequence + analyze_block + analyze_return_statement + analyze_application + analyze_example + 2 + + (define (analyze exp) + (cond ((self-evaluating? exp) + (analyze-self-evaluating exp)) + ((quoted? exp) (analyze-quoted exp)) + ((variable? exp) (analyze-variable exp)) + ((assignment? exp) (analyze-assignment exp)) + ((definition? exp) (analyze-definition exp)) + ((if? exp) (analyze-if exp)) + ((lambda? exp) (analyze-lambda exp)) + ((begin? exp) (analyze-sequence (begin-actions exp))) + ((cond? exp) (analyze (cond->if exp))) + ((application? exp) (analyze-application exp)) + (else + (error "Unknown expression type - - ANALYZE" exp)))) + + +function analyze(component) { + return is_literal(component) + ? analyze_literal(component) + : is_name(component) + ? analyze_name(component) + : is_application(component) + ? analyze_application(component) + : is_operator_combination(component) + ? analyze(operator_combination_to_application(component)) + : is_conditional(component) + ? analyze_conditional(component) + : is_lambda_expression(component) + ? analyze_lambda_expression(component) + : is_sequence(component) + ? analyze_sequence(sequence_statements(component)) + : is_block(component) + ? analyze_block(component) + : is_return_statement(component) + ? analyze_return_statement(component) + : is_function_declaration(component) + ? analyze(function_decl_to_constant_decl(component)) + : is_declaration(component) + ? analyze_declaration(component) + : is_assignment(component) + ? analyze_assignment(component) + : error(component, "unknown syntax -- analyze"); +} + + + + analyze_...metacircular + + Here is the simplest syntactic analysis + + + procedure, + which handles self-evaluating expressions. + + + function, + which handles literal expressions. + + + It returns an execution + + procedure + function + + that ignores its environment argument and just returns the + + expression: + value of the literal: + + + analyze_literal_example + +// null is the empty environment (not used here) +analyze_literal(parse("true;"))(null); + + + + analyze_literal + functions_4_1_2 + analyze_literal_example + true + +(define (analyze-self-evaluating exp) + (lambda (env) exp)) + + +function analyze_literal(component) { + return env => literal_value(component); +} + + + + + + + + For a quoted expression, we can gain a little efficiency by + extracting the text of the quotation only once, in the analysis phase, + rather than in the execution phase. + + analyze_quoted + analyze_quoted_quoted + +(define (analyze-quoted exp) + (let ((qval (text-of-quotation exp))) + (lambda (env) qval))) + + + + + + + + + + Looking up + + + a variable value + + + the value of a name + + + must still be done in the execution phase, since this depends upon knowing + the environment.There is, however, an important part of the + + + variable search + + + search for a name + + + that can be done as part of the syntactic analysis. + As we will show in section, + one can determine the position in the environment structure where the + value of the variable will be found, thus obviating the need to scan the + environment for the entry that matches the variable. + + + analyze_variable_example + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +analyze_name(parse("myname;")) + (extend_environment(list("myname"), list(1), + the_global_environment)); + + + + analyze_variable + analyze_variable_example + 1 + +(define (analyze-variable exp) + (lambda (env) (lookup-variable-value exp env))) + + +function analyze_name(component) { + return env => lookup_symbol_value(symbol_of_name(component), env); +} + + + + + + + + To analyze an application, we analyze the + function expression and argument expressions + and construct an execution function that calls the + execution function of the function expression + (to obtain the actual function to be applied) and the + argument-expression execution functions (to obtain the actual arguments). + We then pass these to + execute_application, + which is the analog of apply in + section. + The function execute_application + differs from apply in that the + function body for a compound function + has already been analyzed, so there is no need to do further analysis. + Instead, we just call the execution function + for the body on the extended environment. + + analyze_application_example + analyze + +analyze(parse("x + 1;")) +(extend_environment(list("x"), list(5), the_global_environment)); + + + + execute_applicationmetacircular + analyze_application + analyze_application_example + 6 + +function analyze_application(component) { + const ffun = analyze(function_expression(component)); + const afuns = map(analyze, arg_expressions(component)); + return env => execute_application(ffun(env), + map(afun => afun(env), afuns)); +} +function execute_application(fun, args) { + if (is_primitive_function(fun)) { + return apply_primitive_function(fun, args); + } else if (is_compound_function(fun)) { + const result = function_body(fun) + (extend_environment(function_parameters(fun), + args, + function_environment(fun))); + return is_return_value(result) + ? return_value_content(result) + : undefined; + } else { + error(fun, "unknown function type -- execute_application"); + } +} + + + + + + + + + For conditionals, + we extract and analyze the predicate, consequent, and alternative at + analysis time. + + analyze_if_example + analyze + +analyze_conditional(parse("true ? 3 : 7;")) + (the_global_environment); + + + + analyze_if + analyze_if_example + 3 + +function analyze_conditional(component) { + const pfun = analyze(conditional_predicate(component)); + const cfun = analyze(conditional_consequent(component)); + const afun = analyze(conditional_alternative(component)); + return env => is_truthy(pfun(env)) ? cfun(env) : afun(env); +} + + + + + + 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 the + lambda expression + may be applied many times. + + analyze_lambda_example + analyze + +list_ref(analyze_lambda_expression(parse("x => x;")) + (the_global_environment), + 2) + (extend_environment(list("x"), list(7), + the_global_environment)); + + + + analyze_lambda + analyze_lambda_example + [ 'return_value', [ 7, null ] ] + +function analyze_lambda_expression(component) { + const params = lambda_parameter_symbols(component); + const bfun = analyze(lambda_body(component)); + return env => make_function(params, bfun, env); +} + + + + + + + Analysis of a sequence of + statements is more + involved.See exercise for + some insight into the processing of sequences. Each + statement + in the sequence is analyzed, yielding an execution + function. These execution functions + are combined to produce an execution + function that takes an environment as argument and sequentially + calls each individual execution + function with the environment as argument. + + analyze_sequence_example + analyze + +analyze_sequence(sequence_statements(parse("10; 20; 30;"))) + (the_global_environment); + + + + analyze_sequence + analyze_sequence_example + 30 + +function analyze_sequence(stmts) { + function sequentially(fun1, fun2) { + return env => { + const fun1_val = fun1(env); + return is_return_value(fun1_val) + ? fun1_val + : fun2(env); + }; + } + function loop(first_fun, rest_funs) { + return is_null(rest_funs) + ? first_fun + : loop(sequentially(first_fun, head(rest_funs)), + tail(rest_funs)); + } + const funs = map(analyze, stmts); + return is_null(funs) + ? env => undefined + : loop(head(funs), tail(funs)); +} + + + + + + + + The body of a + block is scanned only once for local declarations. + The bindings are installed in the environment when + the execution function for the block is called. + + analyze_block_example + analyze + +analyze_block(parse("{ const x = 4; x; }")) + (the_global_environment); + + + + analyze_block + list_of_unassigned + analyze_block_example + 4 + +function analyze_block(component) { + const body = block_body(component); + const bfun = analyze(body); + const locals = scan_out_declarations(body); + const unassigneds = list_of_unassigned(locals); + return env => bfun(extend_environment(locals, unassigneds, env)); +} + + + + + + + For return statements, we analyze the return expression. + The execution function for the return statement simply calls + the execution function for the return expression and wraps + the result in a return value. + + analyze_return_statement_example + analyze + +analyze_return_statement(list_ref(parse("() => x + 1;"), 2)) + (extend_environment(list("x"), list(6), the_global_environment)); + + + + analyze_return_statement + analyze_return_statement_example + [ 'return_value', [ 7, null ] ] + +function analyze_return_statement(component) { + const rfun = analyze(return_expression(component)); + return env => make_return_value(rfun(env)); +} + + + + + + + + + + + + Analyze-assignment + + The function + analyze_assignment + + + must defer actually setting the variable until the execution, when the + environment has been supplied. However, the fact that the + + + assignment-value expression + + + assignment-value expression + + + can be analyzed (recursively) during analysis is a major gain in + efficiency, because the + + + assignment-value expression + + + assignment-value expression + + + will now be analyzed only once. The same holds true for + + definitions. + constant and variable declarations. + + + analyze_assignment_example + analyze + +analyze_assignment(parse("x = x + 1;")) + (extend_environment(list("x"), list(7), the_global_environment)); + + + + analyze_assignment + analyze_assignment_example + 8 + +(define (analyze-assignment exp) + (let ((var (assignment-variable exp)) + (vproc (analyze (assignment-value exp)))) + (lambda (env) + (set-variable-value! var (vproc env) env) + 'ok))) + +(define (analyze-definition exp) + (let ((var (definition-variable exp)) + (vproc (analyze (definition-value exp)))) + (lambda (env) + (define-variable! var (vproc env) env) + 'ok))) + + +function analyze_assignment(component) { + const symbol = assignment_symbol(component); + const vfun = analyze(assignment_value_expression(component)); + return env => { + const value = vfun(env); + assign_symbol_value(symbol, value, env); + return value; + }; +} +function analyze_declaration(component) { + const symbol = declaration_symbol(component); + const vfun = analyze(declaration_value_expression(component)); + return env => { + assign_symbol_value(symbol, vfun(env), env); + return undefined; + }; +} + + + + + + + + For if expressions, + we extract and analyze the predicate, consequent, and alternative at + analysis time. + + analyze_if + 3 + +(define (analyze-if exp) + (let ((pproc (analyze (if-predicate exp))) + (cproc (analyze (if-consequent exp))) + (aproc (analyze (if-alternative exp)))) + (lambda (env) + (if (true? (pproc env)) + (cproc env) + (aproc env))))) + + + + + + Analyzing a + lambda + expression also achieves a major gain in efficiency: We analyze the + lambda + body only once, even though procedures + resulting from evaluation of the + lambda + may be applied many times. + + analyze_lambda + +(define (analyze-lambda exp) + (let ((vars (lambda-parameters exp)) + (bproc (analyze-sequence (lambda-body exp)))) + (lambda (env) (make-procedure vars bproc env)))) + + + + + + Analysis of a sequence of + expressions (as in a + begin or the body + of a lambda expression) + is more + involved.See exercise for + some insight into the processing of sequences. Each + expression + in the sequence is analyzed, yielding an execution + procedure. These execution + procedures are combined to produce an execution + procedure + that takes an environment as argument and sequentially + calls each individual execution + procedure with the environment as argument. + + analyze_sequence + +(define (analyze-sequence exps) + (define (sequentially proc1 proc2) + (lambda (env) (proc1 env) (proc2 env))) + (define (loop first-proc rest-procs) + (if (null? rest-procs) + first-proc + (loop (sequentially first-proc (car rest-procs)) + (cdr rest-procs)))) + (let ((procs (map analyze exps))) + (if (null? procs) + (error "Empty sequence - - ANALYZE")) + (loop (car procs) (cdr procs)))) + + + + + + To analyze an application, we analyze the + operator and operands + and construct an execution + procedure + that calls the + operator execution function + (to obtain the actual + procedure + to be applied) and the + operand + execution + procedures + (to obtain the actual arguments). We then pass these to + execute-application, + which is the analog of apply in + section. + Execute-application + differs from apply in that the + procedure + body for a compound + procedure + has already been analyzed, so there is no need to do further analysis. + Instead, we just call the execution + procedure + for the body on the extended environment. + + execute-applicationmetacircular + analyze_application + +(define (analyze-application exp) + (let ((fproc (analyze (operator exp))) + (aprocs (map analyze (operands exp)))) + (lambda (env) + (execute-application (fproc env) + (map (lambda (aproc) (aproc env)) + aprocs))))) + +(define (execute-application proc args) + (cond ((primitive-procedure? proc) + (apply-primitive-procedure proc args)) + ((compound-procedure? proc) + ((procedure-body proc) + (extend-environment (procedure-parameters proc) + args + (procedure-environment proc)))) + (else + (error + "Unknown procedure type - - EXECUTE-APPLICATION" + proc)))) + + + + + + + Our new evaluator uses the same data structures, syntax + + procedures, + functions, + + and + + run-time support procedures + runtime support functions + + as in sections, + , + and. + + analyze_...metacircular + + + + Extend the evaluator in this section to support the special form + analyzing evaluatorletlet + let. (See + exercise.) + + + + Extend the evaluator in this section to support + while loopanalyzingimplementing in analyzing evaluator + while loops. + (See exercise.) + + + + + + Alyssa P. Hacker doesnt understand why + analyze_...metacircular + + + analyze-sequence + + + analyze_sequence + + + needs to be so complicated. All the other analysis + + procedures + functions + + are straightforward transformations of the corresponding evaluation + + procedures + functions + + (or + + eval + evaluate + + clauses) in + section. + She expected + + + analyze-sequence + + + analyze_sequence + + + to look like this: + + + analyze_sequence_2 + analyze_sequence_example + +(define (analyze-sequence exps) + (define (execute-sequence procs env) + (cond ((null? (cdr procs)) ((car procs) env)) + (else ((car procs) env) + (execute-sequence (cdr procs) env)))) + (let ((procs (map analyze exps))) + (if (null? procs) + (error "Empty sequence - - ANALYZE")) + (lambda (env) (execute-sequence procs env)))) + + +function analyze_sequence(stmts) { + function execute_sequence(funs, env) { + if (is_null(funs)) { + return undefined; + } else if (is_null(tail(funs))) { + return head(funs)(env); + } else { + const head_val = head(funs)(env); + return is_return_value(head_val) + ? head_val + : execute_sequence(tail(funs), env); + } + } + const funs = map(analyze, stmts); + return env => execute_sequence(funs, env); +} + + + Eva Lu Ator explains to Alyssa that the version in the text does more of the + work of evaluating a sequence at analysis time. Alyssas + + sequence-execution procedure, + + + sequence-execution function, + + + rather than having the calls to the individual execution + + procedures + functions + + built in, loops through the + + procedures + functions + + in order to call them: In effect, although the individual + + expressions + statements + + in the sequence have been analyzed, the sequence itself has not been. +

+ Compare the two versions of + + analyze-sequence. + + + analyze_sequence. + + + For example, consider the common case (typical of + + procedure + function + + bodies) where the sequence has just one + + expression. + statement. + + What work will the + execution + + procedure + function + + produced by Alyssas program do? What about the execution + + procedure + function + + produced by the program in the text above? How do the two versions compare + for a sequence with two expressions? + +
+ + + + Design and carry out some experiments to compare the speed of the original + metacircular evaluator with the version in this section. Use your results + to estimate the fraction of time that is spent in analysis versus execution + for various + + procedures. + functions. + + + + syntactic analysis, separated from executionin metacircular evaluator + analyzing evaluator + metacircular evaluator for JavaScriptanalyzing version + + + + + + parse_and_evaluate_4_1_7 + evaluate_4_1_7 + +function parse_and_evaluate(input) { + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment(locals, unassigneds, + the_global_environment); + return evaluate(program, program_env); +} + + + + + evaluate_4_1_7_test_factorial + parse_and_evaluate_4_1_7 + 120 + +parse_and_evaluate(" \ +function factorial(n) { \ + return n === 1 \ + ? 1 \ + : n * factorial(n - 1); \ +} \ +factorial(5); "); + + + + evaluate_4_1_7_test_append + parse_and_evaluate_4_1_7 + [ 'b', [ 'c', [ 'd', null ] ] ] + +parse_and_evaluate(" \ +function append(xs, ys) { \ + return is_null(xs) \ + ? ys \ + : pair(head(xs), append(tail(xs), ys)); \ +} \ +tail(append(list('a', 'b'), list('c', 'd'))); "); + + + + evaluate_4_1_7_test_map + parse_and_evaluate_4_1_7 + [ 3, [ 4, [ 5, null ] ] ] + +parse_and_evaluate(" \ +function map(f, xs) { \ + return is_null(xs) \ + ? null \ + : pair(f(head(xs)), map(f, tail(xs))); \ +} \ +tail(map(x => x + 1, list(1, 2, 3, 4))); "); + + + + +
diff --git a/xml/cn/chapter4/section2/section2.xml b/xml/cn/chapter4/section2/section2.xml new file mode 100644 index 000000000..582e3f166 --- /dev/null +++ b/xml/cn/chapter4/section2/section2.xml @@ -0,0 +1,85 @@ +
+ + + Variations on a Scheme: + + Lazy Evaluation + + + + + + + lazy evaluator + delayed evaluationlazyin lazy evaluator + + + Now that we have an evaluator expressed as a + + Lisp + JavaScript + + program, we can experiment with alternative choices in + programming languagedesign of + embedded language, language design using + language design + simply by modifying the evaluator. Indeed, new languages are often + invented by first writing an evaluator that embeds the new language + within an existing high-level language. For example, if we wish to + discuss some aspect of a proposed modification to + + Lisp + JavaScript + + with another member of the + + Lisp + JavaScript + + community, we can supply an evaluator that embodies + the change. The recipient can then experiment with the new + evaluator and send back comments as further modifications. Not only + does the high-level implementation base make it easier to test and + debug the evaluator; in addition, the embedding enables the designer + to snarfSnarf: To grab, especially a large document or + file for the purpose of using it either with or without the owners + permission. Snarf down: To snarf, sometimes with the + connotation of absorbing, processing, or understanding. + (These definitions were + snarf + snarfed from + Steele, Guy Lewis Jr. + Steele et al.1983. + See also + Raymond, Eric + Raymond 1996.) features + from the underlying language, just as our embedded + + Lisp + JavaScript + + evaluator uses primitives and control structure from the underlying + + Lisp. + JavaScript. + + Only later (if ever) need the designer go to the trouble of building a + complete implementation in a low-level language or in hardware. In + this section and the next we explore some variations on + + Scheme + JavaScript + + that provide significant additional expressive power. + + + + &subsection4.2.1; + + + &subsection4.2.2; + + + &subsection4.2.3; + +
diff --git a/xml/cn/chapter4/section2/subsection1.xml b/xml/cn/chapter4/section2/subsection1.xml new file mode 100644 index 000000000..360f1cb2b --- /dev/null +++ b/xml/cn/chapter4/section2/subsection1.xml @@ -0,0 +1,399 @@ + + + Normal Order and Applicative Order + + + + + normal-order evaluationapplicative order vs. + applicative-order evaluationnormal order vs. + + + In section, where we began + our discussion of models of evaluation, we noted that + + Scheme + JavaScript + + is an applicative-order language, namely, that all the arguments to + + Scheme + JavaScript + + + procedures + functions + + are evaluated when the + + procedure + function + + is applied. In contrast, normal-order languages delay evaluation of + + procedure + function + + arguments until the actual argument values are needed. Delaying evaluation of + + procedure + function + + arguments until the last possible moment (e.g., until they are required by a + primitive operation) is called + lazy evaluation + lazy evaluation.The difference between the + lazy terminology and the normal-order + terminology is somewhat fuzzy. Generally, lazy refers to the + mechanisms of particular evaluators, while normal-order + refers to the semantics of languages, independent of any particular + evaluation strategy. But this is not a hard-and-fast distinction, and the + two terminologies are often used interchangeably. Consider the + + procedure + function + + + try_me_example + +try_me(0, head(null)); + + + + try_me + try_me_example + 1 + +(define (try a b) + (if (= a 0) 1 b)) + + +function try_me(a, b) { + return a === 0 ? 1 : b; +} + + +// Source Academy opens this program +// in lazy mode. Choose "Source §2" to +// to compare with strict mode +function try_me(a, b) { + return a === 0 ? 1 : b; +} + + + Evaluating + + + (try 0 (/ 1 0)) + generates + + + try_me(0, head(null)); + signals + + + an error in + + Scheme. + JavaScript. + + With lazy evaluation, there would be no error. Evaluating the + + expression + statement + + would return1, because the argument + + (/ 1 0) + head(null) + + would never be evaluated. + + + + An example that exploits lazy evaluation is the + + definition + declaration + + of a + + procedure + function + + unless + + unless + unless_example + +(define (unless condition usual-value exceptional-value) + (if condition exceptional-value usual-value)) + + +function unless(condition, usual_value, exceptional_value) { + return condition ? exceptional_value : usual_value; +} + + +// Source Academy opens this program +// in lazy mode. Choose "Source §2" to +// to compare with strict mode +function unless(condition, usual_value, exceptional_value) { + return condition ? exceptional_value : usual_value; +} + + + that can be used in + + expressions + statements + + such as + + xs_is_null + +const xs = null; + + + + unless_example + unless + xs_is_null + +(unless (= b 0) + (/ a b) + (begin (display "exception: returning 0") + 0)) + + +unless(is_null(xs), head(xs), display("error: xs should not be null")); + + + This wont work in an applicative-order language because both the + usual value and the exceptional value will be evaluated before + unless is called (compare + exercise). An advantage of lazy evaluation is + that some + + procedures, + functions, + + such as unless, can do useful computation + even if evaluation of some of their arguments would produce errors or + would not terminate. + + + + + If the body of a + + procedure + function + + is entered before an argument has been evaluated we say that the + + procedure + function + + is + non-strict + non-strict in that argument. If the argument is evaluated before + the body of the + + procedure + function + + is entered we say that the + + procedure + function + + is + strict + strict in that + argument.The strict versus non-strict + terminology means essentially the same as + applicative-order versus normal-order, except + that it refers to individual + + procedures + functions + + and arguments rather than to the language as a whole. At a conference on + programming languages you might hear someone say, The normal-order + language + Hassle + Hassle has certain strict primitives. Other + + procedures + functions + + take their arguments by lazy evaluation. + In a purely applicative-order language, all + + procedures + functions + + are strict in each argument. In a purely normal-order language, all compound + + procedures + functions + + are non-strict in each argument, and primitive + + procedures + functions + + may be either strict or non-strict. There are also languages (see + exercise) that give + programmers detailed control over the strictness of the + + procedures + functions + + they define. + + + + A striking example of a + + procedure + function + + that can usefully be made non-strict is + + cons + pair + + (or, in general, almost any constructor for data structures). + One can do useful computation, combining elements to form + data structures and operating on the resulting data structures, + even if the values of the elements are not known. It makes perfect + sense, for instance, to compute the length of a list without knowing + the values of the individual elements in the list. We will exploit + this idea in section to implement the + streams of chapter as lists formed of non-strict + + cons pairs. + pairs. + + + + + + + + Suppose that (in ordinary applicative-order Scheme) we define + + + Suppose that (in ordinary applicative-order JavaScript) we define + + + unless as shown above and then define + factorial in terms + of unless as + + + factorial_lazy + unless + factorial_unless_example + +(define (factorial n) + (unless (= n 1) + (* n (factorial (- n 1))) + 1)) + + +function factorial(n) { + return unless(n === 1, + n * factorial(n - 1), + 1); +} + + +// Source Academy opens this program +// in lazy mode. Choose "Source §2" to +// to compare with strict mode + +function factorial(n) { + return unless(n === 1, + n * factorial(n - 1), + 1); +} + + + + factorial_unless_example + + + +factorial(5); + + + What happens if we attempt to evaluate + + (factorial 5)? + factorial(5)? + + + Will our + + definitions + functions + + work in a normal-order language? + + (provided by GitHub user joeng03) +When evaluated in default (‘strict’) mode, the code undergoes applicative order reduction and runs into an infinite loop because it needs to evaluate the arguments of the unless function, but the factorial function does not have a terminating condition. +When evaluated in lazy mode, the code undergoes normal order reduction. It produces the correct output because the unless function is applied before its arguments are evaluated. + + + + + + Ben Bitdiddle and Alyssa P. Hacker + syntactic formprocedurefunction vs. + syntactic form vs. + disagree over the importance of lazy + evaluation for implementing things such as + unless. Ben points out that its possible + to implement unless in applicative order as a + + special + syntactic + + form. Alyssa counters that, if one did that, + unless would be merely syntax, not a + + procedure + function + + that could be used in conjunction with higher-order + procedures. + functions. + + Fill in the details on both sides of the argument. + + + Show how to implement unless as a derived + expression (like cond or + let), and give an example of a situation + where it might be useful to have unless + available as a procedure, rather than as a special form. + + + Show how to implement unless as a derived + component (like operator combination), + by catching in evaluate + applications whose function expression is the name + unless. + Give an example of a situation where it might be useful to have + unless available as a function, + rather than as a syntactic form. + + + + + normal-order evaluationapplicative order vs. + applicative-order evaluationnormal order vs. + diff --git a/xml/cn/chapter4/section2/subsection2.xml b/xml/cn/chapter4/section2/subsection2.xml new file mode 100644 index 000000000..8c49ab288 --- /dev/null +++ b/xml/cn/chapter4/section2/subsection2.xml @@ -0,0 +1,1687 @@ + + + An Interpreter with Lazy Evaluation + + + + headline_4_1_1_lazy + +// functions from SICP JS 4.1.1 +// with modifications for lazy evaluation +// according to SICP JS 4.2.2 + + + + + functions_4_1_1_lazy + headline_4_1_1_lazy + eval_lazy + actual_value_lazy + apply_lazy + eval_if_lazy + eval_sequence + eval_block + eval_return + eval_assignment + eval_definition + force_it_lazy + delay_it_lazy + + + + + + + In this section we will implement a normal-order language that is + the same as + + Scheme + JavaScript + + except that compound + + procedures + functions + + are non-strict in each argument. Primitive + + procedures + functions + + will still be strict. It is not difficult to modify the evaluator of + section so that the language it + interprets behaves this way. Almost all the required changes center around + + procedure + function + + application. + + + + The basic idea is that, when applying a + + procedure, + function, + + the interpreter must determine which arguments are to be evaluated and which + are to be delayed. The delayed arguments are not evaluated; instead, they + are transformed into objects called + thunk + thunks.The word thunk was invented by an informal + thunkorigin of name + working group that was discussing the implementation of call-by-name + Algolthunks + in Algol 60. They observed that most of the analysis of (thinking + about) the expression could be done at compile time; thus, at run + time, the expression would already have been thunk about + Ingerman, Peter + (Ingerman et al.1960). + The thunk must contain the information required to produce the value + of the argument when it is needed, as if it had been evaluated at + the time of the application. Thus, the thunk must contain the + argument expression and the environment in + which the + + procedure + function + + application is being evaluated. + + + + The process of evaluating the expression in a thunk is called + forcingthunkof thunk + thunkforcing + forcing.This is analogous to the + + + use of + forceforcing a thunk vs. + force + on + + + forcing of + + + the delayed objects that were introduced in chapter to + represent streams. The critical difference between what we are + doing here and what we did in chapter is that we are building + delaying and forcing into the evaluator, and thus making this uniform + and automatic throughout the language. + In general, a thunk will be forced only when its value is needed: + when it is passed to a primitive + + procedure + function + + that will use the value of the thunk; when it is the value of a predicate of + a conditional; and when it is the value of + + an operator + a function expression + + that is about to be + applied as a + + procedure. + function. + + One design choice we have available is whether or not to + memoizationthunkof thunks + memoize thunks, similar to the optimization for streams in + section. With memoization, the first + time a thunk is forced, it stores the value that is computed. Subsequent + forcings simply return the stored value without repeating the computation. + Well make our interpreter memoize, because this is more efficient for + many applications. There are tricky considerations here, + however.Lazy evaluation combined with memoization is sometimes + referred to as + call-by-need argument passing + call-by-need argument passing, in contrast to + call-by-name argument passing. + call-by-name argument passing + (Call-by-name, introduced in + Algolcall-by-name argument passing + Algol 60, is similar to non-memoized lazy + evaluation.) As language designers, we can build our evaluator to memoize, + not to memoize, or leave this an option for programmers + (exercise). As you might + expect from chapter, these choices raise issues that become both + subtle and confusing in the presence of assignments. (See + exercises + and.) + An excellent article by + Clinger, William + Clinger (1982) attempts to clarify the + multiple dimensions of confusion that arise here. + + thunk + + + + Modifying the evaluator + + + + + The main difference between the lazy evaluator and the one in + section is in the handling of + procedure + function + applications in + + eval + evaluate + + and + apply. + + + + evaluate (lazy) + + + The application? clause of + evaluate (lazy) + eval becomes + + + The is_application + clause of + evaluate (lazy) + evaluate becomes + + + + eval_lazy_example + functions_4_1_1_lazy + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +(define the-global-environment (setup-environment)) +(eval (read) the-global-environment) + + +const my_program = parse("1; { true; 3; }"); +evaluate(my_program, the_empty_environment); + + + + eval_lazy + eval_lazy_example + 3 + +((application? exp) +(apply (actual-value (operator exp) env) + (operands exp) + env)) + + +: is_application(component) +? apply(actual_value(function_expression(component), env), + arg_expressions(component), env) + + +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(actual_value(function_expression(component), env), + 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"); +} + + + This is almost the same as the + + application? + is_application + + + clause of + + + eval + + + evaluate + + + in section. For lazy evaluation, + however, we call apply with the + + operand + argument + + expressions, rather than the arguments produced by evaluating them. Since + we will need the environment to construct thunks if the arguments are to be + delayed, we must pass this as well. We still evaluate the + + operator, + function expression, + + because + apply needs the actual + + procedure + function + + to be applied in order to dispatch on its type (primitive versus compound) + and apply it. + + + + Whenever we need the actual value of an expression, we use + + actual_value_lazy_example + functions_4_1_1_lazy + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +actual_value(parse("1 + 2;"), the_global_environment); + + + + actual_value + actual_value_lazy + actual_value_lazy_example + 3 + +(define (actual-value exp env) + (force-it (eval exp env))) + + +function actual_value(exp, env) { + return force_it(evaluate(exp, env)); +} + + + instead of just + + + eval, + + + evaluate, + + + so that if the expressions value is a thunk, it will be forced. + + + + Our new version of apply is also almost the + same as the version in section. + The difference is that + + + eval + + + evaluate + + + has passed in unevaluated + + + operand + + + argument + + + expressions: For primitive + + procedures + functions + + (which are strict), we evaluate all the arguments before applying the + primitive; for compound + + procedures + functions + + (which are non-strict) we delay all the + arguments before applying the + procedure. + function. + + apply_lazy_example + functions_4_1_1_lazy + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const plus = list("primitive", (x, y) => x + y); +apply(plus, list(list("literal", 1), list("literal", 2)), the_global_environment); + + + + apply (lazy) + apply_lazy + list_of_arg_values + apply_lazy_example + 3 + +(define (apply procedure arguments env) + (cond ((primitive-procedure? procedure) + (apply-primitive-procedure + procedure + (list-of-arg-values arguments env))) ; changed + ((compound-procedure? procedure) + (eval-sequence + (procedure-body procedure) + (extend-environment + (procedure-parameters procedure) + (list-of-delayed-args arguments env) ; changed + (procedure-environment procedure)))) + (else + (error + "Unknown procedure type - - APPLY" procedure)))) + + +function apply(fun, args, env) { + if (is_primitive_function(fun)) { + return apply_primitive_function( + fun, + list_of_arg_values(args, env)); // changed + } else if (is_compound_function(fun)) { + const result = evaluate( + function_body(fun), + extend_environment( + function_parameters(fun), + list_of_delayed_args(args, env), // changed + function_environment(fun))); + return is_return_value(result) + ? return_value_content(result) + : undefined; + } else { + error(fun, "unknown function type -- apply"); + } +} + + + The + procedures + functions + that process the arguments are just like + + + list-of-values + + + list_of_values + + + from section, + except that + + + list-of-delayed-args + + + list_of_delayed_args + + + delays the arguments instead of evaluating them, and + + + list-of-arg-values + + + list_of_arg_values + + + uses + + + actual-value + + + actual_value + + + instead of + + + eval: + + + evaluate: + + + + list_of_arg_values + list_of_delayed_args + list_of_arg_values + +(define (list-of-arg-values exps env) + (if (no-operands? exps) + '() + (cons (actual-value (first-operand exps) env) + (list-of-arg-values (rest-operands exps) + env)))) + +(define (list-of-delayed-args exps env) + (if (no-operands? exps) + '() + (cons (delay-it (first-operand exps) env) + (list-of-delayed-args (rest-operands exps) + env)))) + + +function list_of_arg_values(exps, env) { + return map(exp => actual_value(exp, env), exps); +} +function list_of_delayed_args(exps, env) { + return map(exp => delay_it(exp, env), exps); +} + + + + + + The other place we must change the evaluator is in the handling of + + + if, + + + conditionals, + + + where we must use + + + actual-value + + + actual_value + + + instead of + + + eval + + + evaluate + + + to get the value of the predicate + expression before testing whether it is true or false: + + eval_if_lazy_example + functions_4_1_1_lazy + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +const my_cond_expr = parse("true ? 1 : 2;"); +eval_conditional(my_cond_expr, the_empty_environment); + + + + eval_conditional (lazy) + eval_if_lazy + eval_if_lazy_example + 1 + +(define (eval-if exp env) + (if (true? (actual-value (if-predicate exp) env)) + (eval (if-consequent exp) env) + (eval (if-alternative exp) env))) + + +function eval_conditional(component, env) { + return is_truthy(actual_value(conditional_predicate(component), env)) + ? evaluate(conditional_consequent(component), env) + : evaluate(conditional_alternative(component), env); +} + + + + + + Finally, we must change the + driver looplazyin lazy evaluator + + + driver-loop + + + driver_loop + + + procedure + function + + (from section) to use + + + actual-value + + + actual_@value + + + instead of + + + eval, + + + evaluate, + + + so that if a delayed value is propagated back to the + + + read-eval-print loop, + + + read-evaluate-print loop, + + + it will be forced before being printed. + We also change the prompts to indicate that + this is the lazy evaluator: + + promptslazy evaluator + driver_loopfor lazy evaluator + driver_loop_lazy + functions_4_1_1_lazy + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + user_print + user_read + driver_loop_lazy_example + +(define input-prompt ";;; L-Eval input:") +(define output-prompt ";;; L-Eval value:") + +(define (driver-loop) + (prompt-for-input input-prompt) + (let ((input (read))) + (let ((output + (actual-value input the-global-environment))) + (announce-output output-prompt) + (user-print output))) + (driver-loop)) + + +const input_prompt = "L-evaluate input: "; +const output_prompt = "L-evaluate value: "; + +function driver_loop(env) { + const input = user_read(input_prompt); + if (is_null(input)) { + display("evaluator terminated"); + } else { + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment(locals, unassigneds, env); + const output = actual_value(program, program_env); + user_print(output_prompt, output); + return driver_loop(program_env); + } +} + + +const input_prompt = "L-evaluate input: "; +const output_prompt = "L-evaluate value: "; + +function driver_loop(env) { + const input = user_read(input_prompt); + if (is_null(input)) { + display("--- evaluator terminated ---", ""); + } else { + display("----------------------------", + input_prompt + "\n" + input + "\n"); + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment(locals, unassigneds, env); + const output = actual_value(program, program_env); + user_print(output_prompt, output); + return driver_loop(program_env); + } +} + + + + + + With these changes made, we can start the evaluator and test it. The + successful evaluation of the + + + try + + + try_me + + + expression + discussed in section indicates + that the interpreter is performing lazy evaluation: + + driver_loop_lazy_example + driver_loop_lazy + +(define the-global-environment (setup-environment)) + +(driver-loop) + + +const the_global_environment = setup_environment(); +driver_loop(the_global_environment); + + +driver_loop(the_global_environment); + +// L-evaluate input: +// function try_me(a, b) { return a === 0 ? 1 : b; } +// L-evaluate value: +// undefined + +// L-evaluate input: +// try_me(0, head(null)); +// L-evaluate value: +// 1 + + + + +;;; L-Eval input: +(define (try a b) +(if (= a 0) 1 b)) +;;; L-Eval value: +ok + + +L-evaluate input: + + +function try_me(a, b) { + return a === 0 ? 1 : b; +} + + +L-evaluate value: +undefined + + + + +;;; L-Eval input: +(try 0 (/ 1 0)) +;;; L-Eval value: +1 + + +L-evaluate input: + + + try_me(0, head(null)); + + +L-evaluate value: +1 + + + + + + + Representing thunks + + + thunkimplementation of + + + Our evaluator must arrange to create thunks when + + procedures + functions + + are applied to arguments and to force these thunks later. A thunk must + package an expression together with the environment, so that the argument + can be produced later. To force the thunk, we simply extract the expression + and environment from the thunk and evaluate the expression in the + environment. We use + + + actual-value + + + actual_value + + + rather than + + + eval + + + evaluate + + + so that in case the value of the expression is itself a thunk, we will force + that, and so on, until we reach something that is not a thunk: + + force_it + force_it_lazy_v1 + +(define (force-it obj) + (if (thunk? obj) + (actual-value (thunk-exp obj) (thunk-env obj)) + obj)) + + +function force_it(obj) { + return is_thunk(obj) + ? actual_value(thunk_exp(obj), thunk_env(obj)) + : obj; +} + + + + + + One easy way to package an expression with an environment is to make a list + containing the expression and the environment. Thus, we create a thunk as + follows: + + delay_it + delay_it_lazy + eval_lazy_example + +(define (delay-it exp env) + (list 'thunk exp env)) + +(define (thunk? obj) + (tagged-list? obj 'thunk)) + +(define (thunk-exp thunk) (cadr thunk)) + +(define (thunk-env thunk) (caddr thunk)) + + +function delay_it(exp, env) { + return list("thunk", exp, env); +} +function is_thunk(obj) { + return is_tagged_list(obj, "thunk"); +} +function thunk_exp(thunk) { return head(tail(thunk)); } + +function thunk_env(thunk) { return head(tail(tail(thunk))); } + + + + + + Actually, what we want for our interpreter is not quite this, but + rather thunks that have been memoized. + + When a thunk is forced, we will turn it into an evaluated thunk by replacing + the stored expression with its value and changing the + thunk tag so that it can be recognized as + already evaluated.Notice that we also erase the + env from the thunk once the expressions + value has been computed. This makes no difference in the values returned by + the interpreter. It does help save space, however, because removing the + reference from the thunk to the env once it is + no longer needed allows this structure to be + garbage collectionmemoization and + memoizationgarbage collection and + garbage-collected and its space + recycled, as we will discuss in + section. + + Similarly, we could have allowed unneeded environments in the memoized + delayed objects of section + to be garbage-collected, by having + + + memo-proc + + + memo + + + do something like + + (set! proc '()) + fun = null; + + to discard the + + procedure + proc + + function + fun + + + (which includes the environment in which the + + + delay + + + lambda expression + that makes up the tail of the stream + + + was evaluated) after storing its + value. + + + + force_itmemoized version + force_it_lazy + eval_lazy_example + +(define (evaluated-thunk? obj) + (tagged-list? obj 'evaluated-thunk)) + +(define (thunk-value evaluated-thunk) (cadr evaluated-thunk)) + +(define (force-it obj) + (cond ((thunk? obj) + (let ((result (actual-value + (thunk-exp obj) + (thunk-env obj)))) + (set-car! obj 'evaluated-thunk) + (set-car! (cdr obj) result) ; replace exp with its value + (set-cdr! (cdr obj) '()) ; forget unneeded env + result)) + ((evaluated-thunk? obj) + (thunk-value obj)) + (else obj))) + + +function is_evaluated_thunk(obj) { + return is_tagged_list(obj, "evaluated_thunk"); +} +function thunk_value(evaluated_thunk) { + return head(tail(evaluated_thunk)); +} + +function force_it(obj) { + if (is_thunk(obj)) { + const result = actual_value(thunk_exp(obj), thunk_env(obj)); + set_head(obj, "evaluated_thunk"); + set_head(tail(obj), result); // replace exp with its value + set_tail(tail(obj), null); // forget unneeded env + return result; + } else if (is_evaluated_thunk(obj)) { + return thunk_value(obj); + } else { + return obj; + } +} + + + Notice that the same + + + delay-it + + + delay_it + + + procedure + function + works both with and + without memoization.thunkimplementation of + + + + Suppose we type in the following + + + definitions + + + declarations + + + to the lazy evaluator: + + exercise_4_27_a + exercise_4_27 + +(define count 0) + +(define (id x) + (set! count (+ count 1)) + x) + + +let count = 0; +function id(x) { + count = count + 1; + return x; +} + + + + + Give the missing values in the following sequence of interactions, and explain + your answers.This exercise demonstrates that the interaction between + lazy evaluation and side effects can be very confusing. This is just what you + might expect from the discussion in chapter. + + exercise_4_27 + driver_loop_lazy + +(define w (id (id 10))) + + +const w = id(id(10)); + + +const w = id(id(10)); + + +driver_loop(the_global_environment); + +// L-evaluate input: +// let count = 0; function id(x) { count = count + 1; return x; } +// L-evaluate value: +// undefined + +// L-evaluate input: +// const w = id(id(10)); +// L-evaluate value: +// undefined + +// L-evaluate input: +// count; +// L-evaluate value: +// <response> + +// L-evaluate input: +// w; +// L-evaluate value: +// <response> + +// L-evaluate input: +// count; +// L-evaluate value: +// <response> + + + + +;;; L-Eval input: +count +;;; L-Eval value: +response + + +L-evaluate input: + + +count; + + +L-evaluate value: +response + + + + +;;; L-Eval input: +w +;;; L-Eval value: +response + + +L-evaluate input: + + +w; + + +L-evaluate value: +response + + + + +;;; L-Eval input: +count +;;; L-Eval value: +response + + +L-evaluate input: + + +count; + + +L-evaluate value: +response + + + + + + + + Eval + + + The function + evaluate + + + uses + + + actual-value + + + actual_value + + + rather than + + + eval + + + evaluate + + + to evaluate the + + operator + function expression + + before passing it to + apply, in order to force the value of the + + operator. + function expression. + + Give an example that demonstrates the need for this forcing. + + + + + Exhibit a program that you would expect to run much more slowly without + memoization than with memoization. Also, consider the following + interaction, where the id + + procedure + function + + is defined as in exercise and + count starts at 0: + + exercise_4_29 + driver_loop_lazy + +(define (square x) + (* x x)) + + +;;; L-Eval input: +(square (id 10)) +;;; L-Eval value: +response +;;; L-Eval input: +count +;;; L-Eval value: +response + + +function square(x) { + return x * x; +} + + +driver_loop(the_global_environment); + +// L-evaluate input: +// let count = 0; function id(x) { count = count + 1; return x; } +// L-evaluate value: +// undefined + +// L-evaluate input: +// function square(x) { return x * x; } +// L-evaluate value: +// undefined + +// L-evaluate input: +// square(id(10)); +// L-evaluate value: +// <response> + +// L-evaluate input: +// count; +// L-evaluate value: +// <response> + + + + +L-evaluate input: + + +square(id(10)); + + +L-evaluate value: +response + + + + +L-evaluate input: + + +count; + + +L-evaluate value: +response + + + Give the responses both when the evaluator memoizes and when it does not. + + + + + + + + Cy D. Fect, a reformed C programmer, is worried that some side effects + may never take place, because the lazy evaluator doesnt force the + expressions in a sequence. + Since the value of an expression in a sequence + other than the last one is not used (the expression is there only for + its effect, such as assigning to a variable or printing), there can be + no subsequent use of this value (e.g., as an argument to a primitive + procedure) that will cause it to be forced. Cy thus thinks that when + evaluating sequences, we must force all expressions in the sequence + except the final one. He proposes to modify + eval-sequence + from section to use + actual-value + rather than eval: + + + Cy D. Fect, a reformed C programmer, is worried that some side effects + may never take place, because the lazy evaluator doesnt force the + statements in a sequence. + Since the value of a statement in a sequence + may not be used (the statement may be there only for + its effect, such as assigning to a variable or printing), there may be + no subsequent use of this value (e.g., as an argument to a primitive + function) that will cause it to be forced. + Cy thus thinks that when + evaluating sequences, we must force all statements in the sequence. + He proposes to modify + evaluate_sequence + from section to use + actual_value + rather than + evaluate: + + + + exercise_4_30_a + +(define (eval-sequence exps env) + (cond ((last-exp? exps) (actual-value (first-exp exps) env)) + (else (actual-value (first-exp exps) env) + (eval-sequence (rest-exps exps) env)))) + + +function eval_sequence(stmts, env) { + if (is_empty_sequence(stmts)) { + return undefined; + } else if (is_last_statement(stmts)) { + return actual_value(first_statement(stmts), env); + } else { + const first_stmt_value = + actual_value(first_statement(stmts), env); + if (is_return_value(first_stmt_value)) { + return first_stmt_value; + } else { + return eval_sequence(rest_statements(stmts), env); + } + } +} + + +
    +
  1. + Ben Bitdiddle thinks Cy is wrong. He shows Cy the + + + for-each + + + for_each + + + + procedure + function + + described in exercise, which gives an + important example of a sequence with side effects: + + for_each + exercise_4_30_b + driver_loop_lazy + +(define (for-each proc items) + (if (null? items) + 'done + (begin (proc (car items)) + (for-each proc (cdr items))))) + + +function for_each(fun, items) { + if (is_null(items)){ + return "done"; + } else { + fun(head(items)); + for_each(fun, tail(items)); + } +} + + +driver_loop(the_global_environment); + +// L-evaluate input: +// function for_each(fun, items) { if (is_null(items)){ return undefined; } else { fun(head(items)); for_each(fun, tail(items)); } } +// L-evaluate value: +// undefined + +// L-evaluate input: +// for_each(x => display(x), list(57, 321, 88)); +// 57 +// 321 +// 88 +// L-evaluate value: +// undefined + + + He claims that the evaluator in the text (with the original + + + eval-sequence) + + + eval_sequence) + + + handles this correctly: + + +;;; L-Eval input: + + +(for-each (lambda (x) (newline) (display x)) + (list 57 321 88)) + + +57 +321 +88 +;;; L-Eval value: +done + + +L-evaluate input: + + +for_each(display, list(57, 321, 88)); + + +57 +321 +88 +L-evaluate value: +"done" + + + Explain why Ben is right about the behavior of + + + for-each. + + + for_each. + + +
  2. +
  3. + Cy agrees that Ben is right about the + + + for-each + + + for_each + + + example, but says that thats not the kind of program he + was thinking about when he proposed his change to + + + eval-sequence. + + + eval_sequence. + + + He + + defines + declares + + the following two + procedures + functions + in the lazy evaluator: + + exercise_4_30_c + driver_loop_lazy + +(define (p1 x) + (set! x (cons x '(2))) + x) + +(define (p2 x) + (define (p e) + e + x) + (p (set! x (cons x '(2))))) + + +function f1(x) { + x = pair(x, list(2)); + return x; +} + +function f2(x) { + function f(e) { + e; + return x; + } + return f(x = pair(x, list(2))); +} + + + What are the values of + + (p1 1) + f1(1) + + and + + (p2 1) + f2(1) + + with the original + + + eval-sequence? + + + eval_sequence? + + + What would the values be with Cys proposed change to + + + eval-sequence? + + + eval_sequence? + + +
  4. +
  5. + Cy also points out that changing + + + eval-sequence + + + eval_sequence + + + as he proposes does not affect the behavior of the example in part a. + Explain why this is true. +
  6. +
  7. + How do you think sequences + ought to be treated in the lazy evaluator? + Do you like Cys approach, the approach in the text, or some other + approach? +
  8. +
+
+ + + + The approach taken in this section is somewhat unpleasant, because it + makes an incompatible change to + Scheme. + JavaScript. + It might be nicer to implement lazy evaluation as an + upward compatibility + upward-compatible extension, that is, so that ordinary + + Scheme + JavaScript + + programs will work as before. We can do this by + + extending the syntax of procedure + introducing optional parameter declaration as a new + syntactic form inside function + + declarations to let the user control whether or not arguments are to be + delayed. While were at it, we may as well also give the user the + choice between delaying with and without memoization. For example, the + + + definition + + + declaration + + + + +(define (f a (b lazy) c (d lazy-memo)) + $\ldots$) + + +function f(a, b, c, d) { + parameters("strict", "lazy", "strict", "lazy_memo"); + $\ldots$ +} + + + would define f to be a + + procedure + function + + of four arguments, where the first and third arguments are evaluated when the + + procedure + function + + is called, the second argument is delayed, and the fourth argument is both + delayed and memoized. + + + Thus, ordinary procedure definitions will produce the same behavior as + ordinary Scheme, + while adding the + lazy-memo + declaration to each parameter of every compound procedure + will produce the behavior of the lazy evaluator defined in this section. + Design and implement the changes required to produce such an extension to + Scheme. You will have to implement new syntax procedures + to handle the new syntax for define. + + + You can assume that the parameter declaration is always + the first statement in the body of a function declaration, + and if it is omitted, all parameters are strict. + Thus, ordinary function declaration + will produce the same behavior as ordinary JavaScript, + while adding the + "lazy_memo" + declaration to each parameter of every compound function + will produce the behavior of the lazy evaluator defined in this section. + Design and implement the changes required to produce such an extension to + JavaScript. The parse function will + treat parameter declarations as function applications, so you need to + modify apply to dispatch to your + implementation of the new syntactic form. + + + You must also arrange for + + + eval + + + evaluate + + + or apply to determine when arguments are to be + delayed, and to force or delay arguments accordingly, and you must arrange + for forcing to memoize or not, as appropriate. + + + + lazy evaluator + + + + + parse_and_evaluate_lazy + functions_4_1_1_lazy + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + +function parse_and_evaluate(input) { + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment(locals, unassigneds, + the_global_environment); + return actual_value(program, program_env); +} + + + + + parse_and_evaluate_test_factorial_lazy + parse_and_evaluate_lazy + 120 + +parse_and_evaluate(" \ +function factorial(n) { \ + return n === 1 \ + ? 1 \ + : n * factorial(n - 1); \ +} \ +factorial(5); "); + + + + parse_and_evaluate_test_append_lazy + parse_and_evaluate_lazy + [ 'b', [ 'c', [ 'd', null ] ] ] + +parse_and_evaluate(" \ +function append(xs, ys) { \ + return is_null(xs) \ + ? ys \ + : pair(head(xs), append(tail(xs), ys)); \ +} \ +tail(append(list('a', 'b'), list('c', 'd'))); "); + + + + parse_and_evaluate_test_map_lazy + parse_and_evaluate_lazy + [ 3, [ 4, [ 5, null ] ] ] + +parse_and_evaluate(" \ +function map(f, xs) { \ + return is_null(xs) \ + ? null \ + : pair(f(head(xs)), map(f, tail(xs))); \ +} \ +tail(map(x => x + 1, list(1, 2, 3, 4))); "); + + + + parse_and_evaluate_test_try_me_lazy + parse_and_evaluate_lazy + 1 + +parse_and_evaluate(" \ +function try_me(a, b) { \ + return a === 0 ? 1 : b; \ +} \ +try_me(0, head(null)); "); + + + + +
diff --git a/xml/cn/chapter4/section2/subsection3.xml b/xml/cn/chapter4/section2/subsection3.xml new file mode 100644 index 000000000..6fe67e3b5 --- /dev/null +++ b/xml/cn/chapter4/section2/subsection3.xml @@ -0,0 +1,518 @@ + + + Streams as Lazy Lists + + + + + stream(s)implemented as lazy lists + lazy list + list(s)lazy + lazy pair + pair(s)lazy + + + In section, we showed how to + implement streams as delayed lists. + + + special formprocedurefunction vs. + specialsyntactic form vs. + + + delayed expressionlazy evaluation and + + + + + We introduced special forms delay and + cons-stream, which allowed us + + + We used a + lambda expressionlazy evaluation and + lambda expression + + + to construct a + promise to evaluatelazy evaluation and + promise to compute the + + cdr + tail + + of a stream, without actually fulfilling that promise until later. + + + We could use this general technique of introducing special forms + whenever we need more control over the evaluation process, but this is + awkward. For one thing, a special form is not a first-class object + like a procedure, so we cannot use it together with higher-order + procedures.This is precisely the issue with the + unless procedure, + as in + exercise. + Additionally, we were forced to create streams as a new kind of data + object similar but not identical to lists, and this required us to + reimplement many ordinary list operations (map, + append, and so on) for use with streams. + + + We were forced to create streams as a new kind of data object similar + but not identical to lists, and this required us to reimplement many + ordinary list operations (map, + append, and so on) for use with streams. + + + + + + With lazy evaluation, streams and lists can be identical, so there is + no need for special forms or for + separate list and stream operations. All we need to do is to arrange matters + so that + + cons + pair + + is non-strict. One way to accomplish this is to extend the lazy evaluator + to allow for non-strict primitives, and to implement + + cons + pair + + as one of these. An easier way is to recall + (section) that there is no fundamental need + to implement + + cons + pair + + as a primitive at all. Instead, we can represent + pair(s)functional representation of + pairs as + proceduresfunctions:This + is the + + procedural + functional + + representation described in exercise. + Essentially any + + procedural + functional + + representation (e.g., a message-passing implementation) would do as well. + Notice that we can install these definitions in the lazy evaluator simply by + typing them at the driver loop. If we had originally included + + cons, + pair, + + + car, + head, + + and + + cdr + tail + + as primitives in the global environment, they will be redefined. (Also see + exercises + and.) + + pair_lazy_header + +const my_pair_lazy = ` + + + + pair_lazy_footer + + +head(tail(pair(1, pair(3, 2)))); +`; + + + + pair_lazy_example + parse_and_evaluate_lazy + pair_lazy_header + pair_lazy + pair_lazy_footer + 3 + +parse_and_evaluate(my_pair_lazy); + + + + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + pair_lazy + +(define (cons x y) + (lambda (m) (m x y))) + +(define (car z) + (z (lambda (p q) p))) + +(define (cdr z) + (z (lambda (p q) q))) + + +function pair(x, y) { + return m => m(x, y); +} +function head(z) { + return z((p, q) => p); +} +function tail(z) { + return z((p, q) => q); +} + + + + + + In terms of these basic operations, the standard definitions of the list + operations will work with infinite lists (streams) as well as finite ones, + and the stream operations can be implemented as list operations. Here are + some examples: + + list_lib_test_header + +const my_list_lib_test = ` + + + + list_lib_test_footer + + +list_ref(integers, 17); +`; + + + + list_lib_test + parse_and_evaluate_lazy + list_lib_test_header + pair_lazy + list_library_lazy + list_lib_test_header + list_lib_test_footer + 18 + +parse_and_evaluate(my_list_lib_test); + + + + list_ref + map + scale_list + add_lists + ones (infinite stream)lazy-list version + integers (infinite stream)lazy-list version + list_library_lazy + +(define (list-ref items n) + (if (= n 0) + (car items) + (list-ref (cdr items) (- n 1)))) + +(define (map proc items) + (if (null? items) + '() + (cons (proc (car items)) + (map proc (cdr items))))) + +(define (scale-list items factor) + (map (lambda (x) (* x factor)) + items)) + +(define (add-lists list1 list2) + (cond ((null? list1) list2) + ((null? list2) list1) + (else (cons (+ (car list1) (car list2)) + (add-lists (cdr list1) (cdr list2)))))) + +(define ones (cons 1 ones)) + +(define integers (cons 1 (add-lists ones integers))) + + +function list_ref(items, n) { + return n === 0 + ? head(items) + : list_ref(tail(items), n - 1); +} +function map(fun, items) { + return is_null(items) + ? null + : pair(fun(head(items)), + map(fun, tail(items))); +} +function scale_list(items, factor) { + return map(x => x * factor, items); +} +function add_lists(list1, list2) { + return is_null(list1) + ? list2 + : is_null(list2) + ? list1 + : pair(head(list1) + head(list2), + add_lists(tail(list1), tail(list2))); +} +const ones = pair(1, ones); +const integers = pair(1, add_lists(ones, integers)); + + + + +;;; L-Eval input: + + +(list-ref integers 17) + + +;;; L-Eval value: +18 + + +L-evaluate input: + + +list_ref(integers, 17); + + +L-evaluate value: +18 + + + + + + + Note that these lazy lists are even lazier than the streams of + chapter: The + + car + head + + of the list, as well as the + + cdr, + tail, + + is delayed.This permits us to create delayed versions of more + general kinds of + list structures, not just sequences. + Hughes, R. J. M. + Hughes 1990 + discusses some + applications of + lazy treetreelazy + lazy trees. + In fact, even accessing the + + car + head + + or + + cdr + tail + + of a lazy pair need not force the value of a list element. The value will be + forced only when it is really needede.g., for use as the argument + of a primitive, or to be printed as an answer. + + + + Lazy pairs also help with the problem that arose with streams in + section, where we + found that formulating stream models of systems with loops may require us to + sprinkle our programs with + + + delayexplicit vs.automatic + delayed evaluationexplicit vs.automatic + explicit + delay + operations, beyond the ones supplied by + cons-stream. + + + additional lambda expressions for + delayed evaluationexplicit vs.automatic + delayed expressionexplicit vs.automatic + delays, beyond the ones required to + construct a stream pair. + + + With lazy evaluation, all arguments to + + procedures + functions + + are delayed uniformly. For instance, we can implement + + procedures + functions + + to integrate lists and solve differential equations as we originally + intended in section: + + lazy_integral_header + +const my_integral = ` + + + + lazy_integral_footer + + +list_ref(solve(x => x, 1, 0.1), 10); +`; + + + + lazy_integral_test + parse_and_evaluate_lazy + lazy_integral_header + pair_lazy + list_library_lazy + lazy_integral + lazy_integral_footer + 2.5937424601 + +parse_and_evaluate(my_integral); + + + + integrallazy-list version + solve differential equationlazy-list version + lazy_integral + +(define (integral integrand initial-value dt) + (define int + (cons initial-value + (add-lists (scale-list integrand dt) + int))) + int) + + (define (solve f y0 dt) + (define y (integral dy y0 dt)) + (define dy (map f y)) + y) + + + ;;; L-Eval input: + (list-ref (solve (lambda (x) x) 1 0.001) 1000) + ;;; L-Eval value: + 2.716924 + + +function integral(integrand, initial_value, dt) { + const int = pair(initial_value, + add_lists(scale_list(integrand, dt), + int)); + return int; +} +function solve(f, y0, dt) { + const y = integral(dy, y0, dt); + const dy = map(f, y); + return y; +} + + + + +L-evaluate input: + + +list_ref(solve(x => x, 1, 0.001), 1000); + + +L-evaluate value: +2.716924 + + + + + + + Give some examples that illustrate the difference between the streams + of chapter and the lazier lazy lists described in + this section. How can you take advantage of this extra laziness? + + + + Ben Bitdiddle tests the lazy list implementation given above by + evaluating the expression + + +(car '(a b c)) + + +head(list("a", "b", "c")); + + + To his surprise, this produces an error. After some thought, he realizes + that the lists obtained + + + by reading in quoted expressions + + + from the primitive list function + + + are different from the lists manipulated by the new definitions of + + cons, + pair, + + + car, + head, + + and + + cdr. + tail. + + Modify + + + the evaluators treatment of + quoted expressions so that quoted lists + + + the evaluator such that applications of the primitive + list + function + + + typed at the driver loop will produce true lazy lists. + + + + + Modify the driver loop for the evaluator so that lazy pairs and lists will + print in some reasonable way. (What are you going to do about infinite + lists?) You may also need to modify the representation of lazy pairs so + that the evaluator can identify them in order to print them. + + + + delayed evaluationlazyin lazy evaluator + stream(s)implemented as lazy lists + lazy list + list(s)lazy + lazy pair + pair(s)lazy + + diff --git a/xml/cn/chapter4/section3/section3.xml b/xml/cn/chapter4/section3/section3.xml new file mode 100644 index 000000000..78533f4b8 --- /dev/null +++ b/xml/cn/chapter4/section3/section3.xml @@ -0,0 +1,277 @@ +
+ + + Variations on a Scheme: + + Nondeterministic Computing + + + + + + + nondeterministic computing + + + In this section, we extend the + + Scheme + JavaScript + + evaluator to support a + programming paradigm called nondeterministic computing by + building into the evaluator a facility to support + automatic search + automatic search. + This is a much more profound change to the language than the + introduction of lazy evaluation in + section. + + + + Nondeterministic computing, like stream processing, is useful for + nondeterministic programming vs.JavaScript programming + generate and test applications. Consider the task of + starting with two lists of positive integers and finding a pair of + integersone from the first list and one from the second + listwhose sum is prime. We saw how to handle this with finite + sequence operations in section and + with infinite streams in section. + Our approach was to generate the sequence of all possible pairs and filter + these to select the pairs whose sum is prime. Whether we actually generate + the entire sequence of pairs first as in chapter, or interleave the + generating and filtering as in chapter, is immaterial to the + essential image of how the computation is organized. + + + + The nondeterministic approach evokes a different image. Imagine simply + that we choose (in some way) a number from the first list and a number + from the second list and require (using some mechanism) that their + nondeterministic programspairs with prime sums + sum be prime. This is expressed by the following + + procedure: + function: + + + prime_sum_pair + prime_sum_pair_non_det + prime_sum_pair_non_det_example + [ 3, [ 20, null ] ] + +(define (prime-sum-pair list1 list2) + (let ((a (an-element-of list1)) + (b (an-element-of list2))) + (require (prime? (+ a b))) + (list a b))) + + +function prime_sum_pair(list1, list2) { + const a = an_element_of(list1); + const b = an_element_of(list2); + require(is_prime(a + b)); + return list(a, b); +} + + + It might seem as if this + + procedure + function + + merely restates the problem, + rather than specifying a way to solve it. Nevertheless, this is a + legitimate nondeterministic program.We assume that we have + previously defined a + + procedure + function + + + prime? + is_prime + + that tests whether numbers are prime. Even with + + prime? + is_prime + + defined, the + + prime-sum-pair + prime_sum_pair + + + + procedure + function + + may look suspiciously like the unhelpful + + pseudo-Lisp + pseudo-JavaScript + + attempt to define the square-root function, which we described at the + beginning of section. In fact, a square-root + + procedure + function + + along those lines can actually be formulated as a nondeterministic program. + By incorporating a search mechanism into the evaluator, we are eroding the + declarative vs.imperative knowledgenondeterministic computing and + imperative vs.declarative knowledgenondeterministic computing and + distinction between purely declarative descriptions and imperative + specifications of how to compute answers. Well go even farther in + this direction in + section. + + + + The key idea here is that + + expressions + components + + in a nondeterministic language + can have more than one possible value. For instance, + + an-element-of + an_element_of + + might return any element of the given list. Our nondeterministic program + evaluator will work by automatically choosing a possible value and keeping + track of the choice. If a subsequent requirement is not met, the evaluator + will try a different choice, and it will keep trying new choices until the + evaluation succeeds, or until we run out of choices. Just as the lazy + evaluator freed the programmer from the details of how values are delayed + and forced, the nondeterministic program evaluator will free the programmer + from the details of how choices are made. + + + + It is instructive to contrast the different images of + timein nondeterministic computing + time evoked by + nondeterministic evaluation and stream processing. Stream processing + uses lazy evaluation to decouple the time when the stream of possible + answers is assembled from the time when the actual stream elements are + produced. The evaluator supports the illusion that all the possible + answers are laid out before us in a timeless sequence. With + nondeterministic evaluation, + + an expression + a component + + represents the exploration + of a set of possible worlds, each determined by a set of choices. + Some of the possible worlds lead to dead ends, while others have + useful values. The nondeterministic program evaluator supports the + illusion that time branches, and that our programs have different + possible execution histories. When we reach a dead end, we can + revisit a previous choice point and proceed along a different branch. + + + + The nondeterministic program evaluator implemented below is called the + amb evaluator because it is based on + + a new special form + a new syntactic form + + called amb. We can type the above + + definition + declaration + + of + + prime-sum-pair + prime_sum_pair + + + at the amb evaluator driver loop (along with + + definitions + declarations + + of + + prime?, + is_prime, + + + an-element-of, + an_element_of, + + + and require) and run the + + procedure + function + + as follows: + + prime_sum_pair_non_det_example + is_prime2 + prime_sum_pair_non_det + +;;; Amb-Eval input: + + +(prime-sum-pair '(1 3 5 8) '(20 35 110)) + + +;;; Starting a new problem +;;; Amb-Eval value: +(3 20) + + +amb-evaluate input: + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); + + +Starting a new problem +amb-evaluate value: +[3, [20, null]] + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + The value returned was obtained after the evaluator repeatedly chose + elements from each of the lists, until a successful choice was made. + + + Section introduces + amb and explains how it supports nondeterminism + through the evaluators automatic search mechanism. + Section presents examples of + nondeterministic programs, and + section gives the details of how + to implement the amb evaluator by modifying the + ordinary + + Scheme + JavaScript + + evaluator. + + + + &subsection4.3.1; + + + &subsection4.3.2; + + + &subsection4.3.3; + + +
diff --git a/xml/cn/chapter4/section3/subsection1.xml b/xml/cn/chapter4/section3/subsection1.xml new file mode 100644 index 000000000..1d5a04b86 --- /dev/null +++ b/xml/cn/chapter4/section3/subsection1.xml @@ -0,0 +1,765 @@ + + + + Amb and Search + Search and amb + + + + + + + To extend + + Scheme + JavaScript + + to support nondeterminism, we introduce a new + + special form + syntactic form + + amb + calledamb.The idea of + amb for nondeterministic programming was + first described in 1961 by + McCarthy, John + John McCarthy (see + McCarthy 1967). + The expression + + + (amb $e_1\ e_2\ldots e_n$) + + + amb($e_1,\ e_2,\ldots, e_n$) + + + returns the value of one of the $n$ expressions + $e_i$ ambiguously. For example, + the expression + + list_non_det + +(list (amb 1 2 3) (amb 'a 'b)) + + +list(amb(1, 2, 3), amb("a", "b")); + + +list(amb(1, 2, 3), amb("a", "b")); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + can have six possible values: + + +(1 a) (1 b) (2 a) (2 b) (3 a) (3 b) + + +list(1, "a") list(1, "b") list(2, "a") +list(2, "b") list(3, "a") list(3, "b") + + + + + Amb + + + An amb expression + + + with a single choice produces an ordinary (single) value. + + + + + + Amb + + + An amb expression + + + with no choicesthe expression + + (amb)is + amb()is + + + failure, in nondeterministic computation + an expression with no acceptable values. Operationally, we can think of + + (amb) + amb() + + as an expression that when evaluated causes the computation to + fail: The computation aborts and no value is produced. + Using this idea, we can express the requirement that a particular predicate + expression p must be true as follows: + + require_non_det_example + +const x = amb(1, 3, 5, 7, 9); +require(x >= 4); +x; +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + require + require_non_det + require_non_det_example + 5 + +(define (require p) + (if (not p) (amb))) + + +function require(p) { + if (! p) { + amb(); + } else {} +} + + + + + + With amb and + require, we can implement the + + an-element-of procedure + an_element_of function + + + used above: + + an_element_of_example + +const xs = list("apple", "banana", "cranberry"); +an_element_of(xs); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + an_element_of + an_element_of + an_element_of_example + 'apple' + +(define (an-element-of items) + (require (not (null? items))) + (amb (car items) (an-element-of (cdr items)))) + + +function an_element_of(items) { + require(! is_null(items)); + return amb(head(items), an_element_of(tail(items))); +} + + + + An-element-of + + An application of an_element_of + + + fails if the list is empty. Otherwise it ambiguously returns either the + first element of the list or an element chosen from the rest of the list. + + + + We can also express infinite ranges of choices. The following + + procedure + function + + potentially returns any integer greater than or equal to some + given$n$: + + an_integer_starting_from_example + +const x = an_integer_starting_from(1); +require(x >= 4.5); +x; +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + an_integer_starting_from + an_integer_starting_from + an_integer_starting_from_example + 5 + +(define (an-integer-starting-from n) + (amb n (an-integer-starting-from (+ n 1)))) + + +function an_integer_starting_from(n) { + return amb(n, an_integer_starting_from(n + 1)); +} + + + This is like the stream + + procedure + integers-starting-from + + function + integers_starting_from + + + described in section , but with an + important difference: The stream + + procedure + function + + returns an object that represents the sequence of all integers beginning + with $n$, whereas the + amb + + procedure + function + + returns a single integer.In actuality, the distinction between + nondeterministically returning a single choice and returning all choices + depends somewhat on our point of view. From the perspective of the code + that uses the value, the nondeterministic choice returns a single value. + From the perspective of the programmer designing the code, the + nondeterministic choice potentially returns all possible values, and the + computation branches so that each value is investigated + separately. + + + + + Abstractly, we can imagine that evaluating an + amb expression causes + timein nondeterministic computing + time to split into + branches, where the computation continues on each branch with one of the + possible values of the expression. We say that + amb represents a + nondeterministic choice point + nondeterministic choice point. If we had a machine with a + sufficient number of processors that could be dynamically allocated, we + could implement the search in a straightforward way. Execution would + proceed as in a sequential machine, until an amb + expression is encountered. At this point, more processors would be allocated + and initialized to continue all of the parallel executions implied by the + choice. Each processor would proceed sequentially as if it were the only + choice, until it either terminates by encountering a failure, or it further + subdivides, or it finishes.One might object that this is a + hopelessly inefficient mechanism. It might require millions of processors + to solve some easily stated problem this way, and most of the time most + of those processors would be idle. This objection should be taken in + the context of history. Memory used to be considered just such an + expensive commodity. + memoryin 1965 + In 1965 a megabyte of RAM cost about 400,000. Now every personal + computer has many gigabytes of RAM, and most of the time most of that RAM is + unused. It is hard to underestimate the cost of mass-produced + electronics. + + + + On the other hand, if we have a machine that can execute only one process + (or a few concurrent processes), we must consider the alternatives + failure, in nondeterministic computationsearching and + sequentially. One could imagine modifying an evaluator to pick at random a + branch to follow whenever it encounters a choice point. Random choice, + however, can easily lead to failing values. We might try running the + evaluator over and over, making random choices and hoping to find a + non-failing value, but it is better to + systematic search + searchsystematic + systematically search all possible execution paths. The + amb evaluator that we will develop and work + with in this section implements a systematic search as follows: When the + evaluator encounters an application of amb, it + initially selects the first alternative. This selection may itself lead to + a further choice. The evaluator will always initially choose the first + alternative at each choice point. If a choice results in a failure, then + the evaluator + automagically + automagicallyAutomagically: Automatically, but in a way + which, for some reason (typically because it is too complicated, or too ugly, + or perhaps even too trivial), the speaker doesnt feel like + explaining. + Steele, Guy Lewis Jr. + (Steele 1983, + Raymond, Eric + Raymond 1996) + backtracking + backtracks to the most recent choice point and tries the next + alternative. If it runs out of alternatives at any choice point, the + evaluator will back up to the previous choice point and resume from there. + This process leads to a search strategy known as + depth-first search + searchdepth-first + depth-first search or + chronological backtracking + chronological + backtracking.The integration of + automatic searchhistory of + automatic search strategies + into programming languages has had a long and checkered history. The first + suggestions that nondeterministic algorithms might be elegantly encoded in a + programming language with search and automatic backtracking came from + Floyd, Robert + Robert Floyd (1967). + Hewitt, Carl Eddie + Carl Hewitt (1969) invented a programming language called + Planner + Planner that explicitly supported automatic chronological backtracking, + providing for a built-in depth-first search strategy. + Sussman, Gerald Jay + Winograd, Terry + Charniak, Eugene + Sussman, Winograd, and Charniak (1971) implemented a subset of this language, + called + MicroPlanner + MicroPlanner, which was used to support work in problem solving and robot + planning. Similar ideas, arising from logic and theorem proving, led to the + genesis in Edinburgh and Marseille of the elegant language + Prolog + Prolog (which we will discuss in + section). After sufficient + frustration with automatic search, + McDermott, Drew + Sussman, Gerald Jay + McDermott and Sussman (1972) developed a language called + Conniver + Conniver, which included mechanisms for placing the search strategy under + programmer control. This proved unwieldy, however, and + Sussman, Gerald Jay + Stallman, Richard M. + Sussman and Stallman (1975) found a more tractable approach while + investigating methods of symbolic analysis for electrical circuits. They + developed a nonchronological backtracking scheme that was based on tracing + out the logical dependencies connecting facts, a technique that has come to + be known as + dependency-directed backtracking + dependency-directed backtracking. Although their method was + complex, it produced reasonably efficient programs because it did little + redundant search. + Doyle, Jon + Doyle (1979) and + McAllester, David Allen + McAllester (1978, 1980) + generalized and clarified the methods of Stallman and Sussman, developing a + new paradigm for formulating search that is now called + truth maintenance + truth maintenance. + + + Modern problem-solving systems all + + + Many problem-solving systems + + + use some form of truth-maintenance system as a substrate. See + Forbus, Kenneth D. + de Kleer, Johan + Forbus and de Kleer 1993 for a discussion of elegant + ways to build truth-maintenance systems and applications using truth + maintenance. + Zabih, Ramin + McAllester, David Allen + Chapman, David + Zabih, McAllester, and Chapman 1987 describes a + Schemenondeterministic extension of + nondeterministic extension to Scheme that is based on + amb; it is similar to the interpreter described + in this section, but more sophisticated, because it uses dependency-directed + backtracking rather than chronological + backtracking. + Winston, Patrick Henry + Winston 1992 gives an introduction to + both kinds of backtracking. + + + + + Driver loop + + + + The + driver loopnondeterministicin nondeterministic evaluator + driver loop for the amb evaluator has some + unusual properties. It reads + + an expression + a program + + and prints the value of the + first non-failing execution, as in the + + prime-sum-pair + prime_sum_pair + + + example shown above. If we want to see the value of the next successful + execution, we can ask the interpreter to backtrack and attempt to generate a + second non-failing execution. + + + This is signaled by typing the symbol + try-again + try-again. If any expression except + try-again + is given, the interpreter will start a new problem, discarding the + unexplored alternatives in the previous problem. + + + This is signaled by typing + retry + retry. + If any other input except retry + is given, the interpreter will start a new problem, discarding the + unexplored alternatives in the previous problem. + + + Here is a sample interaction: + + interaction_non_det + is_prime2 + prime_sum_pair_non_det + +;;; Amb-Eval input: + + +(prime-sum-pair '(1 3 5 8) '(20 35 110)) + + +;;; Starting a new problem +;;; Amb-Eval value: +(3 20) + + +amb-evaluate input: + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); + + +Starting a new problem +amb-evaluate value: +[3, [20, null]] + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + interaction_non_det_2 + is_prime2 + prime_sum_pair_non_det + +;;; Amb-Eval input: + + +try-again + + +;;; Amb-Eval value: +(3 110) + + +amb-evaluate input: + + +retry + + +amb-evaluate value: +[3, [110, null]] + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + interaction_non_det_3 + is_prime2 + prime_sum_pair_non_det + +;;; Amb-Eval input: + + +try-again + + +;;; Amb-Eval value: +(8 35) + +;;; Amb-Eval input: + + +amb-evaluate input: + + +retry + + +amb-evaluate value: +[8, [35, null]] + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + interaction_non_det_4 + is_prime2 + prime_sum_pair_non_det + +try-again + + +;;; There are no more values of +(prime-sum-pair (quote (1 3 5 8)) (quote (20 35 110))) + + +amb-evaluate input: + + +retry + + +There are no more values of +prime_sum_pair([1, [3, [5, [8, null]]]], [20, [35, [110, null]]]) + + +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + interaction_non_det_5 + is_prime2 + prime_sum_pair_non_det + +;;; Amb-Eval input: + + +(prime-sum-pair '(19 27 30) '(11 36 58)) + + +;;; Starting a new problem +;;; Amb-Eval value: +(30 11) + + +amb-evaluate input: + + +prime_sum_pair(list(19, 27, 30), list(11, 36, 58)); + + +Starting a new problem +amb-evaluate value: +[30, [11, null]] + + +prime_sum_pair(list(19, 27, 30), list(11, 36, 58)); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + + + + Write a + + procedure + an-integer-between + + function + an_integer_between + + + that returns an integer between two given bounds. This can be used to + implement a + + procedure + function + + that finds + Pythagorean tripleswith nondeterministic programs + nondeterministic programsPythagorean triples + Pythagorean triples, i.e., triples of integers + $(i,j,k)$ between the given bounds such + that $i \leq j$ and + $i^2 + j^2 =k^2$, as follows: + + pythagorean_triple_amb_example + +a_pythogorean_triple_between(5, 15); +// Press "Run" for the first solution. Type +// retry +// in the REPL on the right, for more solutions + + + + pythagorean_triple_amb + pythagorean_triple_amb_example + [ 5, [ 12, [ 13, null] ] ] + +(define (a-pythagorean-triple-between low high) + (let ((i (an-integer-between low high))) + (let ((j (an-integer-between i high))) + (let ((k (an-integer-between j high))) + (require (= (+ (* i i) (* j j)) (* k k))) + (list i j k))))) + + +function a_pythogorean_triple_between(low, high) { + const i = an_integer_between(low, high); + const j = an_integer_between(i, high); + const k = an_integer_between(j, high); + require(i * i + j * j === k * k); + return list(i, j, k); +} + + + + + +// solution by GitHub user jonathantorres + +function an_integer_between(low, high) { + require(low <= high); + return amb(low, an_integer_between(low+1, high)); +} + + + + + + + + Exercise discussed how to + generate the stream of all + Pythagorean tripleswith nondeterministic programs + nondeterministic programsPythagorean triples + Pythagorean triples, with no upper bound + on the size of the integers to be searched. Explain why simply replacing + + + an-integer-between + + + an_integer_between + + + by + + + an-integer-starting-from + + + an_integer_starting_from + + + in the + + procedure + function + + in + exercise is not an adequate way to + generate arbitrary Pythagorean triples. Write a + + procedure + function + + that actually will accomplish this. (That is, write a + + procedure + function + + for which repeatedly typing + + + try-again + + + retry + + + would in principle eventually generate all Pythagorean triples.) + + + + + Ben Bitdiddle claims that the following method for generating + Pythagorean tripleswith nondeterministic programs + nondeterministic programsPythagorean triples + Pythagorean + triples is more efficient than the one in + exercise. Is he correct? + (Hint: Consider the number of possibilities that must be explored.) + + a_pythagorean_triple_between_2_example + +function is_integer(x) { + return x === math_floor(x); +} +a_pythagorean_triple_between(5, 15); + + + + a_pythagorean_triple_between_2 + a_pythagorean_triple_between_2_example + +(define (a-pythagorean-triple-between low high) + (let ((i (an-integer-between low high)) + (hsq (* high high))) + (let ((j (an-integer-between i high))) + (let ((ksq (+ (* i i) (* j j)))) + (require (>= hsq ksq)) + (let ((k (sqrt ksq))) + (require (integer? k)) + (list i j k)))))) + + +function a_pythagorean_triple_between(low, high) { + const i = an_integer_between(low, high); + const hsq = high * high; + const j = an_integer_between(i, high); + const ksq = i * i + j * j; + require(hsq >= ksq); + const k = math_sqrt(ksq); + require(is_integer(k)); + return list(i, j, k); +} + + + + diff --git a/xml/cn/chapter4/section3/subsection2.xml b/xml/cn/chapter4/section3/subsection2.xml new file mode 100644 index 000000000..0fd14ed0d --- /dev/null +++ b/xml/cn/chapter4/section3/subsection2.xml @@ -0,0 +1,1091 @@ + + + Examples of Nondeterministic Programs + + + + + Section describes the + implementation of the amb evaluator. First, + however, we give some examples of how it can be used. The advantage of + nondeterministic programming is that we can suppress the details of how + search is carried out, thereby + expressing our programs at a higher level of + abstractionsearchof search in nondeterministic programming + abstraction. + + + + Logic Puzzles + + + puzzleslogic puzzles + logic puzzles + nondeterministic programslogic puzzles + + + The following puzzle (adapted from + Dinesman, Howard P. + Dinesman 1968) + is typical of a large class of simple logic puzzles: +
+ The software company + Gargle + Gargle is expanding, and Alyssa, Ben, Cy, Lem, and Louis + are moving into a row of five private offices in a + new building. Alyssa does not move into the last office. Ben does not + move into the first office. Cy takes neither the first nor the last office. + Lem moves into an office after Ben's. Louis's office is not next to + Cy's. Cy's office is not next to Ben's. Who moves into which office? + +
+
+ + We can determine who moves into which office in a straightforward way by + enumerating all the possibilities and imposing the given + restrictions:Our program uses the following + + procedure + function + + to determine if the elements of a list are distinct: + + distinct_example + +distinct(list(1, 2, 4, 4, 5)); + + + + distinct + distinct + distinct_example + false + +(define (distinct? items) + (cond ((null? items) true) + ((null? (cdr items)) true) + ((member (car items) (cdr items)) false) + (else (distinct? (cdr items))))) + + +function distinct(items) { + return is_null(items) + ? true + : is_null(tail(items)) + ? true + : is_null(member(head(items), tail(items))) + ? distinct(tail(items)) + : false; +} + + + + + Member + member + is like + memq + except that it uses equal? instead + of eq? to test for equality. + + + + office_move + office_move + distinct + office_move_example + [ 'alyssa', [ 3, null ] ] + +(define (multiple-dwelling) + (let ((baker (amb 1 2 3 4 5)) + (cooper (amb 1 2 3 4 5)) + (fletcher (amb 1 2 3 4 5)) + (miller (amb 1 2 3 4 5)) + (smith (amb 1 2 3 4 5))) + (require + (distinct? (list baker cooper fletcher miller smith))) + (require (not (= baker 5))) + (require (not (= cooper 1))) + (require (not (= fletcher 5))) + (require (not (= fletcher 1))) + (require (> miller cooper)) + (require (not (= (abs (- smith fletcher)) 1))) + (require (not (= (abs (- fletcher cooper)) 1))) + (list (list 'baker baker) + (list 'cooper cooper) + (list 'fletcher fletcher) + (list 'miller miller) + (list 'smith smith)))) + + +function office_move() { + const alyssa = amb(1, 2, 3, 4, 5); + const ben = amb(1, 2, 3, 4, 5); + const cy = amb(1, 2, 3, 4, 5); + const lem = amb(1, 2, 3, 4, 5); + const louis = amb(1, 2, 3, 4, 5); + require(distinct(list(alyssa, ben, cy, lem, louis))); + require(alyssa !== 5); + require(ben !== 1); + require(cy !== 5); + require(cy !== 1); + require(lem > ben); + require(math_abs(louis - cy) !== 1); + require(math_abs(cy - ben) !== 1); + return list(list("alyssa", alyssa), + list("ben", ben), + list("cy", cy), + list("lem", lem), + list("louis", louis)); +} + + + + office_move_example + +(multiple-dwelling) + + +office_move(); + + +head(office_move()); + + + + + + Evaluating the expression + (multiple-dwelling) + + office_move() + + + produces the result + + +((baker 3) (cooper 2) (fletcher 4) (miller 5) (smith 1)) + + +list(list("alyssa", 3), list("ben", 2), list("cy", 4), + list("lem", 5), list("louis", 1)) + + + Although this simple + + procedure + function + + works, it is very slow. + Exercises + and discuss some possible + improvements. + + + + + Modify the office-move + + procedure + function + + to omit the requirement that Louis's office is not next to Cy's. + How many solutions are there to this modified puzzle? + + + + + Does the order of the restrictions in the office-move + + procedure + function + + affect the answer? Does it affect the time to find an answer? If you + think it matters, demonstrate a faster program obtained from the given + one by reordering the restrictions. If you think it does not matter, + argue your case. + + + + + In the office move problem, how many sets of assignments are + there of people to offices, both before and after the requirement that + office assignments be distinct? It is very inefficient to generate all + possible assignments of people to offices and then leave it to + backtracking to eliminate them. For example, most of the restrictions + depend on only one or two of the person-office + + variables, + names, + + and can thus be imposed before offices have been selected for all the people. + Write and demonstrate a much more efficient nondeterministic + + procedure + function + + that solves this problem based upon generating only those possibilities that + are not already ruled out by previous restrictions. + + + (Hint: This will require a nest of let + expressions.) + + + + + + + Write an ordinary + nondeterministic programming vs.JavaScript programming + + Scheme + JavaScript + + program to solve the office move puzzle. + + + + + Solve the following Liars puzzle (adapted from + Phillips, Hubert + Phillips 1934): +
+ Alyssa, Cy, Eva, Lem, and Louis meet for a business lunch at SoSoService. + Their meals arrive one after the other, a considerable time after they + placed their orders. To entertain Ben, who expects them back at the office + for a meeting, they decide to each make one true statement and one false + statement about their orders: +
    +
  • + Alyssa: + Lem's meal arrived second. Mine arrived third. +
  • +
  • + Cy: + Mine arrived first. Eva's arrived second. +
  • +
  • + Eva: + Mine arrived third, and poor Cy's arrived last. +
  • +
  • + Lem: + Mine arrived second. Louis's arrived fourth. +
  • +
  • + Louis: + Mine arrived fourth. Alyssa's meal arrived first. +
  • +
+ What was the real order in which the five diners received their meals? + +
+
+ + + + Use the amb evaluator to solve the following + puzzle (adapted from + Phillips, Hubert + Phillips 1961): +
+ Alyssa, Ben, Cy, Eva, and Louis each pick a different chapter of SICP JS + and solve all the exercises in that chapter. + Louis solves the exercises in the Functions chapter, + Alyssa the ones in the Data chapter, and + Cy the ones in the State chapter. + They decide to check each other's work, and + Alyssa volunteers to check the exercises in the Meta chapter. + The exercises in the Register Machines chapter are solved by Ben + and checked by Louis. + The person who checks the exercises in the Functions chapter + solves the exercises that are checked by Eva. + Who checks the exercises in the Data chapter? +
+ Try to write the program so that it runs efficiently (see + exercise). Also determine + how many solutions there are if we are not told that Alyssa checks the + exercises in the Meta chapter. + +
+ + puzzleslogic puzzles + logic puzzles + nondeterministic programslogic puzzles + + + + Exercise described the + chess, eight-queens puzzle + eight-queens puzzle + puzzleseight-queens puzzle + nondeterministic programming vs.JavaScript programming + eight-queens puzzle of placing queens on a chessboard so that + no two attack each other. Write a nondeterministic program to solve this + puzzle. + + + + Parsing natural language + + + parsing natural language + nondeterministic programsparsing natural language + + + Programs designed to accept natural language as input usually start by + attempting to parse the input, that is, to match the input + against some grammatical structure. For example, we might try to + recognize simple sentences consisting of an article followed by a noun + followed by a verb, such as The cat eats. To accomplish + such an analysis, we must be able to identify the parts of speech of + individual words. We could start with some lists that classify various + words:Here we use the convention that the first element of each + list designates the part of speech for the rest of the words in the + list. + + nouns + verbs + articles + nouns + +(define nouns '(noun student professor cat class)) + +(define verbs '(verb studies lectures eats sleeps)) + +(define articles '(article the a)) + + +const nouns = list("noun", "student", "professor", "cat", "class"); + +const verbs = list("verb", "studies", "lectures", "eats", "sleeps"); + +const articles = list("article", "the", "a"); + + + We also need a + grammar + grammar, that is, a set of rules describing how + grammatical elements are composed from simpler elements. A very + simple grammar might stipulate that a sentence always consists of two + piecesa noun phrase followed by a verband that a noun + phrase consists of an article followed by a noun. With this grammar, the + sentence The cat eats is parsed as follows: + + +(sentence (noun-phrase (article the) (noun cat)) + (verb eats)) + + +list("sentence", + list("noun-phrase", list("article", "the"), list("noun", "cat"), + list("verb", "eats")) + + + + + parse_... + + + We can generate such a parse with a simple program that has separate + + procedures + functions + + for each of the grammatical rules. To parse a sentence, we identify its + two constituent pieces and return a list of these two elements, tagged with + the symbol sentence: + + parse_sentence + parse_noun_phrase + nouns + parse_input_example + [ "sentence", [ ["noun-phrase", [["article", ["the", null]], [["noun", ["cat", null]], null]]], [["verb", ["eats", null]], null]]] + +(define (parse-sentence) + (list 'sentence + (parse-noun-phrase) + (parse-word verbs))) + + +function parse_sentence() { + return list("sentence", + parse_noun_phrase(), + parse_word(verbs)); +} + + + A noun phrase, similarly, is parsed by finding an article followed by a + noun: + + parse_noun_phrase + nouns + parse_word + parse_input_example + +(define (parse-noun-phrase) + (list 'noun-phrase + (parse-word articles) + (parse-word nouns))) + + +function parse_noun_phrase() { + return list("noun-phrase", + parse_word(articles), + parse_word(nouns)); +} + + + + + + At the lowest level, parsing boils down to repeatedly checking that + the next + + unparsed + not-yet-parsed + + word is a member of the list of words for the + required part of speech. To implement this, we maintain a global + variable + + + *unparsed*, + + + not_yet_parsed, + + + which is the input that has not yet been parsed. Each time we check a word, + we require that + + + *unparsed* + + + not_yet_parsed + + + must be nonempty and that it should begin with a word from the designated + list. If so, we remove that word from + + + *unparsed* + + + not_yet_parsed + + + and return the word together with its part of speech (which is found at + the head of the list):Notice that + + + parse-word + + + parse_word + + + uses + + set! + assignment + + to modify the + + unparsed + not-yet-parsed + + input list. For this to work, our + amb evaluator must undo the effects of + + set! operations + assignments + + when it backtracks. + + parse_word + unparsed + parse_input_example + +(define (parse-word word-list) + (require (not (null? *unparsed*))) + (require (memq (car *unparsed*) (cdr word-list))) + (let ((found-word (car *unparsed*))) + (set! *unparsed* (cdr *unparsed*)) + (list (car word-list) found-word))) + + +function parse_word(word_list) { + require(! is_null(not_yet_parsed)); + require(! is_null(member(head(not_yet_parsed), tail(word_list)))); + const found_word = head(not_yet_parsed); + not_yet_parsed = tail(not_yet_parsed); + return list(head(word_list), found_word); +} + + + + + + To start the parsing, all we need to do is set + + + *unparsed* + + + not_yet_parsed + + + to be + the entire input, try to parse a sentence, and check that nothing is + left over: + + unparsed + +(define *unparsed* '()) + + +let not_yet_parsed = null; + + + + parse_input + unparsed + parse_sentence + parse_input_example + +(define (parse input) + (set! *unparsed* input) + (let ((sent (parse-sentence))) + (require (null? *unparsed*)) + sent)) + + +function parse_input(input) { + not_yet_parsed = input; + const sent = parse_sentence(); + require(is_null(not_yet_parsed)); + return sent; +} + + + + + We can now try the parser and verify that it works for our simple test + sentence: + + parse_input_example + parse_input + +;;; Amb-Eval input: + + +(parse '(the cat eats)) + + +;;; Starting a new problem +;;; Amb-Eval value: +(sentence (noun-phrase (article the) (noun cat)) (verb eats)) + + +amb-evaluate input: + + +parse_input(list("the", "cat", "eats")); + + +parse_input(list("the", "cat", "eats")); + + +Starting a new problem +amb-evaluate value: +list("sentence", + list("noun-phrase", list("article", "the"), list("noun", "cat")), + list("verb", "eats")) + + + + + + The amb evaluator is useful here because it is + convenient to express the parsing constraints with the aid of + require. Automatic search and backtracking + really pay off, however, when we consider more complex grammars where there + are choices for how the units can be decomposed. + + + + Lets add to our grammar a list of prepositions: + + prepositions + prepositions + +(define prepositions '(prep for to in by with)) + + +const prepositions = list("prep", "for", "to", "in", "by", "with"); + + + and define a prepositional phrase (e.g., for the cat) to be + a preposition followed by a noun phrase: + + parse_prepositional_phrase + parse_word + prepositions + parse_input_example_2 + [ "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]]] + +(define (parse-prepositional-phrase) + (list 'prep-phrase + (parse-word prepositions) + (parse-noun-phrase))) + + +function parse_prepositional_phrase() { + return list("prep-phrase", + parse_word(prepositions), + parse_noun_phrase()); +} + + + Now we can define a sentence to be a noun phrase followed by a verb + phrase, where a verb phrase can be either a verb or a verb phrase + extended by a prepositional phrase:Observe that this + definition is recursivea verb may be followed by any number + of prepositional phrases. + + parse_sentence_2 + parse_prepositional_phrase + parse_word + parse_noun_phrase_2 + parse_input_example_2 + +(define (parse-sentence) + (list 'sentence + (parse-noun-phrase) + (parse-verb-phrase))) + +(define (parse-verb-phrase) + (define (maybe-extend verb-phrase) + (amb verb-phrase + (maybe-extend (list 'verb-phrase + verb-phrase + (parse-prepositional-phrase))))) + (maybe-extend (parse-word verbs))) + + +function parse_sentence() { + return list("sentence", + parse_noun_phrase(), + parse_verb_phrase()); +} +function parse_verb_phrase() { + function maybe_extend(verb_phrase) { + return amb(verb_phrase, + maybe_extend(list("verb-phrase", + verb_phrase, + parse_prepositional_phrase()))); + } + return maybe_extend(parse_word(verbs)); +} + + + + parse_input_2 + unparsed + parse_sentence_2 + +(define (parse input) + (set! *unparsed* input) + (let ((sent (parse-sentence))) + (require (null? *unparsed*)) + sent)) + + +function parse_input(input) { + not_yet_parsed = input; + const sent = parse_sentence(); + require(is_null(not_yet_parsed)); + return sent; +} + + + + + + While were at it, we can also elaborate the definition of noun + phrases to permit such things as a cat in the class. What + we used to call a noun phrase, well now call a simple noun phrase, + and a noun phrase will now be either a simple noun phrase or a noun phrase + extended by a prepositional phrase: + + parse_noun_phrase_2 + parse_prepositional_phrase + parse_word + nouns + parse_input_example_2 + +(define (parse-simple-noun-phrase) + (list 'simple-noun-phrase + (parse-word articles) + (parse-word nouns))) + +(define (parse-noun-phrase) + (define (maybe-extend noun-phrase) + (amb noun-phrase + (maybe-extend (list 'noun-phrase + noun-phrase + (parse-prepositional-phrase))))) + (maybe-extend (parse-simple-noun-phrase))) + + +function parse_simple_noun_phrase() { + return list("simple-noun-phrase", + parse_word(articles), + parse_word(nouns)); +} +function parse_noun_phrase() { + function maybe_extend(noun_phrase) { + return amb(noun_phrase, + maybe_extend(list("noun-phrase", + noun_phrase, + parse_prepositional_phrase()))); + } + return maybe_extend(parse_simple_noun_phrase()); +} + + + + + parse_... + + + Our new grammar lets us parse more complex sentences. For example + + parse_input_example_2 + parse_input_2 + +(parse '(the student with the cat sleeps in the class)) + + +parse_input(list("the", "student", "with", "the", "cat", + "sleeps", "in", "the", "class")); + + + produces + + +(sentence + (noun-phrase + (simple-noun-phrase (article the) (noun student)) + (prep-phrase (prep with) + (simple-noun-phrase + (article the) (noun cat)))) + (verb-phrase + (verb sleeps) + (prep-phrase (prep in) + (simple-noun-phrase + (article the) (noun class))))) + + +list("sentence", + list("noun-phrase", + list("simple-noun-phrase", + list("article", "the"), list("noun", "student")), + list("prep-phrase", list("prep", "with"), + list("simple-noun-phrase", + list("article", "the"), + list("noun", "cat")))), + list("verb-phrase", + list("verb", "sleeps"), + list("prep-phrase", list("prep", "in"), + list("simple-noun-phrase", + list("article", "the"), + list("noun", "class"))))) + + + + + + Observe that a given input may have more than one legal parse. In the + sentence The professor lectures to the student with the cat, + it may be that the professor is lecturing with the cat, or that the student + has the cat. Our nondeterministic program finds both possibilities: + + multiple_legal_parses + parse_input_2 + +(parse '(the professor lectures to the student with the cat)) + + +parse_input(list("the", "professor", "lectures", + "to", "the", "student", "with", "the", "cat")); + + + produces + + +(sentence + (simple-noun-phrase (article the) (noun professor)) + (verb-phrase + (verb-phrase + (verb lectures) + (prep-phrase (prep to) + (simple-noun-phrase + (article the) (noun student)))) + (prep-phrase (prep with) + (simple-noun-phrase + (article the) (noun cat))))) + + +list("sentence", + list("simple-noun-phrase", + list("article", "the"), list("noun", "professor")), + list("verb-phrase", + list("verb-phrase", + list("verb", "lectures"), + list("prep-phrase", list("prep", "to"), + list("simple-noun-phrase", + list("article", "the"), + list("noun", "student")))), + list("prep-phrase", list("prep", "with"), + list("simple-noun-phrase", + list("article", "the"), + list("noun", "cat"))))) + + + + Asking the evaluator to retry yields + + +(sentence + (simple-noun-phrase (article the) (noun professor)) + (verb-phrase + (verb lectures) + (prep-phrase (prep to) + (noun-phrase + (simple-noun-phrase + (article the) (noun student)) + (prep-phrase (prep with) + (simple-noun-phrase + (article the) (noun cat))))))) + + +list("sentence", + list("simple-noun-phrase", + list("article", "the"), list("noun", "professor")), + list("verb-phrase", + list("verb", "lectures"), + list("prep-phrase", list("prep", "to"), + list("noun-phrase", + list("simple-noun-phrase", + list("article", "the"), + list("noun", "student")), + list("prep-phrase", list("prep", "with"), + list("simple-noun-phrase", + list("article", "the"), + list("noun", "cat"))))))) + + + + + nondeterministic programsparsing natural language + + + + With the grammar given above, the following sentence can be parsed in five + different ways: The professor lectures to the student in the class + with the cat. Give the five parses and explain the differences in + shades of meaning among them. + + + + + The + nondeterministic evaluatororder of argument evaluation + evaluators in sections and + do not determine what order + + operands + argument expressions + + are + evaluated in. We will see that the amb evaluator + evaluates them from left to right. Explain why our parsing program + wouldnt work if the + + operands + argument expressions + + were evaluated in some other order. + + + + + Louis Reasoner suggests that, since a verb phrase is either a verb or + a verb phrase followed by a prepositional phrase, it would be much more + straightforward to + + define + declare + + the + + procedure + function + + + parse-verb-phrase + parse_verb_phrase + + + as follows (and similarly for noun phrases): + + +(define (parse-verb-phrase) + (amb (parse-word verbs) + (list 'verb-phrase + (parse-verb-phrase) + (parse-prepositional-phrase)))) + + +function parse_verb_phrase() { + return amb(parse_word(verbs), + list("verb-phrase", + parse_verb_phrase(), + parse_prepositional_phrase())); +} + + + Does this work? Does the programs behavior change if we interchange + the order of expressions in the amb? + + + + + Extend the grammar given above to handle more complex sentences. For + example, you could extend noun phrases and verb phrases to include adjectives + and adverbs, or you could handle compound sentences.This kind of + grammar can become arbitrarily complex, but it + is only a + parsing natural languagereal language understanding vs.toy parser + toy as far as real language understanding is concerned. + Real natural-language understanding by computer requires an elaborate + mixture of syntactic analysis and interpretation of meaning. On the + other hand, even toy parsers can be useful in supporting flexible + command languages for programs such as information-retrieval systems. + Winston, Patrick Henry + Winston 1992 discusses computational approaches to + real language understanding and also the applications of simple grammars + to command languages. + + + + + Alyssa P. Hacker is more interested in + generating sentences + generating interesting sentences + than in parsing them. She reasons that by simply changing the + + procedure + parse-word + + function + parse_word + + + so that it ignores the input sentence and instead always + succeeds and generates an appropriate word, we can use the programs we had + built for parsing to do generation instead. Implement Alyssas idea, + and show the first half-dozen or so sentences generated.Although + Alyssas idea works just fine (and is surprisingly simple), the + sentences that it generates are a bit boringthey dont + sample the possible sentences of this language in a very interesting way. + In fact, the grammar is highly recursive in many places, and + Alyssas technique falls into one of these recursions + and gets stuck. See exercise for a way to deal + with this. + + + parsing natural language + nondeterministic computing + +
diff --git a/xml/cn/chapter4/section3/subsection3.xml b/xml/cn/chapter4/section3/subsection3.xml new file mode 100644 index 000000000..f21d92861 --- /dev/null +++ b/xml/cn/chapter4/section3/subsection3.xml @@ -0,0 +1,2557 @@ + + + Implementing the + + Amb + amb + + Evaluator + + + + + nondeterministic evaluator + + + The evaluation of an ordinary + + Scheme expression + JavaScript program + + may return a value, may never terminate, or may signal an error. + In nondeterministic + + Scheme + JavaScript + + the evaluation of + + an expression + a program + + may in addition result in the discovery of + a dead end, in which case evaluation must backtrack to a previous choice + point. The interpretation of nondeterministic + + Scheme + JavaScript + + is complicated by this extra case. + + + + We will construct the amb evaluator for + nondeterministic + + Scheme + JavaScript + + by modifying the + analyzing evaluatoras basis for nondeterministic evaluator + analyzing evaluator of + section.We chose to + implement the lazy evaluator in + section as a modification of the + ordinary metacircular evaluator of + section. In contrast, we will + base the amb evaluator on the analyzing + evaluator of section, because + the execution + + procedures + functions + + in that evaluator provide a convenient framework for implementing + backtracking. As in the analyzing evaluator, evaluation of + + an expression + a component + + is accomplished by calling an + execution procedurefunctionin nondeterministic evaluator + execution + + procedure + function + + produced by analysis of that + + expression. + component. + + The difference between the interpretation of ordinary + + Scheme + JavaScript + + and the interpretation of nondeterministic + + Scheme + JavaScript + + will be entirely + in the execution + + procedures. + functions. + + + + + + Execution + + procedures + functions + + and continuations + + + + continuationin nondeterministic evaluator + + + Recall that the + execution procedurefunctionin nondeterministic evaluator + execution + + procedures + functions + + for the ordinary evaluator take one argument: the environment of execution. + In contrast, the execution + + procedures + functions + + in the amb evaluator take three arguments: + the environment, and two + + procedures + functions + + called + + continuation procedures. + continuation functions. + + The evaluation of + + an expression + a component + + will finish by calling one of these two + continuations: If the evaluation results in a value, the + success continuation (nondeterministic evaluator) + success continuation is called with that value; if the evaluation + results in the discovery of a dead end, the + failure continuation (nondeterministic evaluator) + failure continuation is called. Constructing and calling + appropriate continuations is the mechanism by which the nondeterministic + evaluator implements backtracking. + + + + It is the job of the success continuation to receive a value and proceed + with the computation. Along with that value, the success continuation is + passed another failure continuation, which is to be called subsequently if + the use of that value leads to a dead end. + + + + It is the job of the failure continuation to try another branch of the + nondeterministic process. The essence of the nondeterministic + language is in the fact that + + expressions + components + + may represent choices among + alternatives. The evaluation of such + + an expression + a component + + must proceed with + one of the indicated alternative choices, even though it is not known + in advance which choices will lead to acceptable results. To deal + with this, the evaluator picks one of the alternatives and passes this + value to the success continuation. Together with this value, the + evaluator constructs and passes along a failure continuation that can + be called later to choose a different alternative. + + + + + A failure is triggered during evaluation (that is, a failure + continuation is called) when a user program explicitly rejects the + current line of attack (for example, a call to + require may result in execution of + + (amb), + amb(), + + an expression that always + failssee section). The failure + continuation in hand at that point will cause the most recent choice point + to choose another alternative. If there are no more alternatives to be + considered at that choice point, a failure at an earlier choice point + is triggered, and so on. Failure continuations are also invoked by + the driver loop in response to a + + try-again + + retry + + + request, to find another value of the + + expression. + program. + + + + + In addition, if a side-effect operation (such as assignment to a + variable) occurs on a branch of the process resulting from a choice, + it may be necessary, when the process finds a dead end, to undo the + side effect before making a new choice. This is accomplished by + having the side-effect operation produce a failure continuation that + undoes the side effect and propagates the failure. + + + + In summary, failure continuations are constructed by +
    +
  • + amb expressionsto provide a + mechanism to make alternative choices if the current choice made by the + amb expression leads to a dead end; +
  • +
  • + the top-level driverto provide a mechanism to report failure + when the choices are exhausted; +
  • +
  • + assignmentsto intercept failures and undo assignments + during backtracking. +
  • +
+
+ + + Failures are initiated only when a dead end is encountered. This occurs +
    +
  • + if the user program executes + + (amb); + amb(); + +
  • +
  • + if the user types + + + try-again + + + retry + + + at the top-level driver. +
  • + +
+
+ + + Failure continuations are also called during processing of a failure: +
    +
  • When the failure continuation created by an assignment finishes + undoing a side effect, it calls the failure continuation it intercepted, + in order to propagate the failure back to the choice point that + led to this assignment or to the top level. + +
  • +
  • When the failure continuation for an amb + runs out of choices, it calls the failure continuation that was originally + given to the amb, in order to propagate the + failure back to the previous choice point or to the top level. +
  • +
+
+ + continuationin nondeterministic evaluator + + + Structure of the evaluator + + + + The syntax- and data-representation + + procedures + functions + + for the amb evaluator, and also the basic + analyzenondeterministic + analyze + + procedure, + function, + + are identical to those in the evaluator of + section, except for the fact + that we need additional syntax + + procedures + functions + + to recognize + + + the amb special + form:We + assume that the evaluator supports let + (see exercise), + which we have used in our nondeterministic programs. + + + the amb syntactic form: + + + + is_amb + is_amb_amb + functions_4_1_2 + +(define (amb? exp) (tagged-list? exp 'amb)) + +(define (amb-choices exp) (cdr exp)) + + +function is_amb(component) { + return is_tagged_list(component, "application") && + is_name(function_expression(component)) && + symbol_of_name(function_expression(component)) === "amb"; +} +function amb_choices(component) { + return arg_expressions(component); +} + + + + + + +\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. The 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.With this treatment, + amb is no longer a name with + proper scoping. To avoid confusion, we must + refrain from declaring amb as a + name in our nondeterministic programs. + + + + + + We must also add to the dispatch in analyze a + clause that will recognize + + this special form and generate an appropriate execution procedure: + + such expressions and generate an appropriate execution + function: + + + is_amb_case_amb + all_solutions_test_5 + +((amb? exp) (analyze-amb exp)) + + +$\ldots$ +: is_amb(component) +? analyze_amb(component) +: is_application(component) +$\ldots$ + + + + + + + + The top-level + + procedure + function + + ambeval (similar to the version of + + eval + evaluate + + given in section) analyzes the + given + + expression + component + + and applies the resulting execution + + procedure + function + + to the given environment, together with two given continuations: + + analyze_amb_headline + +// functions from SICP JS 4.3.3 + + + + analyze_amb + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + analyze_amb_headline + is_amb_amb + analyze_literal_amb + analyze_variable_amb + analyze_lambda_amb + analyze_sequence_amb + analyze_declaration_amb + analyze_assignment_amb + analyze_if_amb + scan_out_declarations + analyze_block_amb + analyze_return_statement_amb + analyze_application_amb + analyze_amb_amb + + (define (analyze exp) + (cond ((self-evaluating? exp) + (analyze-self-evaluating exp)) + ((quoted? exp) (analyze-quoted exp)) + ((variable? exp) (analyze-variable exp)) + ((assignment? exp) (analyze-assignment exp)) + ((definition? exp) (analyze-definition exp)) + ((if? exp) (analyze-if exp)) + ((lambda? exp) (analyze-lambda exp)) + ((begin? exp) (analyze-sequence (begin-actions exp))) + ((cond? exp) (analyze (cond->if exp))) + ((application? exp) (analyze-application exp)) + (else + (error "Unknown expression type - - ANALYZE" exp)))) + + +function analyze(component) { + return is_literal(component) + ? analyze_literal(component) + : is_name(component) + ? analyze_name(component) + : is_amb(component) + ? analyze_amb(component) + : is_application(component) + ? analyze_application(component) + : is_operator_combination(component) + ? analyze(operator_combination_to_application(component)) + : is_conditional(component) + ? analyze_conditional(component) + : is_lambda_expression(component) + ? analyze_lambda_expression(component) + : is_sequence(component) + ? analyze_sequence(sequence_statements(component)) + : is_block(component) + ? analyze_block(component) + : is_return_statement(component) + ? analyze_return_statement(component) + : is_function_declaration(component) + ? analyze(function_decl_to_constant_decl(component)) + : is_declaration(component) + ? analyze_declaration(component) + : is_assignment(component) + ? analyze_assignment(component) + : error(component, "unknown syntax -- analyze"); +} + + + + ambeval + ambeval + analyze_amb + all_solutions_test_4 + +(define (ambeval exp env succeed fail) + ((analyze exp) env succeed fail)) + + +function ambeval(component, env, succeed, fail) { + return analyze(component)(env, succeed, fail); +} + + + + + + A success + success continuation (nondeterministic evaluator) + continuationin nondeterministic evaluator + continuation is a + + procedure + function + + of two arguments: the value just obtained and another failure continuation to + be used if that value leads to a subsequent failure. A + failure continuation (nondeterministic evaluator) + failure continuation + is a + + procedure + function + + of no arguments. So + the general form of an + execution procedurefunctionin nondeterministic evaluator + execution + + procedure + function + + is + + +(lambda (env succeed fail) + ;; succeed is (lambda (value fail) $\ldots$) + ;; fail is (lambda () $\ldots$) + $\ldots$) + + +(env, succeed, fail) => { + // $\texttt{succeed}\,$ is $\texttt{(value, fail) =>}~\ldots$ + // $\texttt{fail}\,$ is $\texttt{() =>}~\ldots$ + $\ldots$ +} + + + + + For example, executing + + + + +(ambeval exp + the-global-environment + (lambda (value fail) value) + (lambda () 'failed)) + + + + + + +ambeval(component, + the_global_environment, + (value, fail) => value, + () => "failed"); + + + + + will attempt to evaluate the given + + expression + component + + and will return either the + + expressions + components + + value (if the evaluation succeeds) or the + + + symbol failed + + + string "failed" + + + (if the evaluation fails). + The call to ambeval in the driver loop shown + below uses much more complicated continuation + + procedures, + functions, + + which continue the loop and support the + + + try-again + + + retry + + + request. + + + + Most of the complexity of the amb evaluator + results from the mechanics of passing the continuations around as the + execution + + procedures + functions + + call each other. In going through the following code, you should compare + each of the execution + + procedures + functions + + with the corresponding + + procedure + function + + for the ordinary evaluator given in + section. + + + + Simple expressions + + + analyze_...nondeterministic + + + The execution + + procedures + functions + + for the simplest kinds of expressions are + essentially the same as those for the ordinary evaluator, except for the + need to manage the continuations. The execution + + procedures + functions + + simply succeed with the value of the expression, passing along the failure + continuation that was passed to them. + + analyze_literal_amb + all_solutions_test_4 + +(define (analyze-self-evaluating exp) + (lambda (env succeed fail) + (succeed exp fail))) + + +function analyze_literal(component) { + return (env, succeed, fail) => + succeed(literal_value(component), fail); +} + + + + analyze_variable_amb + all_solutions_test_4 + +(define (analyze-variable exp) + (lambda (env succeed fail) + (succeed (lookup-variable-value exp env) + fail))) + + +function analyze_name(component) { + return (env, succeed, fail) => + succeed(lookup_symbol_value(symbol_of_name(component), + env), + fail); +} + + + + analyze_lambda_amb + all_solutions_test_4 + +(define (analyze-lambda exp) + (let ((vars (lambda-parameters exp)) + (bproc (analyze-sequence (lambda-body exp)))) + (lambda (env succeed fail) + (succeed (make-procedure vars bproc env) + fail)))) + + +function analyze_lambda_expression(component) { + const params = lambda_parameter_symbols(component); + const bfun = analyze(lambda_body(component)); + return (env, succeed, fail) => + succeed(make_function(params, bfun, env), + fail); +} + + + + + + Notice that looking up a + + variable + name + + always succeeds. + failure, in nondeterministic computationbug vs. + If + + + lookup-variable-value + + + lookup_symbol_value + + + fails to find the + + variable, + name, + + it signals an + error, as usual. Such a failure indicates a program + buga reference to an unbound + + variable; + name; + + it is not an indication + that we should try another nondeterministic choice instead of the one that + is currently being tried. + + + + + Conditionals and sequences + + + + Conditionals are also handled in a similar way as in the ordinary + evaluator. The execution + + procedure + function + + generated by + + + analyze-if + + + analyze_conditional + + + invokes the predicate execution + + procedure + pproc + + function + pfun + + + with a success continuation that checks whether the predicate value is true + and goes on to execute either the consequent or the alternative. If the + execution of + + + pproc + + + pfun + + + fails, the original failure continuation for + the + + + if + + + conditional + + + expression is called. + + analyze_if_amb + all_solutions_test_4 + +(define (analyze-if exp) + (let ((pproc (analyze (if-predicate exp))) + (cproc (analyze (if-consequent exp))) + (aproc (analyze (if-alternative exp)))) + (lambda (env succeed fail) + (pproc env + ;; success continuation for evaluating the predicate + ;; to obtain pred-value + (lambda (pred-value fail2) + (if (true? pred-value) + (cproc env succeed fail2) + (aproc env succeed fail2))) + ;; failure continuation for evaluating the predicate + fail)))) + + +function analyze_conditional(component) { + const pfun = analyze(conditional_predicate(component)); + const cfun = analyze(conditional_consequent(component)); + const afun = analyze(conditional_alternative(component)); + return (env, succeed, fail) => + pfun(env, + // success continuation for evaluating the predicate + // to obtain $\texttt{pred\char`_value}$ + (pred_value, fail2) => + is_truthy(pred_value) + ? cfun(env, succeed, fail2) + : afun(env, succeed, fail2), + // failure continuation for evaluating the predicate + fail); +} + + + + + + + + Sequences are also handled in the same way as in the previous + evaluator, except for the machinations in the + subprocedure + sequentially that are required for passing the + continuations. Namely, to sequentially execute a + and then b, we call + a with a success continuation that calls + b. + + + (define (analyze-sequence exps) + (define (sequentially a b) + (lambda (env succeed fail) + (a env + ;; success continuation for calling a + (lambda (a-value fail2) + (b env succeed fail2)) + ;; failure continuation for calling a + fail))) + (define (loop first-proc rest-procs) + (if (null? rest-procs) + first-proc + (loop (sequentially first-proc (car rest-procs)) + (cdr rest-procs)))) + (let ((procs (map analyze exps))) + (if (null? procs) + (error "Empty sequence - - ANALYZE")) + (loop (car procs) (cdr procs)))) + + + + + Sequences are also handled in the same way as in the previous + evaluator, except for the machinations in the + subfunction + sequentially that are required for passing the + continuations. Namely, to sequentially execute a + and then b, we call + a with a success continuation that calls + b. + + analyze_sequence_amb + all_solutions_test_4 + +function analyze_sequence(stmts) { + function sequentially(a, b) { + return (env, succeed, fail) => + a(env, + // success continuation for calling $\texttt{a}$ + (a_value, fail2) => + is_return_value(a_value) + ? succeed(a_value, fail2) + : b(env, succeed, fail2), + // failure continuation for calling $\texttt{a}$ + fail); + } + function loop(first_fun, rest_funs) { + return is_null(rest_funs) + ? first_fun + : loop(sequentially(first_fun, head(rest_funs)), + tail(rest_funs)); + } + const funs = map(analyze, stmts); + return is_null(funs) + ? env => undefined + : loop(head(funs), tail(funs)); +} + + + + + + + + + + Definitions + Declarations + + and assignments + + + + + + Definitions + Declarations + + are another case where we must go to some trouble to + manage the continuations, because it is necessary to evaluate the + + + definition-value expression before actually defining the new variable. + + + declaration-value expression before actually declaring the new name. + + + To accomplish this, the + + definition-value + declaration-value + + execution + + procedure + vproc + + function + vfun + + + is called with the environment, a success continuation, and the + failure continuation. If the execution of + + + vproc + + + vfun + + + succeeds, obtaining a value val for the + + + defined variable, the variable is defined and the success is propagated: + + + declared name, the name is declared and the success is propagated: + + + + analyze_declaration_amb + all_solutions_test_4 + +(define (analyze-definition exp) + (let ((var (definition-variable exp)) + (vproc (analyze (definition-value exp)))) + (lambda (env succeed fail) + (vproc env + (lambda (val fail2) + (define-variable! var val env) + (succeed 'ok fail2)) + fail)))) + + +function analyze_declaration(component) { + const symbol = declaration_symbol(component); + const vfun = analyze(declaration_value_expression(component)); + return (env, succeed, fail) => + vfun(env, + (val, fail2) => { + assign_symbol_value(symbol, val, env); + return succeed(undefined, fail2); + }, + fail); +} + + + + + + Assignments + failure continuation (nondeterministic evaluator)constructed by assignment + are more interesting. This is the first place where we + really use the continuations, rather than just passing them around. + The execution + + procedure + function + + for assignments starts out like the one for + + definitions. + declarations. + + It first attempts + to obtain the new value to be assigned to the + + variable. + name. + + If this evaluation of + + vproc + vfun + + fails, the assignment fails. + + + + If + + vproc + vfun + + succeeds, however, and we go on to make the assignment, we must consider the + possibility that this branch of the computation might later fail, which will + require us to backtrack out of the assignment. Thus, we must arrange to + undo the assignment as part of the backtracking process.We + + + didnt worry about undoing definitions, + since we can + assume that + internal definitionnondeterministicin nondeterministic evaluator + internal definitions + are scanned out + (section). + + + didnt worry about undoing declarations, since we assume that a + name cant be used prior to the evaluation of its declaration, + internal declarationnondeterministicin nondeterministic evaluator + so its previous value doesnt matter. + + + + + + This is accomplished by giving + + vproc + vfun + + a success continuation (marked with the comment *1* below) + that saves the old value of the variable before assigning the new value to + the variable and proceeding from the assignment. The failure continuation + that is passed along with the value of the assignment (marked with the + comment *2* below) restores the old value of the variable + before continuing the failure. That is, a successful assignment provides a + failure continuation that will intercept a subsequent failure; whatever + failure would otherwise have called fail2 calls + this + + procedure + function + + instead, to undo the assignment before actually calling + fail2. + + analyze_assignment_amb + all_solutions_test_4 + +(define (analyze-assignment exp) + (let ((var (assignment-variable exp)) + (vproc (analyze (assignment-value exp)))) + (lambda (env succeed fail) + (vproc env + (lambda (val fail2) ; *1* + (let ((old-value + (lookup-variable-value var env))) + (set-variable-value! var val env) + (succeed 'ok + (lambda () ; *2* + (set-variable-value! var + old-value + env) + (fail2))))) + fail)))) + + +function analyze_assignment(component) { + const symbol = assignment_symbol(component); + const vfun = analyze(assignment_value_expression(component)); + return (env, succeed, fail) => + vfun(env, + (val, fail2) => { // *1* + const old_value = lookup_symbol_value(symbol, + env); + assign_symbol_value(symbol, val, env); + return succeed(val, + () => { // *2* + assign_symbol_value(symbol, + old_value, + env); + return fail2(); + }); + }, + fail); +} + + + + + + + + + + Return statements and blocks + + + + Analyzing return statements is straightforward. + The return expression is analyzed to produce an execution function. + The execution function for the return statement calls that execution + function with a success continuation that wraps the return value + in a return value object and passes it to the original success continuation. + + analyze_return_statement_amb + all_solutions_test_4 + +function analyze_return_statement(component) { + const rfun = analyze(return_expression(component)); + return (env, succeed, fail) => + rfun(env, + (val, fail2) => + succeed(make_return_value(val), fail2), + fail); +} + + + + + The execution function for blocks calls the bodys execution + function on an extended environment, without changing success or + failure continuations. + + analyze_block_amb + list_of_unassigned + all_solutions_test_4 + +function analyze_block(component) { + const body = block_body(component); + const locals = scan_out_declarations(body); + const unassigneds = list_of_unassigned(locals); + const bfun = analyze(body); + return (env, succeed, fail) => + bfun(extend_environment(locals, unassigneds, env), + succeed, + fail); +} + + + + + + + + + + Procedure + Function + + applications + + + + + The execution + + procedure + function + + for applications contains no new ideas except for the technical complexity + of managing the continuations. This complexity arises in + + analyze-application, + + analyze_@application, + + due to the need to keep track of the success and failure continuations as + we evaluate the + + operands. + argument expressions. + + We use a + + procedure get-args + function get_args + + + to evaluate the list of + + operands, + argument expressions, + + rather than a simple + map as in the ordinary evaluator. + + analyze_application_amb + get_args_amb + execute_application_amb + all_solutions_test_4 + +(define (analyze-application exp) + (let ((fproc (analyze (operator exp))) + (aprocs (map analyze (operands exp)))) + (lambda (env succeed fail) + (fproc env + (lambda (proc fail2) + (get-args aprocs + env + (lambda (args fail3) + (execute-application + proc args succeed fail3)) + fail2)) + fail)))) + + +function analyze_application(component) { + const ffun = analyze(function_expression(component)); + const afuns = map(analyze, arg_expressions(component)); + return (env, succeed, fail) => + ffun(env, + (fun, fail2) => + get_args(afuns, + env, + (args, fail3) => + execute_application(fun, + args, + succeed, + fail3), + fail2), + fail); +} + + + + + analyze_...nondeterministic + + + + + In get-args, notice how + cdring down the list of + aproc + execution procedures and + consing + up the resulting list of + args is accomplished by calling each + aproc + in the list with a success continuation that recursively calls + get-args. + + + In get_args, + notice how walking down the list of + afun + execution functions + and constructing the resulting list of + args + is accomplished by calling each + afun + in the list with a success continuation that recursively calls + get_args. + + + Each of these recursive calls to + + get-args + get_args + + has a success continuation whose value is the + + + cons + of the newly obtained argument onto the list of accumulated arguments: + + + new list resulting from using + pair + to adjoin the newly obtained argument + to the list of accumulated arguments: + + + + get_args_amb + all_solutions_test_4 + +(define (get-args aprocs env succeed fail) + (if (null? aprocs) + (succeed '() fail) + ((car aprocs) env + ;; success continuation for this aproc + (lambda (arg fail2) + (get-args (cdr aprocs) + env + ;; success continuation for recursive + ;; call to get-args + (lambda (args fail3) + (succeed (cons arg args) + fail3)) + fail2)) + fail))) + + +function get_args(afuns, env, succeed, fail) { + return is_null(afuns) + ? succeed(null, fail) + : head(afuns)(env, + // success continuation for this $\texttt{afun}$ + (arg, fail2) => + get_args(tail(afuns), + env, + // success continuation for + // recursive call to $\texttt{get\char`_args}$ + (args, fail3) => + succeed(pair(arg, args), + fail3), + fail2), + fail); +} + + + + + + The actual + + procedure + function + + application, which is performed by + + execute-application, + execute_application, + + + is accomplished in the same way as for the ordinary evaluator, except for + the need to manage the continuations. + + execute_applicationnondeterministic + execute_application_amb + all_solutions_test_4 + +(define (execute-application proc args succeed fail) + (cond ((primitive-procedure? proc) + (succeed (apply-primitive-procedure proc args) + fail)) + ((compound-procedure? proc) + ((procedure-body proc) + (extend-environment (procedure-parameters proc) + args + (procedure-environment proc)) + succeed + fail)) + (else + (error + "Unknown procedure type - - EXECUTE-APPLICATION" + proc)))) + + +function execute_application(fun, args, succeed, fail) { + return is_primitive_function(fun) + ? succeed(apply_primitive_function(fun, args), + fail) + : is_compound_function(fun) + ? function_body(fun)( + extend_environment(function_parameters(fun), + args, + function_environment(fun)), + (body_result, fail2) => + succeed(is_return_value(body_result) + ? return_value_content(body_result) + : undefined, + fail2), + fail) + : error(fun, "unknown function type - execute_application"); +} + + + + + + + Evaluating amb expressions + + + + The amb + + special + syntactic + + form is the key element in the nondeterministic language. Here we see the + essence of the interpretation process and the reason for keeping track of + the continuations. The execution + + procedure + function + + for amb defines a loop + + try-next + try_next + + that cycles through the execution + + procedures + functions + + for all the possible values of the amb + expression. Each execution + + procedure + function + + is called with a + failure continuation (nondeterministic evaluator)constructed by ambconstructed by amb + failure continuation that will try the next one. When + there are no more alternatives to try, the entire + amb expression fails. + + analyze_amb + analyze_amb_amb + all_solutions_test_4 + +(define (analyze-amb exp) + (let ((cprocs (map analyze (amb-choices exp)))) + (lambda (env succeed fail) + (define (try-next choices) + (if (null? choices) + (fail) + ((car choices) env + succeed + (lambda () + (try-next (cdr choices)))))) + (try-next cprocs)))) + + +function analyze_amb(component) { + const cfuns = map(analyze, amb_choices(component)); + return (env, succeed, fail) => { + function try_next(choices) { + return is_null(choices) + ? fail() + : head(choices)(env, + succeed, + () => + try_next(tail(choices))); + } + return try_next(cfuns); + }; +} + + + + + + Driver loop + + + driver loopnondeterministicin nondeterministic evaluator + + + The driver loop for the amb evaluator is + complex, due to the mechanism that permits the user to retry in evaluating + + an expression. + a program. + + The driver uses a + + procedure + function + + called + + internal-loop, + internal_loop, + + + which takes as argument a + + procedure + function + + failure continuation (nondeterministic evaluator)constructed by driver loop + + + try-again. + + + retry. + + + The intent is that calling + + try-again + + + retry + + should go on to the next untried alternative in the nondeterministic + evaluation. + + Internal-loop + + The function + internal_loop + + either calls + + try-again + retry + + in response to the user typing + + try-again + + retry + + + at the driver loop, or else starts a new evaluation by calling + ambeval. + + + + The failure continuation for this call to + ambeval + + informs the user that there are no more values and reinvokes the driver + loop. + + + + The success continuation for the call to ambeval + + is more subtle. We print the obtained value and then + + + invoke the internal loop again + + + reinvoke the internal loop + + + with a + + + try-again + + + retry + + + + procedure + function + + that will be able to try the next alternative. This + + + next-alternative + + + next_alternative + + + + procedure + function + + is the second argument that was passed to the success continuation. + Ordinarily, we think of this second argument as a failure continuation to + be used if the current evaluation branch later fails. In this case, + however, we have completed a successful evaluation, so we can invoke the + failure alternative branch in order to search for additional + successful evaluations. + + driver_loop_amb_example + +(driver-loop) + + +driver_loop(); + + + + promptsnondeterministic evaluator + driver_loopfor nondeterministic evaluator + driver_loop_amb + ambeval + user_print + user_read + driver_loop_amb_example + +(define input-prompt ";;; Amb-Eval input:") +(define output-prompt ";;; Amb-Eval value:") + +(define (driver-loop) + (define (internal-loop try-again) + (prompt-for-input input-prompt) + (let ((input (read))) + (if (eq? input 'try-again) + (try-again) + (begin + (newline) + (display ";;; Starting a new problem ") + (ambeval input + the-global-environment + ;; ambeval success + (lambda (val next-alternative) + (announce-output output-prompt) + (user-print val) + (internal-loop next-alternative)) + ;; ambeval failure + (lambda () + (announce-output + ";;; There are no more values of") + (user-print input) + (driver-loop))))))) + (internal-loop + (lambda () + (newline) + (display ";;; There is no current problem") + (driver-loop)))) + + +const input_prompt = "amb-evaluate input:"; +const output_prompt = "amb-evaluate value:"; + +function driver_loop(env) { + function internal_loop(retry) { + const input = user_read(input_prompt); + if (is_null(input)) { + display("evaluator terminated"); + } else if (input === "retry") { + return retry(); + } else { + display("Starting a new problem"); + const program = parse(input); + const locals = scan_out_declarations(program); + const unassigneds = list_of_unassigned(locals); + const program_env = extend_environment( + locals, unassigneds, env); + return ambeval( + program, + program_env, + // ambeval success + (val, next_alternative) => { + user_print(output_prompt, val); + return internal_loop(next_alternative); + }, + // ambeval failure + () => { + display("There are no more values of"); + display(input); + return driver_loop(program_env); + }); + } + } + return internal_loop(() => { + display("There is no current problem"); + return driver_loop(env); + }); +} + + +const input_prompt = "amb-evaluate input:"; +const output_prompt = "amb-evaluate value:"; +function driver_loop() { + function internal_loop(retry) { + const input = user_read(input_prompt); + if (is_null(input)) { + display("--- evaluator terminated ---", ""); + } else if (input === "retry") { + display("----------------------------", + input_prompt + "\n" + input + "\n"); + return retry(); + } else { + display("--- starting new problem ---", + input_prompt + "\n" + input + "\n"); + ambeval(parse("{ " + input + " }"), + the_global_environment, + // ambeval success + (val, next_alternative) => { + user_print(output_prompt, val); + return internal_loop(next_alternative); + }, + // ambeval failure + () => { + display("----------------------------", + "no more values of:\n" + input + "\n"); + return driver_loop(); + }); + } + } + return internal_loop( + () => { + display("--- no current problem ---", ""); + return driver_loop(); + }); +} + + + The initial call to + + internal-loop + internal_loop + + uses a + + try-again + retry + + + procedure + function + + that complains that there is no current problem and restarts the driver loop. + This is the behavior that will happen if the user types + + try-again + + retry + + + when there is no evaluation in progress. + + + + + We start the driver loop as usual, by setting up the global environment + and passing it as the enclosing environment for the first iteration of + driver_loop. + + +const the_global_environment = setup_environment(); +driver_loop(the_global_environment); + + + + + + + + Implement a new + + special + syntactic + + form ramb that is like + amb except that it searches alternatives in a + random order, rather than from left to right. Show how this can help with + Alyssas problem in exercise. + + + + + + + + Implement a new kind of assignment called + permanent-set! that + + + Change the implementation of assignment so that it + + + is not undone upon failure. For example, we can choose two distinct + elements from a list and count the number of trials required to make a + successful choice as follows: + + +(define count 0) + +(let ((x (an-element-of '(a b c))) + (y (an-element-of '(a b c)))) + (permanent-set! count (+ count 1)) + (require (not (eq? x y))) + (list x y count)) + + +;;; Starting a new problem +;;; Amb-Eval value: +(a b 2) + +;;; Amb-Eval input: + + +let count = 0; + +let x = an_element_of("a", "b", "c"); +let y = an_element_of("a", "b", "c"); +count = count + 1; +require(x !== y); +list(x, y, count); + + +Starting a new problem +amb-evaluate value: +["a", ["b", [2, null]]] + + + + +try-again + + +;;; Amb-Eval value: +(a c 3) + + +amb-evaluate input: + + +retry + + +amb-evaluate value: +["a", ["c", [3, null]]] + + + + + What values would have been displayed if we had used + set! here rather than + permanent-set!? + + + What values would have been displayed if we had used + the original meaning of assignment rather than + permanent assignment? + + + + + + + + + + Implement a new construct called if-fail + that permits the user to catch the failure of an expression. + If-fail takes two expressions. It evaluates + the first expression as usual and returns as usual if the evaluation + succeeds. If the evaluation fails, however, the value of the second + expression is returned, as in the following example: + + + + +;;; Amb-Eval input: + + + + +(if-fail (let ((x (an-element-of '(1 3 5)))) + (require (even? x)) + x) + 'all-odd) + + +;;; Starting a new problem +;;; Amb-Eval value: +all-odd + +;;; Amb-Eval input: + + + + +(if-fail (let ((x (an-element-of '(1 3 5 8)))) + (require (even? x)) + x) + 'all-odd) + + +;;; Starting a new problem +;;; Amb-Eval value: +8 + + + + + + + + We shall horribly abuse the syntax for conditional statements, by + implementing a construct of the following form: + + +if (evaluation_succeeds_take) { $statement$ } else { $alternative$ } + + + The construct permits the user to catch the failure of a + statement. It evaluates the statement as usual and returns + as usual if the evaluation succeeds. If the evaluation fails, + however, the given alternative statement + is evaluated, as in the following example: + + +amb-evaluate input: + + +if (evaluation_succeeds_take) { + const x = an_element_of(list(1, 3, 5)); + require(is_even(x)); + x; +} else { + "all odd"; +} + + +Starting a new problem +amb-evaluate value: +"all odd" + + + + +amb-evaluate input: + + +if (evaluation_succeeds_take) { + const x = an_element_of(list(1, 3, 5, 8)); + require(is_even(x)); + x; +} else { + "all odd"; +} + + +Starting a new problem +amb-evaluate value: +8 + + + Implement this construct by extending the + amb + evaluator. Hint: The function + is_amb shows + how to abuse the existing JavaScript syntax in order to implement + a new syntactic form. + + + + + + + With + + + permanent-set! + + + the new kind of assignment + + + as described in exercise and + + + if-fail + + + the construct + + +if (evaluation_succeeds_take) { $\ldots$ } else { $\ldots$ } + + + + + as in exercise, what will be the result of + evaluating + + +(let ((pairs '())) + (if-fail (let ((p (prime-sum-pair '(1 3 5 8) '(20 35 110)))) + (permanent-set! pairs (cons p pairs)) + (amb)) + pairs)) + + +let pairs = null; +if (evaluation_succeeds_take) { + const p = prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); + pairs = pair(p, pairs); // using permanent assignment + amb(); +} else { + pairs; +} + + + + + + + If we had not realized that + requireas a syntactic form + require could be + implemented as an ordinary + + procedure + function + + that uses amb, to be defined by the user as + part of a nondeterministic program, we would have had to implement it + as a + + special + syntactic + + form. This would require syntax + + procedures + functions + + + +(define (require? exp) (tagged-list? exp 'require)) + +(define (require-predicate exp) (cadr exp)) + + +function is_require(component) { + return is_tagged_list(component, "require"); +} +function require_predicate(component) { return head(tail(component)); } + + + and a new clause in the dispatch in analyze + + + ((require? exp) (analyze-require exp)) + + +: is_require(component) +? analyze_require(component) + + + as well the + + procedure + function + + + + analyze-require + + + analyze_require + + + that handles require + expressions. Complete the following definition of + + + analyze-require. + + + analyze_require. + + + + +(define (analyze-require exp) + (let ((pproc (analyze (require-predicate exp)))) + (lambda (env succeed fail) + (pproc env + (lambda (pred-value fail2) + (if ?? + ?? + (succeed 'ok fail2))) + fail)))) + + +function analyze_require(component) { + const pfun = analyze(require_predicate(component)); + return (env, succeed, fail) => + pfun(env, + (pred_value, fail2) => + ?? + ? ?? + : succeed("ok", fail2), + fail); +} + + + + + nondeterministic evaluator + + + all_solutions + ambeval + +function all_solutions(input) { + let solutions = null; + function internal_loop(input, retry) { + if (input === "retry") { + return retry(); + } else { + return ambeval(parse("{ " + input + " }"), + the_global_environment, + // ambeval success + (val, next_alternative) => { + solutions = pair(val, solutions); + return internal_loop("retry", next_alternative); + }, + // ambeval failure + () => undefined + ); + } + } + internal_loop( + input, + () => { + display("// internal error"); + }); + return reverse(solutions); +} + + + + first_solution + ambeval + +function first_solution(input) { + return ambeval(parse("{ " + input + " }"), + the_global_environment, + (val, next_alternative) => val, + () => undefined); +} + + + + all_solutions_test_1 + first_solution + all_solutions + [ 'apple', [ 'banana', [ 'cranberry', null ] ] ] + +all_solutions(" \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ + } \ +function an_element_of(items) { \ + require(! is_null(items)); \ + return amb(head(items), an_element_of(tail(items))); \ +} \ +const xs = list('apple', 'banana', 'cranberry'); \ +an_element_of(xs); "); + + + + all_solutions_test_2 + first_solution + all_solutions + [ 8, [ 35, null ] ] + +all_solutions(" \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ + } \ +function an_element_of(items) { \ + require(! is_null(items)); \ + return amb(head(items), an_element_of(tail(items))); \ +} \ +function square(x) { \ + return x * x; \ +} \ +function is_divisible(x, y) { \ + return x % y === 0; \ +} \ +function integers_starting_from(n) { \ + return pair(n, \ + () => integers_starting_from(n + 1) \ + ); \ +} \ +function is_prime(n) { \ + function iter(ps) { \ + return square(head(ps)) > n \ + ? true \ + : is_divisible(n, head(ps)) \ + ? false \ + : iter(stream_tail(ps)); \ + } \ + return iter(primes); \ +} \ +function stream_tail(xs) { \ + return tail(xs)(); \ +} \ +function stream_filter(pred, s) { \ + return is_null(s) \ + ? null \ + : pred(head(s)) \ + ? pair(head(s), \ + () => stream_filter(pred, \ + stream_tail(s))) \ + : stream_filter(pred, \ + stream_tail(s)); \ +} \ +const primes = pair(2, \ + () => stream_filter( \ + is_prime, \ + integers_starting_from(3)) \ + ); \ +function prime_sum_pair(list1, list2) { \ + const a = an_element_of(list1); \ + const b = an_element_of(list2); \ + require(is_prime(a + b)); \ + return list(a, b); \ +} \ +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); "); + + +list_ref(all_solutions(" \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ + } \ +function an_element_of(items) { \ + require(! is_null(items)); \ + return amb(head(items), an_element_of(tail(items))); \ +} \ +function square(x) { \ + return x * x; \ +} \ +function is_divisible(x, y) { \ + return x % y === 0; \ +} \ +function integers_starting_from(n) { \ + return pair(n, \ + () => integers_starting_from(n + 1) \ + ); \ +} \ +function is_prime(n) { \ + function iter(ps) { \ + return square(head(ps)) > n \ + ? true \ + : is_divisible(n, head(ps)) \ + ? false \ + : iter(stream_tail(ps)); \ + } \ + return iter(primes); \ +} \ +function stream_tail(xs) { \ + return tail(xs)(); \ +} \ +function stream_filter(pred, s) { \ + return is_null(s) \ + ? null \ + : pred(head(s)) \ + ? pair(head(s), \ + () => stream_filter(pred, \ + stream_tail(s))) \ + : stream_filter(pred, \ + stream_tail(s)); \ +} \ +const primes = pair(2, \ + () => stream_filter( \ + is_prime, \ + integers_starting_from(3)) \ + ); \ +function prime_sum_pair(list1, list2) { \ + const a = an_element_of(list1); \ + const b = an_element_of(list2); \ + require(is_prime(a + b)); \ + return list(a, b); \ +} \ +prime_sum_pair(list(1, 3, 5, 8), list(20, 35, 110)); "), + 4); + + + + all_solutions_test_3 + first_solution + all_solutions + [ 'verb', [ 'eats', null ] ] + +all_solutions(" \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ +} \ +function member(item, x) { \ + return is_null(x) \ + ? null \ + : item === head(x) \ + ? x \ + : member(item, tail(x)); \ +} \ +let unparsed = null; \ +const nouns = list('noun', 'student', 'professor', 'cat', 'class'); \ +const verbs = list('verb', 'studies', 'lectures', 'eats', 'sleeps'); \ +const articles = list('article', 'the', 'a'); \ +function parse_word(word_list) { \ + require(! is_null(unparsed)); \ + require(! is_null(member(head(unparsed), tail(word_list)))); \ + const found_word = head(unparsed); \ + unparsed = tail(unparsed); \ + return list(head(word_list), found_word); \ +} \ +function parse_noun_phrase() { \ + return list('noun-phrase', \ + parse_word(articles), \ + parse_word(nouns)); \ +} \ +function parse_sentence() { \ + return list('sentence', \ + parse_noun_phrase(), \ + parse_word(verbs)); \ +} \ +function parse_input(input) { \ + unparsed = input; \ + const sent = parse_sentence(); \ + require(is_null(unparsed)); \ + return sent; \ +} \ +parse_input(list('the', 'cat', 'eats')); "); + + +list_ref(list_ref(all_solutions(" \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ +} \ +function member(item, x) { \ + return is_null(x) \ + ? null \ + : item === head(x) \ + ? x \ + : member(item, tail(x)); \ +} \ +let unparsed = null; \ +const nouns = list('noun', 'student', 'professor', 'cat', 'class'); \ +const verbs = list('verb', 'studies', 'lectures', 'eats', 'sleeps'); \ +const articles = list('article', 'the', 'a'); \ +function parse_word(word_list) { \ + require(! is_null(unparsed)); \ + require(! is_null(member(head(unparsed), tail(word_list)))); \ + const found_word = head(unparsed); \ + unparsed = tail(unparsed); \ + return list(head(word_list), found_word); \ +} \ +function parse_noun_phrase() { \ + return list('noun-phrase', \ + parse_word(articles), \ + parse_word(nouns)); \ +} \ +function parse_sentence() { \ + return list('sentence', \ + parse_noun_phrase(), \ + parse_word(verbs)); \ +} \ +function parse_input(input) { \ + unparsed = input; \ + const sent = parse_sentence(); \ + require(is_null(unparsed)); \ + return sent; \ +} \ +parse_input(list('the', 'cat', 'eats')); "), + 0), + 2); + + + + full_parser + +const full_parser = " \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ +} \ +function member(item, x) { \ + return is_null(x) \ + ? null \ + : item === head(x) \ + ? x \ + : member(item, tail(x)); \ +} \ +let unparsed = null; \ +function parse_word(word_list) { \ + require(! is_null(unparsed)); \ + require(! is_null(member(head(unparsed), tail(word_list)))); \ + const found_word = head(unparsed); \ + unparsed = tail(unparsed); \ + return list(head(word_list), found_word); \ +} \ +const prepositions = list('prep', 'for', 'to', 'in', 'by', 'with'); \ +function parse_prepositional_phrase() { \ + return list('prep-phrase', \ + parse_word(prepositions), \ + parse_noun_phrase()); \ +} \ +const nouns = list('noun', 'student', 'professor', 'cat', 'class'); \ +const verbs = list('verb', 'studies', 'lectures', 'eats', 'sleeps'); \ + \ +const articles = list('article', 'the', 'a'); \ +function parse_simple_noun_phrase() { \ + return list('simple-noun-phrase', \ + parse_word(articles), \ + parse_word(nouns)); \ +} \ +function parse_noun_phrase() { \ + function maybe_extend(noun_phrase) { \ + return amb(noun_phrase, \ + maybe_extend(list('noun-phrase', \ + noun_phrase, \ + parse_prepositional_phrase()))); \ + } \ + return maybe_extend(parse_simple_noun_phrase()); \ +} \ +function parse_sentence() { \ + return list('sentence', \ + parse_noun_phrase(), \ + parse_verb_phrase()); \ +} \ +function parse_verb_phrase() { \ + function maybe_extend(verb_phrase) { \ + return amb(verb_phrase, \ + maybe_extend(list('verb-phrase', \ + verb_phrase, \ + parse_prepositional_phrase()))); \ + } \ + return maybe_extend(parse_word(verbs)); \ +} \ +function parse_input(input) { \ + unparsed = input; \ + const sent = parse_sentence(); \ + require(is_null(unparsed)); \ + return sent; \ +} "; + + + + all_solutions_test_4 + first_solution + all_solutions + full_parser + [ 'article', [ 'the', null ] ] + +all_solutions(full_parser + +" \ +parse_input(list('the', 'student', 'with', 'the', 'cat', \ +'sleeps', 'in', 'the', 'class')); "); + + +list_ref(list_ref(list_ref(list_ref(list_ref(all_solutions(full_parser + +" \ +parse_input(list('the', 'student', 'with', 'the', 'cat', \ +'sleeps', 'in', 'the', 'class')); "), + 0), + 2), + 2), + 2), + 1); + + + + all_solutions_test_5 + first_solution + all_solutions + full_parser + [ 'prep', [ 'with', null ] ] + +all_solutions(full_parser + +" \ +parse_input(list('the', 'professor', 'lectures', \ + 'to', 'the', 'student', 'with', 'the', 'cat')); "); + + +list_ref(list_ref(list_ref(list_ref(list_ref(list_ref(all_solutions(full_parser + +" \ +parse_input(list('the', 'professor', 'lectures', \ + 'to', 'the', 'student', 'with', 'the', 'cat')); "), + 1), + 2), + 2), + 2), + 2), + 1); + + + + first_solutions_test_1 + first_solution + all_solutions + + +first_solution(" \ +function require(p) { \ + return ! p ? amb() : 'Satisfied require'; \ +} \ +function distinct(items) { \ + return is_null(items) \ + ? true \ + : is_null(tail(items)) \ + ? true \ + : is_null(member(head(items), tail(items))) \ + ? distinct(tail(items)) \ + : false; \ +} \ +function member(item, x) { \ + return is_null(x) \ + ? null \ + : item === head(x) \ + ? x \ + : member(item, tail(x)); \ +} \ +function multiple_dwelling() { \ + const baker = amb(1, 2, 3, 4, 5); \ + const cooper = amb(1, 2, 3, 4, 5); \ + const fletcher = amb(1, 2, 3, 4, 5); \ + const miller = amb(1, 2, 3, 4, 5); \ + const smith = amb(1, 2, 3, 4, 5); \ + require(distinct(list(baker, cooper, fletcher, miller, smith))); \ + require(! (baker === 5)); \ + require(! (cooper === 1)); \ + require(! (fletcher === 5)); \ + require(! (fletcher === 1)); \ + require(miller > cooper); \ + require(! (math_abs(smith - fletcher) === 1)); \ + require(! (math_abs(fletcher - cooper) === 1)); \ + return list(list('baker', baker), \ + list('cooper', cooper), \ + list('fletcher', fletcher), \ + list('miller', miller), \ + list('smith', smith)); \ +} \ +multiple_dwelling(); "); + + +
diff --git a/xml/cn/chapter4/section4/section4.xml b/xml/cn/chapter4/section4/section4.xml new file mode 100644 index 000000000..0cfa21ed8 --- /dev/null +++ b/xml/cn/chapter4/section4/section4.xml @@ -0,0 +1,418 @@ +
+ Logic Programming + + + + + + logic programming + + + In chapter we stressed that computer science deals with + declarative vs.imperative knowledge + imperative vs.declarative knowledge + mathematicscomputer science vs. + computer sciencemathematics vs. + imperative + (how to) knowledge, whereas mathematics deals with declarative (what + is) knowledge. Indeed, programming languages require that the + programmer express knowledge in a form that indicates the step-by-step + methods for solving particular problems. On the other hand, + high-level languages provide, as part of the language implementation, + a substantial amount of methodological knowledge that frees + the user from concern with numerous details of how a specified + computation will progress. + + + + 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. + + + + 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 + relations, computing in terms of + relations rather than with single-valued functions. Logic + programming extends this idea by combining a relational vision of programming + with a powerful kind of symbolic pattern matching called + unification.Logic programming has grown out of a long + logic programminghistory of + history of research in + theorem proving (automatic) + automatic theorem proving. Early theorem-proving + programs could accomplish very little, because they exhaustively searched + the space of possible proofs. The major breakthrough that made such a + search plausible was the discovery in the early 1960s of the + unificationdiscovery of algorithm + unification algorithm and the + resolution principle + resolution principle (Robinson 1965). + Resolution was used, for example, by + Green, Cordell + Raphael, Bertram + Green and Raphael (1968) (see also Green 1969) as the + basis for a deductive question-answering system. During most of this period, + researchers concentrated on algorithms that are guaranteed to find a proof if + one exists. Such algorithms were difficult to control and to direct toward + a proof. + Hewitt, Carl Eddie + Hewitt (1969) recognized the possibility of merging the control structure of + a programming language with the operations of a logic-manipulation system, + leading to the work in automatic search mentioned in + section + (footnote). At the same time that this + was being done, + Colmerauer, Alain + Colmerauer, in Marseille, was developing rule-based systems for manipulating + natural language (see Colmerauer et al.1973). + He invented a programming language called + Prolog + Prolog for representing those rules. + Kowalski, Robert + Kowalski (1973; 1979) + in Edinburgh, recognized that execution of a Prolog program could be + interpreted as proving theorems (using a proof technique called linear + resolution, Horn-clause + Horn-clause resolution). The merging of the last two strands led to the + logic-programming movement. Thus, in assigning credit for the development + of logic programming, the French can point to Prologs genesis at the + University of Marseille + University of Marseille, while the British can highlight the work at the + University of Edinburgh + University of Edinburgh. According to people at + MIT + MIT, logic programming was developed by these groups in an attempt to figure + out what Hewitt was talking about in his brilliant but impenetrable Ph.D. + thesis. For a history of logic + programming, see + Robinson, J. A. + Robinson 1983. + + + + This approach, when it works, can be a very + declarative vs.imperative knowledgelogic programming and + imperative vs.declarative knowledgelogic programming and + powerful way to write programs. + Part of the power comes from the fact that a single 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 + appendwhatwhat is (rules) vs.how to (procedurefunction) + 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 + + (consuz) + pair(u, z) + + + if v and y + append to form + z.To see the correspondence + between the rules and the + + procedure, + function, + + let x in the + + procedure + function + + (where x is nonempty) correspond to + + (cons u v) + pair(u, v) + + + in the rule. Then z in the rule corresponds + to the append of + + + (cdr x) + tail(x) + + and y. +
  • +
+ 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 + + cant answer: +
+ Find a list y + that + appends 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 + logic programminglogic programming languages + programming languagelogic + logic programming language, the programmer writes an + append + + procedure + function + + by stating the two rules about append given + above. + appendwhatwhat is (rules) vs.how to (procedurefunction) + 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.This certainly does not + relieve the user of the entire problem of how to compute the answer. There + are many different mathematically equivalent sets of rules for formulating + the append relation, only some of which can be + turned into effective devices for computing in any direction. In addition, + sometimes what is information gives no clue + how to compute an answer. For example, consider the problem + of computing the $y$ such that + $y^2 = x$. +
+ + + 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.Interest in logic programming peaked + logic programminghistory of + logic programmingin Japan + logic programmingcomputers for + during the early 1980s when the Japanese government began an ambitious + project aimed at building superfast computers optimized to run logic + programming languages. The speed of such computers was to be measured + in LIPS (Logical Inferences Per Second) rather than the usual FLOPS + (FLoating-point Operations Per Second). Although the project + succeeded in developing hardware and software as originally planned, + the international computer industry moved in a different direction. + See + Feigenbaum, Edward + Shrobe, Howard E. + Feigenbaum and Shrobe 1993 for an overview evaluation + of the Japanese project. The logic programming community has also moved on + to consider relational programming based on techniques other than + simple pattern matching, such as the ability to deal with numerical + constraints such as the ones illustrated in the constraint-propagation + system of section. + + + declarative vs.imperative knowledgelogic programming and + imperative vs.declarative knowledgelogic programming and + + + 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 + query language, because it is very useful for + retrieving information from data bases by formulating + query + 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 + query interpreter + query-language interpreter contains many of the same elements + found in the interpreter of section. In + particular, there will be an evaluate part that classifies + expressions according to type and an apply part that + implements the languages 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. + + + logic programming + + + &subsection4.4.1; + + + &subsection4.4.2; + + + &subsection4.4.3; + + + &subsection4.4.4; + +
diff --git a/xml/cn/chapter4/section4/subsection1.xml b/xml/cn/chapter4/section4/subsection1.xml new file mode 100644 index 000000000..d1e22793e --- /dev/null +++ b/xml/cn/chapter4/section4/subsection1.xml @@ -0,0 +1,2345 @@ + + + Deductive Information Retrieval + + + + + query language + + + Logic programming excels in providing interfaces to + data baselogic programming and + data bases for + information retrieval. The query language we shall implement in this + chapter is designed to be used in this way. + + + + In order to illustrate what the query system does, we will show how it + can be used to manage the data base of personnel records for + Gargle + Gargle, a thriving high-technology company in the + Boston area. The language provides pattern-directed access to + personnel information and can also take advantage of general rules in + order to make logical deductions. + + + + A sample data base + + + query languagedata base + + + data baseGargle personnel + The personnel data base for Gargle + contains + assertion + assertions about company personnel. Here is the + information about Ben Bitdiddle, the resident computer wizard: + + sample_data_base_1 + process_query + sample_data_base_1_example + +(address (Bitdiddle Ben) (Slumerville (Ridge Road) 10)) +(job (Bitdiddle Ben) (computer wizard)) +(salary (Bitdiddle Ben) 60000) + + +address(list("Bitdiddle", "Ben"), + list("Slumerville", list("Ridge", "Road"), 10)) +job(list("Bitdiddle", "Ben"), list("computer", "wizard")) +salary(list("Bitdiddle", "Ben"), 122000) + + +process_query(`assert(address(list("Bitdiddle", "Ben"), + list("Slumerville", list("Ridge", "Road"), 10)))`); +process_query(`assert(job(list("Bitdiddle", "Ben"), + list("computer", "wizard")))`); +process_query(`assert(salary(list("Bitdiddle", "Ben"), 122000))`); + + + + sample_data_base_1_example + sample_data_base_1 + process_query + 'salary(list("Bitdiddle", "Ben"), 122000)' + +process_query('salary(list("Bitdiddle", "Ben"), $x)'); + + +first_answer('salary(list("Bitdiddle", "Ben"), $x)'); + + + + Another option for syntactically representing facts in the data base + would have been to use simply lists, also for the top level + predicates, as in the original book. We did not resist the + temptation to syntactically introduce predicate symbols, + for the following reasons: +
    +
  • + In JavaScript, facts in a datalog data base look much more natural + when rendered as p(x, y) rather + than as list("p", x, y). +
  • +
  • + The analogy between logic programming and functional programming + comes out much clearer; the predicate is in familiar position + and the reader is reminded of function declarations when reading: + append_to_form(list("a", "b"), y, list("a", "b", "c", "d")) rather than + list("append_to_form", list("a", "b"), y, list("a", "b", "c", "d")). +
  • +
  • + The most decisive argument is that in this treatment, function + symbols (as in the predicate calculus) come for free: We can write + predicates such as plus without + any additional effort in the interpreter: + rule(plus($x, 0, $x) and + rule(plus($x, s($y), s($z)), plus($x, $y, $z)). +
  • +
+
+ + + Each assertion is a list (in this case a triple) whose elements can + themselves be lists. + + + Assertions look just like function applications in JavaScript, but + they actually represent information in the data base. The 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. The first symbols do not need to be declared, as do constants + or variables in JavaScript; their scope is global. + + We shy away from calling these symbols + predicate symbols instead of + kind of information + because the term + predicate is already used, also in this section, + for JavaScript expressions and functions that return boolean values. + + + +
+ + + + As resident wizard, Ben is in charge of the companys computer + division, and he supervises two programmers and one technician. Here + is the information about them: + + sample_data_base_2 + process_query + sample_data_base_1 + 'supervisor(list("Tweakit", "Lem", "E"), list("Bitdiddle", "Ben"))' + sample_data_base_2_example + +(address (Hacker Alyssa P) (Cambridge (Mass Ave) 78)) +(job (Hacker Alyssa P) (computer programmer)) +(salary (Hacker Alyssa P) 40000) +(supervisor (Hacker Alyssa P) (Bitdiddle Ben)) + +(address (Fect Cy D) (Cambridge (Ames Street) 3)) +(job (Fect Cy D) (computer programmer)) +(salary (Fect Cy D) 35000) +(supervisor (Fect Cy D) (Bitdiddle Ben)) + +(address (Tweakit Lem E) (Boston (Bay State Road) 22)) +(job (Tweakit Lem E) (computer technician)) +(salary (Tweakit Lem E) 25000) +(supervisor (Tweakit Lem E) (Bitdiddle Ben)) + + +address(list("Hacker", "Alyssa", "P"), + list("Cambridge", list("Mass", "Ave"), 78)) +job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")) +salary(list("Hacker", "Alyssa", "P"), 81000) +supervisor(list("Hacker", "Alyssa", "P"), list("Bitdiddle", "Ben")) + +address(list("Fect", "Cy", "D"), + list("Cambridge", list("Ames", "Street"), 3)) +job(list("Fect", "Cy", "D"), list("computer", "programmer")) +salary(list("Fect", "Cy", "D"), 70000) +supervisor(list("Fect", "Cy", "D"), list("Bitdiddle", "Ben")) + +address(list("Tweakit", "Lem", "E"), + list("Boston", list("Bay", "State", "Road"), 22)) +job(list("Tweakit", "Lem", "E"), list("computer", "technician")) +salary(list("Tweakit", "Lem", "E"), 51000) +supervisor(list("Tweakit", "Lem", "E"), list("Bitdiddle", "Ben")) + + +process_query('assert(address(list("Hacker", "Alyssa", "P"), list("Cambridge", list("Mass", "Ave"), 78)))'); +process_query('assert(job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")))'); +process_query('assert(salary(list("Hacker", "Alyssa", "P"), 81000))'); +process_query('assert(supervisor(list("Hacker", "Alyssa", "P"), list("Bitdiddle", "Ben")))'); + +process_query('assert(address(list("Fect", "Cy", "D"), list("Cambridge", list("Ames", "Street"), 3)))'); +process_query('assert(job(list("Fect", "Cy", "D"), list("computer", "programmer")))'); +process_query('assert(salary(list("Fect", "Cy", "D"), 70000))'); +process_query('assert(supervisor(list("Fect", "Cy", "D"), list("Bitdiddle", "Ben")))'); + +process_query('assert(address(list("Tweakit", "Lem", "E"), list("Boston", list("Bay", "State", "Road"), 22)))'); +process_query('assert(job(list("Tweakit", "Lem", "E"), list("computer", "technician")))'); +process_query('assert(salary(list("Tweakit", "Lem", "E"), 51000))'); +process_query('assert(supervisor(list("Tweakit", "Lem", "E"), list("Bitdiddle", "Ben")))'); + + + + sample_data_base_2_example + +process_query('supervisor($x, list("Bitdiddle", "Ben"))'); + + +first_answer('supervisor($x, list("Bitdiddle", "Ben"))'); + + + \newpage\noindent There is also a programmer trainee, who is supervised by Alyssa: + + sample_data_base_3 + process_query + sample_data_base_2 + 'supervisor(list("Reasoner", "Louis"), list("Hacker", "Alyssa", "P"))' + sample_data_base_3_example + +(address (Reasoner Louis) (Slumerville (Pine Tree Road) 80)) +(job (Reasoner Louis) (computer programmer trainee)) +(salary (Reasoner Louis) 30000) +(supervisor (Reasoner Louis) (Hacker Alyssa P)) + + +address(list("Reasoner", "Louis"), + list("Slumerville", list("Pine", "Tree", "Road"), 80)) +job(list("Reasoner", "Louis"), + list("computer", "programmer", "trainee")) +salary(list("Reasoner", "Louis"), 62000) +supervisor(list("Reasoner", "Louis"), list("Hacker", "Alyssa", "P")) + + +process_query('assert(address(list("Reasoner", "Louis"), list("Slumerville", list("Pine", "Tree", "Road"), 80)))'); +process_query('assert(job(list("Reasoner", "Louis"), list("computer", "programmer", "trainee")))'); +process_query('assert(salary(list("Reasoner", "Louis"), 62000))'); +process_query('assert(supervisor(list("Reasoner", "Louis"), list("Hacker", "Alyssa", "P")))'); + + + + sample_data_base_3_example + +process_query('supervisor(list("Reasoner", $x), $y)'); + + +first_answer('supervisor(list("Reasoner", $x), $y)'); + + + All these people are in the computer division, as indicated by the + word + + + computer + + + "computer" + + + as the first item in their job + descriptions. + + + Ben is a high-level employee. His supervisor is the companys big + wheel himself: + + sample_data_base_4 + process_query + sample_data_base_3 + 'address(list("Warbucks", "Oliver"), list("Swellesley", list("Top", "Heap", "Road")))' + sample_data_base_4_example + +(supervisor (Bitdiddle Ben) (Warbucks Oliver)) + +(address (Warbucks Oliver) (Swellesley (Top Heap Road))) +(job (Warbucks Oliver) (administration big wheel)) +(salary (Warbucks Oliver) 150000) + + +supervisor(list("Bitdiddle", "Ben"), list("Warbucks", "Oliver")) + +address(list("Warbucks", "Oliver"), + list("Swellesley", list("Top", "Heap", "Road"))) +job(list("Warbucks", "Oliver"), list("administration", "big", "wheel")) +salary(list("Warbucks", "Oliver"), 314159) + + +process_query('assert(supervisor(list("Bitdiddle", "Ben"), list("Warbucks", "Oliver")))'); + +process_query('assert(address(list("Warbucks", "Oliver"), list("Swellesley", list("Top", "Heap", "Road"))))'); +process_query('assert(job(list("Warbucks", "Oliver"), list("administration", "big", "wheel")))'); +process_query('assert(salary(list("Warbucks", "Oliver"), 314159))'); + + + + sample_data_base_4_example + +process_query('address($x, list("Swellesley", $y))'); + + +first_answer('address($x, list("Swellesley", $y))'); + + + + + + Besides the computer division supervised by Ben, the company has an + accounting division, consisting of a chief accountant and his + assistant: + + sample_data_base_5 + process_query + sample_data_base_4 + 'job(list("Cratchit", "Robert"), list("accounting", "scrivener"))' + sample_data_base_5_example + +(address (Scrooge Eben) (Weston (Shady Lane) 10)) +(job (Scrooge Eben) (accounting chief accountant)) +(salary (Scrooge Eben) 75000) +(supervisor (Scrooge Eben) (Warbucks Oliver)) + +(address (Cratchet Robert) (Allston (N Harvard Street) 16)) +(job (Cratchet Robert) (accounting scrivener)) +(salary (Cratchet Robert) 18000) +(supervisor (Cratchet Robert) (Scrooge Eben)) + + +address(list("Scrooge", "Eben"), + list("Weston", list("Shady", "Lane"), 10)) +job(list("Scrooge", "Eben"), list("accounting", "chief", "accountant")) +salary(list("Scrooge", "Eben"), 141421) +supervisor(list("Scrooge", "Eben"), list("Warbucks", "Oliver")) + +address(list("Cratchit", "Robert"), + list("Allston", list("N", "Harvard", "Street"), 16)) +job(list("Cratchit", "Robert"), list("accounting", "scrivener")) +salary(list("Cratchit", "Robert"), 26100) +supervisor(list("Cratchit", "Robert"), list("Scrooge", "Eben")) + + +process_query('assert(address(list("Scrooge", "Eben"), list("Weston", list("Shady", "Lane"), 10)))'); +process_query('assert(job(list("Scrooge", "Eben"), list("accounting", "chief", "accountant")))'); +process_query('assert(salary(list("Scrooge", "Eben"), 141421))'); +process_query('assert(supervisor(list("Scrooge", "Eben"), list("Warbucks", "Oliver")))'); + +process_query('assert(address(list("Cratchit", "Robert"), list("Allston", list("N", "Harvard", "Street"), 16)))'); +process_query('assert(job(list("Cratchit", "Robert"), list("accounting", "scrivener")))'); +process_query('assert(salary(list("Cratchit", "Robert"), 26100))'); +process_query('assert(supervisor(list("Cratchit", "Robert"), list("Scrooge", "Eben")))'); + + + + sample_data_base_5_example + +process_query('job($x, pair("accounting", $y))'); + + +first_answer('job($x, pair("accounting", $y))'); + + + + There is also + + + a secretary + + + an administrative assistant + + + for the big wheel: + + sample_data_base_6 + process_query + sample_data_base_5 + 'address(list("Aull", "DeWitt"), list("Slumerville", list("Onion", "Square"), 5))' + sample_data_base_6_example + +(address (Aull DeWitt) (Slumerville (Onion Square) 5)) +(job (Aull DeWitt) (administration secretary)) +(salary (Aull DeWitt) 25000) +(supervisor (Aull DeWitt) (Warbucks Oliver)) + + +address(list("Aull", "DeWitt"), + list("Slumerville", list("Onion", "Square"), 5)) +job(list("Aull", "DeWitt"), list("administration", "assistant")) +salary(list("Aull", "DeWitt"), 42195) +supervisor(list("Aull", "DeWitt"), list("Warbucks", "Oliver")) + + +process_query('assert(address(list("Aull", "DeWitt"), list("Slumerville", list("Onion", "Square"), 5)))'); +process_query('assert(job(list("Aull", "DeWitt"), list("administration", "assistant")))'); +process_query('assert(salary(list("Aull", "DeWitt"), 42195))'); +process_query('assert(supervisor(list("Aull", "DeWitt"), list("Warbucks", "Oliver")))'); + + + + sample_data_base_6_example + +process_query('address($x, list(y, list("Onion", "Square"), $n))'); + + +first_answer('address($x, list(y, list("Onion", "Square"), $n))'); + + + + + + The data base also contains assertions about which kinds of jobs can + be done by people holding other kinds of jobs. For instance, a + computer wizard can do the jobs of both a computer programmer and a + computer technician: + + sample_data_base_7 + process_query + sample_data_base_6 + 'can_do_job(list("computer", "wizard"), list("computer", "technician"))' + sample_data_base_7_example + +(can-do-job (computer wizard) (computer programmer)) +(can-do-job (computer wizard) (computer technician)) + + +can_do_job(list("computer", "wizard"), + list("computer", "programmer")) +can_do_job(list("computer", "wizard"), + list("computer", "technician")) + + +process_query('assert(can_do_job(list("computer", "wizard"), list("computer", "programmer")))'); +process_query('assert(can_do_job(list("computer", "wizard"), list("computer", "technician")))'); + + + + sample_data_base_7_example + +process_query('can_do_job($x, list("computer", $y))'); + + +first_answer('can_do_job($x, list("computer", $y))'); + + + A computer programmer could fill in for a trainee: + + sample_data_base_8 + process_query + sample_data_base_7 + 'can_do_job(list("computer", "programmer"), list("computer", "programmer", "trainee"))' + sample_data_base_8_example + +(can-do-job (computer programmer) + (computer programmer trainee)) + + +can_do_job(list("computer", "programmer"), + list("computer", "programmer", "trainee")) + + +process_query('assert(can_do_job(list("computer", "programmer"), list("computer", "programmer", "trainee")))'); + + + + sample_data_base_8_example + +process_query('can_do_job($x, pair("computer", $y))'); + + +first_answer('can_do_job($x, pair("computer", $y))'); + + + + + secretary, importance of + + + administrative assistant, importance of + + + Also, as is well known, + + sample_data_base_9 + process_query + sample_data_base_8 + 'can_do_job(list("administration", "assistant"), list("administration", "big", "wheel"))' + sample_data_base_9_example + +(can-do-job (administration secretary) + (administration big wheel)) + + +can_do_job(list("administration", "assistant"), + list("administration", "big", "wheel")) + + +process_query('assert(can_do_job(list("administration", "assistant"), list("administration", "big", "wheel")))'); + + + + + sample_data_base_9_example + +process_query('can_do_job(list($x, "assistant"), $y)'); + + +first_answer('can_do_job(list($x, "assistant"), $y)'); + + + + + data baseGargle personnel + query languagedata base + + + Simple queries + + + simple query + + + The query language allows users to retrieve information from the data + base by posing queries in response to the systems prompt. + For example, to find all computer programmers one can say + + simple_queries_input + +;;; Query input: + + + + simple_queries_1 + process_query + sample_data_base_9 + query_driver_loop + 'job(list("Fect", "Cy", "D"), list("computer", "programmer"))' + simple_queries_1_example + + (job ?x (computer programmer)) + + +Query input: + + +job($x, list("computer", "programmer")) + + + + + + simple_queries_1_example + +first_answer('job($x, list("computer", "programmer"))'); + + +process_query('job($x, list("computer", "programmer"))'); + +// Query input: +// job($x, list("computer", "programmer")) +// Query results: +// job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")) +// job(list("Fect", "Cy", "D"), list("computer", "programmer")) + + + The system will respond with the following items: + + +;;; Query results: +(job (Hacker Alyssa P) (computer programmer)) +(job (Fect Cy D) (computer programmer)) + + +Query results: +job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")) +job(list("Fect", "Cy", "D"), list("computer", "programmer")) + + + + + + The input query specifies that we are looking for entries in the data + base that match a certain + pattern + pattern. + + + In this example, the pattern + specifies entries consisting of three items, of which the first is the + literal symbol job, the second can be + anything, and the third is the literal list + (computer programmer). + The anything that can be the second item in the matching + list is specified by a + pattern variable + pattern variable, + ?x. + The general form of a pattern variable is a symbol, taken to be the name + of the variable, preceded by a question mark. We will see below why it + is useful to specify names for pattern variables rather than just putting + ? + into patterns to represent anything. + + + In this example, the pattern + specifies job as the kind of information + that we are looking for. The first item can be + anything, and the second is the literal list + list("computer", "programmer"). + The anything that can be the first item in the matching + assertion is specified by a + pattern variable + pattern variable, + $x. As pattern variables, we use + naming conventions;x$\$$ for pattern variables + 0a4$\$$, pattern variables starting with + JavaScript names that start with a dollar sign. + We 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. + + + The system responds to a simple query by showing all entries in the data + base that match the specified pattern. + + + + A pattern can have more than one variable. For example, the query + + simple_queries_2 + process_query + sample_data_base_9 + query_driver_loop + 'address(list("Aull", "DeWitt"), list("Slumerville", list("Onion", "Square"), 5))' + simple_queries_2_example + +(address ?x ?y) + + +address($x, $y) + + + + + + simple_queries_2_example + +first_answer('address($x, $y)'); + + +process_query('address($x, $y)'); + +// query input: +// address($x, $y) +// query results: +// all employees' addresses + + + will list all the employees addresses. + + + + A pattern can have no variables, in which case the query simply + determines whether that pattern is an entry in the data base. If so, + there will be one match; if not, there will be no matches. + + + + The same pattern variable can appear more than once in a query, + specifying that the same anything must appear in each + position. This is why variables have names. For example, + + simple_queries_3 + process_query + sample_data_base_9 + query_driver_loop + 'supervisor(list("Julius", "Caesar"), list("Julius", "Caesar"))' + simple_queries_3_example + +(supervisor ?x ?x) + + +supervisor($x, $x) + + + + + + simple_queries_3_example + +process_query('assert(supervisor(list("Julius", "Caesar"), list("Julius", "Caesar")))'); +first_answer('supervisor($x, $x)'); + + +process_query('supervisor($x, $x)'); + +// query input: +// supervisor($x, $x) +// query results: +// none + + + finds all people who supervise themselves (though there are no + such assertions in our sample data base). + + + + The query + + simple_queries_4 + process_query + sample_data_base_9 + query_driver_loop + 'job(list("Tweakit", "Lem", "E"), list("computer", "technician"))' + simple_queries_4_example + +(job ?x (computer ?type)) + + +job($x, list("computer", $type)) + + + + + + simple_queries_4_example + +first_answer('job($x, list("computer", $type))'); + + +process_query('job($x, list("computer", $type))'); + +// query input: +// job($x, list("computer", $type)) +// query results: +// job(list("Bitdiddle", "Ben"), list("computer", "wizard")) +// job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")) +// job(list("Fect", "Cy", "D"), list("computer", "programmer")) +// job(list("Tweakit", "Lem", "E"), list("computer", "technician")) + + + matches all job entries whose + + + third + + + second + + + item is a two-element list whose + first item is + + + computer: + + + "computer": + + + + +(job (Bitdiddle Ben) (computer wizard)) +(job (Hacker Alyssa P) (computer programmer)) +(job (Fect Cy D) (computer programmer)) +(job (Tweakit Lem E) (computer technician)) + + +job(list("Bitdiddle", "Ben"), list("computer", "wizard")) +job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")) +job(list("Fect", "Cy", "D"), list("computer", "programmer")) +job(list("Tweakit", "Lem", "E"), list("computer", "technician")) + + + + + This same pattern does not match + + +(job (Reasoner Louis) (computer programmer trainee)) + + + because the third item in the entry is a list of three elements, and + the patterns third item specifies that there should be two + elements. If we wanted to change the pattern so that the third item + could be any + dotted-tail notationqueryin query pattern + list beginning with + computer, + we could + specifyThis uses the dotted-tail + notation introduced in + exercise. + + + + This 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. If we wanted to change the pattern so that the + second item could be any + list beginning with + "computer", + we could + specify + + There is no need for dotted-tail notation in the + JavaScript edition: We simply use + pair instead of + list where needed. + + + + + simple_queries_5 + process_query + sample_data_base_9 + query_driver_loop + 'job(list("Reasoner", "Louis"), list("computer", "programmer", "trainee"))' + simple_queries_5_example + +(job ?x (computer . ?type)) + + +job($x, pair("computer", $type)) + + + + + + simple_queries_5_example + +first_answer('job($x, pair("computer", $type))'); + + +process_query('job($x, pair("computer", $type))'); + +// query input: +// job($x, pair("computer", $type)) +// query results: +// job(list("Bitdiddle", "Ben"), list("computer", "wizard")) +// job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")) +// job(list("Fect", "Cy", "D"), list("computer", "programmer")) +// job(list("Tweakit", "Lem", "E"), list("computer", "technician")) +// job(list("Reasoner", "Louis"), list("computer", "programmer", "trainee")) + + + For example, + + +(computer . ?type) + + +pair("computer", $type) + + + matches the data + + +(computer programmer trainee) + + +list("computer", "programmer", "trainee") + + + with + + + ?type + + + $type + + + as + + the list (programmer trainee). + + list("programmer", "trainee"). + + + It also matches the data + + +(computer programmer) + + +list("computer", "programmer") + + + with + + + ?type + + $type + + + as + + the list (programmer), + list("programmer"), + + + and matches the data + + +(computer) + + +list("computer") + + + with + + + ?type + + $type + + + as the empty + + list (). + list, null. + + + + pattern + + + We can describe the query languages processing of simple queries as + follows: +
    +
  • + The system finds all assignments to variables in the query + pattern that + satisfy a pattern (simple query) + satisfy the patternthat is, all sets + of values for the variables such that if the pattern variables are + instantiate a pattern + instantiated with (replaced by) the values, the result is + in the data base. +
  • +
  • + The system responds to the query by listing all instantiations of the + query pattern with the variable assignments that satisfy it. +
  • +
+ Note that if the pattern has no variables, the query reduces to a + determination of whether that pattern is in the data base. If so, the + empty assignment, which assigns no values to variables, satisfies that + pattern for that data base. +
+ + + Give simple queries that retrieve the following information from the + data base: +
    +
  1. + all people supervised by Ben Bitdiddle; +
  2. +
  3. + the names and jobs of all people in the accounting division; +
  4. +
  5. + the names and addresses of all people who live + in Slumerville. +
  6. +
+ +
+ + simple query + + + Compound queries + + + compound query + + + Simple queries form the primitive operations of the query language. + In order to form compound operations, the query language provides + means of combination. One thing that makes the query language a logic + programming language is that the means of combination mirror the means + of combination used in forming logical expressions: + and, or, and + not. + + + (Here and, or, + and not are not the Lisp primitives, but + rather operations built into the query language.) + + + + + + We can use + and (query language) + and as follows to find the addresses + of all the computer programmers: + + compound_queries_1 + query_driver_loop + process_query + sample_data_base_9 + compound_queries_1_example + 'and(job(list("Fect", "Cy", "D"), list("computer", "programmer")), address(list("Fect", "Cy", "D"), list("Cambridge", list("Ames", "Street"), 3)))' + +(and (job ?person (computer programmer)) + (address ?person ?where)) + + +and(job($person, list("computer", "programmer")), + address($person, $where)) + + + + + + compound_queries_1_example + +first_answer('and(job($person, list("computer", "programmer")), address($person, $where))'); + + +process_query('and(job($person, list("computer", "programmer")), address($person, $where))'); + +// query input: +// and(job($person, list("computer", "programmer")), address($person, $where)) +// query results: +// and(job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")), +// address(list("Hacker", "Alyssa", "P"), list("Cambridge", list("Mass", "Ave"), 78))) +// and(job(list("Fect", "Cy", "D"), list("computer", "programmer")), +// address(list("Fect", "Cy", "D"), list("Cambridge", list("Ames", "Street"), 3))) + + + The resulting output is + + +(and (job (Hacker Alyssa P) (computer programmer)) + (address (Hacker Alyssa P) (Cambridge (Mass Ave) 78))) + +(and (job (Fect Cy D) (computer programmer)) + (address (Fect Cy D) (Cambridge (Ames Street) 3))) + + +and(job(list("Hacker", "Alyssa", "P"), list("computer", "programmer")), + address(list("Hacker", "Alyssa", "P"), + list("Cambridge", list("Mass", "Ave"), 78))) + +and(job(list("Fect", "Cy", "D"), list("computer", "programmer")), + address(list("Fect", "Cy", "D"), + list("Cambridge", list("Ames", "Street"), 3))) + + + + + In general, + + +(and $\langle \textit{query}_{1}\rangle$ $\langle \textit{query}_{2} \rangle$ $\ldots$ $\langle \textit{query}_{n} \rangle$) + + + is + satisfy a compound query + satisfied by all sets of values for the pattern variables that + simultaneously satisfy + $\langle\textit{query}_{1}\rangle\ldots \langle\textit{query}_{n}\rangle$. + + + In general, + + +and(query$_{1}$, query$_{2}$, $\ldots$, query$_{n})$ + + + is + satisfy a compound query + satisfied by all sets of values for the pattern variables that + simultaneously satisfy + query$_{1}, \ldots,$ query$_{n}$. + + + + + + As for simple queries, the system processes a compound query by + finding all assignments to the pattern variables that satisfy the + query, then displaying instantiations of the query with those values. + + + + + Another means of constructing compound queries is through + or (query language) + or. For example, + + compound_queries_2 + process_query + query_driver_loop + sample_data_base_9 + compound_queries_2_example + 'or(supervisor(list("Tweakit", "Lem", "E"), list("Bitdiddle", "Ben")), supervisor(list("Tweakit", "Lem", "E"), list("Hacker", "Alyssa", "P")))' + +(or (supervisor ?x (Bitdiddle Ben)) + (supervisor ?x (Hacker Alyssa P))) + + +or(supervisor($x, list("Bitdiddle", "Ben")), + supervisor($x, list("Hacker", "Alyssa", "P"))) + + + + + + compound_queries_2_example + +first_answer('or(supervisor($x, list("Bitdiddle", "Ben")), supervisor($x, list("Hacker", "Alyssa", "P")))'); + + +process_query('or(supervisor($x, list("Bitdiddle", "Ben")), supervisor($x, list("Hacker", "Alyssa", "P")))'); + +// query input: +// or(supervisor($x, list("Bitdiddle", "Ben")), supervisor($x, list("Hacker", "Alyssa", "P"))) +// query results: +// or(supervisor(list("Hacker", "Alyssa", "P"), list("Bitdiddle", "Ben")), +// supervisor(list("Hacker", "Alyssa", "P"), list("Hacker", "Alyssa", "P"))) +// or(supervisor(list("Fect", "Cy", "D"), list("Bitdiddle", "Ben")), +// supervisor(list("Fect", "Cy", "D"), list("Hacker", "Alyssa", "P"))) +// or(supervisor(list("Tweakit", "Lem", "E"), list("Bitdiddle", "Ben")), +// supervisor(list("Tweakit", "Lem", "E"), list("Hacker", "Alyssa", "P"))) +// or(supervisor(list("Reasoner", "Louis"), list("Bitdiddle", "Ben")), +// supervisor(list("Reasoner", "Louis"), list("Hacker", "Alyssa", "P"))) + + + will find all employees supervised by Ben Bitdiddle or Alyssa P. + Hacker: + + +(or (supervisor (Hacker Alyssa P) (Bitdiddle Ben)) + (supervisor (Hacker Alyssa P) (Hacker Alyssa P))) + +(or (supervisor (Fect Cy D) (Bitdiddle Ben)) + (supervisor (Fect Cy D) (Hacker Alyssa P))) + +(or (supervisor (Tweakit Lem E) (Bitdiddle Ben)) + (supervisor (Tweakit Lem E) (Hacker Alyssa P))) + +(or (supervisor (Reasoner Louis) (Bitdiddle Ben)) + (supervisor (Reasoner Louis) (Hacker Alyssa P))) + + +or(supervisor(list("Hacker", "Alyssa", "P"), + list("Bitdiddle", "Ben")), + supervisor(list("Hacker", "Alyssa", "P"), + list("Hacker", "Alyssa", "P"))) + +or(supervisor(list("Fect", "Cy", "D"), + list("Bitdiddle", "Ben")), + supervisor(list("Fect", "Cy", "D"), + list("Hacker", "Alyssa", "P"))) + +or(supervisor(list("Tweakit", "Lem", "E"), + list("Bitdiddle", "Ben")), + supervisor(list("Tweakit", "Lem", "E"), + list("Hacker", "Alyssa", "P"))) + +or(supervisor(list("Reasoner", "Louis"), + list("Bitdiddle", "Ben")), + supervisor(list("Reasoner", "Louis"), + list("Hacker", "Alyssa", "P"))) + + + + + In general, + + +(or $\langle \textit{query}_{1}\rangle$ $\langle \textit{query}_{2}\rangle$ $\ldots$ $\langle \textit{query}_{n}\rangle$) + + + is satisfied by all sets of values for the pattern variables that + satisfy at least one of + $\langle \textit{query}_{1}\rangle \ldots \langle \textit{query}_{n}\rangle$. + + + In 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$ query$_{n}$. + + + + + + Compound queries can also be formed with + not (query language) + not. + For example, + + compound_queries_3 + process_query + sample_data_base_9 + query_driver_loop + 'and(supervisor(list("Tweakit", "Lem", "E"), list("Bitdiddle", "Ben")), not(job(list("Tweakit", "Lem", "E"), list("computer", "programmer"))))' + compound_queries_3_example + +(and (supervisor ?x (Bitdiddle Ben)) + (not (job ?x (computer programmer)))) + + +and(supervisor($x, list("Bitdiddle", "Ben")), + not(job($x, list("computer", "programmer")))) + + + + + + compound_queries_3_example + +first_answer('and(supervisor($x, list("Bitdiddle", "Ben")), not(job($x, list("computer", "programmer"))))'); + + +process_query('and(supervisor($x, list("Bitdiddle", "Ben")), not(job($x, list("computer", "programmer"))))'); + +// query input: +// and(supervisor($x, list("Bitdiddle", "Ben")), not(job($x, list("computer", "programmer")))) +// query results: +// all people supervised by Ben Bitdiddle who are not computer programmers + + + finds all people supervised by Ben Bitdiddle who are not computer + programmers. In general, + + + + +(not $\langle \textit{query}_{1}\rangle$) + + + + + + +not(query$_{1}$) + + + + + is satisfied by all assignments to the pattern variables that do not + satisfy + $\langle \textit{query}_{1} \rangle$query$_{1}$.Actually, this description of + not is valid only for simple cases. The real + behavior of not is more complex. We will + examine nots peculiarities + in sections + and. + + + + + + The final combining form is called + lisp-value (query language) + lisp-value. When + lisp-value is the first element of a + pattern, it specifies that the next element is a Lisp predicate to be + applied to the rest of the (instantiated) elements as arguments. + In general, + + +(lisp-value $\langle \textit{predicate}\rangle$ $\langle \textit{arg}_{1}\rangle$ $\ldots$ $\langle \textit{arg}_{n} \rangle$) + + + will be satisfied by assignments to the pattern variables for which the + $\langle \textit{predicate} \rangle$ applied + to the instantiated + $\langle \textit{arg}_{1} \rangle, \ldots, \langle \textit{arg}_{n}\rangle$ + is true. For example, to find all people whose salary is greater than + 30,000 we could writeLisp-value + should be used only to perform an operation not + provided in the query language. In particular, it should not + be used to + query languageequality testing in + test equality (since that is what the matching in the + query language is designed to do) or inequality (since that can + be done with the same rule shown + below). + + +(and (salary ?person ?amount) + (lisp-value > ?amount 30000)) + + + satisfy a compound query + + + + + The final combining form starts with + javascript_predicate (query language) + javascript_predicate and + contains a JavaScript predicate. In general, + + +javascript_predicate(predicate) + + + will be satisfied by assignments to the pattern variables + in the predicate for which the + instantiated + predicate is true. + For example, to find all people whose salary is greater than + 50,000 we could write + A query should use + javascript_predicate + only to perform an operation not + provided in the query language. In particular, + javascript_predicate + should not be used to + query languageequality testing in + test equality (since that is what the matching in the + query language is designed to do) or inequality (since that can + be done with the same rule shown + below). + + compound_queries_4 + process_query + sample_data_base_9 + query_driver_loop + compound_queries_4_example + 'and(salary(list("Scrooge", "Eben"), 141421), javascript_predicate((141421 > 50000)))' + +and(salary($person, $amount), javascript_predicate($amount > 50000)) + + + + + + compound_queries_4_example + +first_answer('and(salary($person, $amount), javascript_predicate($amount > 50000))'); + + +process_query('and(salary($person, $amount), javascript_predicate($amount > 50000))'); + +// query input: +// and(salary($person, $amount), javascript_predicate($amount > 50000)) +// query results: +// people whose salary is greater than 50000 + + + + satisfy a compound query + + + + Formulate compound queries that retrieve the following information: +
    +
  1. + the names of all people who are supervised by Ben Bitdiddle, together + with their addresses; +
  2. +
  3. + all people whose salary is less than Ben Bitdiddles, together + with their salary and Ben Bitdiddles salary; +
  4. +
  5. + all people who are supervised by someone who is not in the computer + division, together with the supervisors name and job. +
  6. +
+ +
+ + compound query + + + + Rules + + + rule (query language) + + + In addition to primitive queries and compound queries, the query + language provides means for + query languageabstraction in + abstracting queries. These are given by + rules. The rule + + lives_near (rule) + rules_1 + process_query + sample_data_base_9 + rule_same + 'lives_near(list("Reasoner", "Louis"), list("Aull", "DeWitt"))' + rules_1_example + +(rule (lives-near ?person-1 ?person-2) + (and (address ?person-1 (?town . ?rest-1)) + (address ?person-2 (?town . ?rest-2)) + (not (same ?person-1 ?person-2)))) + + +rule(lives_near($person_1, $person_2), + and(address($person_1, pair($town, $rest_1)), + address($person_2, pair($town, $rest_2)), + not(same($person_1, $person_2)))) + + +process_query(`assert( +rule(lives_near($person_1, $person_2), + and(address($person_1, pair($town, $rest_1)), + address($person_2, pair($town, $rest_2)), + not(same($person_1, $person_2)))))`); + + + + rules_1_example + +first_answer('lives_near(list("Reasoner", "Louis"), $who)'); + + +process_query('lives_near(list("Reasoner", "Louis"), $who)'); + + + specifies that two people live near each other if they live in the + same town. The final not clause prevents the + rule from saying that all people live near themselves. The + same relation is defined by a very simple + rule:Notice that we do not need same + in order to make two things be the same: We just use the same pattern + variable for eachin effect, we have one thing instead of two things + in the first place. For example, see + + + ?town + + + $town + + + in the + + + lives-near + + + lives_near + + + rule and + + + ?middle-manager + + + $middle_manager + + + in the + wheel + rule below. + + + Same + + + The same relation + + + is useful when we want to force two things to be + different, such as + + + ?person-1 + + + $person_1 + + + and + + + ?person-2 + + + $person_2 + + + in the + + + lives-near + + + lives_near + + + rule. Although using the same pattern variable in two + parts of a query forces the same value to appear in both places, using + different pattern variables does not force different values to appear. + (The values assigned to different pattern variables may be the same or + different.) + + same (rule) + rule_same + process_query + sample_data_base_9 + 'same(list("Reasoner", "Louis"), list("Reasoner", "Louis"))' + rule_same_example + +(rule (same ?x ?x)) + + +rule(same($x, $x)) + + +process_query('assert(rule(same($x, $x)))'); + + + + rule_same_example + rule_same + process_query + +first_answer('same(list("Reasoner", $first_name), list($surname, "Louis"))'); + + +process_query('same(list("Reasoner", $first_name), list($surname, "Louis"))'); + + + + + + The following rule declares that a person is a wheel in an + organization if he supervises someone who is in turn a supervisor: + + wheel (rule) + rules_2 + process_query + sample_data_base_9 + rule_same + 'wheel(list("Warbucks", "Oliver"))' + rules_2_example + +(rule (wheel ?person) + (and (supervisor ?middle-manager ?person) + (supervisor ?x ?middle-manager))) + + +rule(wheel($person), + and(supervisor($middle_manager, $person), + supervisor($x, $middle_manager))) + + +process_query(`assert( +rule(wheel($person), + and(supervisor($middle_manager, $person), + supervisor($x, $middle_manager))))`); + + + + rules_2_example + +first_answer('wheel($who)'); + + +process_query('wheel($who)'); + + + + + + + + The general form of a rule is + + +(rule $\langle \textit{conclusion} \rangle$ $\langle \textit{body} \rangle$) + + + where $\langle \textit{conclusion}\rangle$ is + a pattern and $\langle \textit{body} \rangle$ + is any query.We will also allow + rule (query language)without body + rules without bodies, as in + same, and we will interpret such a rule to + mean that the rule conclusion is satisfied by any values of the + variables. + + + The general form of a rule is + + +rule(conclusion, body) + + + where conclusion is a pattern and + body is any query.We will + also allow + rule (query language)without body + rules without bodies, as in + same, and we will interpret such a rule to + mean that the rule conclusion is satisfied by any values of the + variables. + + + We can think of a rule as representing a large (even + infinite) set of assertions, namely all instantiations of the rule conclusion + with variable assignments that satisfy the rule body. When we described + simple queries (patterns), we said that an assignment to variables satisfies + a pattern if the instantiated pattern is in the data base. But the pattern + neednt be explicitly in the data base as an assertion. It + can be an + assertionimplicit + implicit assertion implied by a rule. For example, the + query + + rules_3 + query_driver_loop + sample_data_base_9 + rules_1 + process_query + 'lives_near(list("Aull", "DeWitt"), list("Bitdiddle", "Ben"))' + +first_answer('lives_near($x, list("Bitdiddle", "Ben"))'); + + + (lives-near ?x (Bitdiddle Ben)) + + +lives_near($x, list("Bitdiddle", "Ben")) + + +process_query('lives_near($x, list("Bitdiddle", "Ben"))'); + +// query input: +// lives_near($x, list("Bitdiddle", "Ben")) +// query results: +// lives_near(list("Reasoner", "Louis"), list("Bitdiddle", "Ben")) +// lives_near(list("Aull", "DeWitt"), list("Bitdiddle", "Ben")) + + + results in + + +(lives-near (Reasoner Louis) (Bitdiddle Ben)) +(lives-near (Aull DeWitt) (Bitdiddle Ben)) + + +lives_near(list("Reasoner", "Louis"), list("Bitdiddle", "Ben")) +lives_near(list("Aull", "DeWitt"), list("Bitdiddle", "Ben")) + + + To find all computer programmers who live near Ben Bitdiddle, we can + ask + + rules_4 + query_driver_loop + sample_data_base_9 + rules_1 + process_query + 'and(job(list("Reasoner", "Louis"), list("computer", "programmer", "trainee")), lives_near(list("Reasoner", "Louis"), list("Bitdiddle", "Ben")))' + +first_answer('and(job($x, pair("computer", $something)), lives_near($x, list("Bitdiddle", "Ben")))'); + + +(and (job ?x (computer programmer)) + (lives-near ?x (Bitdiddle Ben))) + + +and(job($x, list("computer", "programmer")), + lives_near($x, list("Bitdiddle", "Ben"))) + + +process_query('and(job($x, list("computer", "programmer")), lives_near($x, list("Bitdiddle", "Ben")))'); + +// query input: +// and(job($x, list("computer", "programmer")), lives_near($x, list("Bitdiddle", "Ben"))) +// query results: + + + + + + As in the case of compound + + procedures, + functions, + + rules can be used as parts of other rules (as we saw with the + + lives-near + lives_near + + + rule above) or even be defined + recursionrulesin rules + recursively. For instance, the rule + + outranked_by (rule) + rules_5 + query_driver_loop + sample_data_base_9 + rules_5_example + +(rule (outranked-by ?staff-person ?boss) + (or (supervisor ?staff-person ?boss) + (and (supervisor ?staff-person ?middle-manager) + (outranked-by ?middle-manager ?boss)))) + + +rule(outranked_by($staff_person, $boss), + or(supervisor($staff_person, $boss), + and(supervisor($staff_person, $middle_manager), + outranked_by($middle_manager, $boss)))) + + +process_query(`assert( +rule(outranked_by($staff_person, $boss), + or(supervisor($staff_person, $boss), + and(supervisor($staff_person, $middle_manager), + outranked_by($middle_manager, $boss)))))`); + + + + rules_5_example + rules_5 + process_query + 'outranked_by(list("Bitdiddle", "Ben"), list("Warbucks", "Oliver"))' + +process_query(`assert( +rule(outranked_by($staff_person, $boss), + or(supervisor($staff_person, $boss), + and(supervisor($staff_person, $middle_manager), + outranked_by($middle_manager, $boss)))))`); +first_answer('outranked_by(list($who, "Ben"), $the_boss)'); + + +process_query('outranked_by(list($who, "Ben"), $the_boss)'); + +// query input: +// outranked_by(list($who, "Ben"), $the_boss) +// query results: +// outranked_by(list("Bitdiddle", "Ben"), list("Warbucks", "Oliver")) + + + says that a staff person is outranked by a boss in the organization if + the boss is the persons supervisor or (recursively) if the + + persons supervisor is outranked by the boss. + + + + Define a rule that says that person 1 can replace person 2 if either + person 1 does the same job as person 2 or someone who does person 1s + job can also do person2s job, and if person 1 and person 2 + are not the same person. Using your rule, give queries that find the + following: +
    +
  1. + all people who can replace Cy D. Fect; +
  2. +
  3. + all people who can replace someone who is being paid more than they + are, together with the two salaries. +
  4. +
+ +
+ + + Define a rule that says that a person is a big shot in a + division if the person works in the division but does not have a supervisor + who works in the division. + + + + + + + Ben Bitdiddle has missed one meeting too many. Fearing that his habit of + forgetting meetings could cost him his job, Ben decides to do something about + it. He adds all the weekly meetings of the firm to the Gargle data base + by asserting the following: + + +(meeting accounting (Monday 9am)) +(meeting administration (Monday 10am)) +(meeting computer (Wednesday 3pm)) +(meeting administration (Friday 1pm)) + + +meeting("accounting", list("Monday", "9am")) +meeting("administration", list("Monday", "10am")) +meeting("computer", list("Wednesday", "3pm")) +meeting("administration", list("Friday", "1pm")) + + + Each of the above assertions is for a meeting of an entire division. + Ben also adds an entry for the company-wide meeting that spans all the + divisions. All of the companys employees attend this meeting. + + +(meeting whole-company (Wednesday 4pm)) + + +meeting("whole-company", list("Wednesday", "4pm")) + + +
    +
  1. + On Friday morning, Ben wants to query the data base for all the meetings + that occur that day. What query should he use? +
  2. +
  3. + Alyssa P. Hacker is unimpressed. She thinks it would be much more + useful to be able to ask for her meetings by specifying her name. So + she designs a rule that says that a persons meetings include all + + + whole-company + + + "whole-company" + + + meetings plus all meetings of that persons division. + Fill in the body of Alyssas rule. + + + + +(rule (meeting-time ?person ?day-and-time) + rule-body) + + + + + + +rule(meeting_time($\texttt{\$}$person, $\texttt{\$}$day_and_time), + $rule$-$body$) + + + + +
  4. +
  5. + Alyssa arrives at work on Wednesday morning and wonders what meetings she + has to attend that day. Having defined the above rule, what query should + she make to find this out? +
  6. +
+ +
+ + + By giving the query + + lives_near (rule) + +(lives-near ?person (Hacker Alyssa P)) + + +lives_near($person, list("Hacker", "Alyssa", "P")) + + + Alyssa P. Hacker is able to find people who live near her, with whom + she can ride to work. On the other hand, when she tries to find all + pairs of people who live near each other by querying + + +(lives-near ?person-1 ?person-2) + + +lives_near($person_1, $person_2) + + + she notices that each pair of people who live near each other is + listed twice; for example, + + +(lives-near (Hacker Alyssa P) (Fect Cy D)) +(lives-near (Fect Cy D) (Hacker Alyssa P)) + + +lives_near(list("Hacker", "Alyssa", "P"), list("Fect", "Cy", "D")) +lives_near(list("Fect", "Cy", "D"), list("Hacker", "Alyssa", "P")) + + + Why does this happen? + Is there a way to find a list of people who live near each other, in + which each pair appears only once? Explain. + + + + + Logic as programs + + + query languagelogical deductions + + + We can regard a rule as a kind of logical implication: If an + assignment of values to pattern variables satisfies the body, + then it satisfies the conclusion. Consequently, we can regard the + query language as having the ability to perform logical + deductions based upon the rules. As an example, consider the + append operation described at the beginning of + section. As we said, + append can be characterized by the following + two rules: +
    +
  • + 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 + + (consuz) + pair(u, z) + + + if v and y + append to form + z. +
  • +
+
+ + + To express this in our query language, we define two rules for a relation + + +(append-to-form x y z) + + +append_to_form(x, y, z) + + + which we can interpret to mean x and + y append to + form z: + + append_to_form (rules) + append_to_form + process_query + 'append_to_form(list(1, 2), list(3, 4), list(1, 2, 3, 4))' + append_to_form_example_1 + +(rule (append-to-form () ?y ?y)) + +(rule (append-to-form (?u . ?v) ?y (?u . ?z)) + (append-to-form ?v ?y ?z)) + + +rule(append_to_form(null, $y, $y)) + +rule(append_to_form(pair($u, $v), $y, pair($u, $z)), + append_to_form($v, $y, $z)) + + +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)))`); + + + + append_to_form_just_the_rules + +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)))`); + + + + append_to_form_example_1 + +first_answer('append_to_form(list(1, 2), list(3, 4), $xs)'); + + +process_query('append_to_form(list(1, 2), list(3, 4), $xs)'); + +// query input: +// append_to_form(list(1, 2), list(3, 4), $xs) +// query results: +// append_to_form(list(1, 2), list(3, 4), list(1, 2, 3, 4)) + + + The first rule has + rule (query language)without body + no body, which means that the conclusion holds for + any value of + + ?y. + $y. + + Note how the second rule makes use of + + + dotted-tail notationqueryin query-language rule + dotted-tail notation to name the + + + pair to name the + + + + car + head + + and + + cdr + tail + + of a list. + + + + Given these two rules, we can formulate queries that compute the + append of two lists: + + append_to_form_example_2 + process_query + append_to_form_just_the_rules + 'append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d"))' + +;;; Query input: + + +Query input: + + +first_answer('append_to_form(list("a", "b"), list("c", "d"), z)'); + + + (append-to-form (a b) (c d) ?z) + + +append_to_form(list("a", "b"), list("c", "d"), $z) + + +process_query('append_to_form(list("a", "b"), list("c", "d"), $z)'); + +// query input: +// append_to_form(list("a", "b"), list("c", "d"), $z) +// query results: +// append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d")) + + +;;; Query results: +(append-to-form (a b) (c d) (a b c d)) + + +Query results: +append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d")) + + + What is more striking, we can use the same rules to ask the question + + + + Which list, when appended to + (a b), yields + (a b c d)? + + + Which list, when appended to + list("a", "b"), yields + list("a", "b", "c", "d")? + + + + This is done as follows: + + append_to_form_example_3 + process_query + append_to_form_just_the_rules + 'append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d"))' + +first_answer('append_to_form(list("a", "b"), y, list("a", "b", "c", "d"))'); + + +;;; Query input: + + +Query input: + + + (append-to-form (a b) ?y (a b c d)) + + +append_to_form(list("a", "b"), $y, list("a", "b", "c", "d")) + + +process_query('append_to_form(list("a", "b"), $y, list("a", "b", "c", "d"))'); + +// query input: +// append_to_form(list("a", "b"), $y, list("a", "b", "c", "d")) +// query results: +// append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d")) + + + ;;; Query results: + (append-to-form (a b) (c d) (a b c d)) + + +Query results: +append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d")) + + + We can + + + also + + + ask for all pairs of lists that + append to form + + (a b c d): + list("a", "b", "c", "d"): + + + + append_to_form_example_4 + process_query + append_to_form_just_the_rules + 'append_to_form(list("a", "b", "c", "d"), null, list("a", "b", "c", "d"))' + +first_answer('append_to_form($x, $y, list("a", "b", "c", "d"))'); + + +;;; Query input: + + +Query input: + + + (append-to-form ?x ?y (a b c d)) + + +append_to_form($x, $y, list("a", "b", "c", "d")) + + +process_query('append_to_form($x, $y, list("a", "b", "c", "d"))'); + +// query input: +// append_to_form($x, $y, list("a", "b", "c", "d")) +// query results: +// append_to_form(null, list("a", "b", "c", "d"), list("a", "b", "c", "d")) +// append_to_form(list("a"), list("b", "c", "d"), list("a", "b", "c", "d")) +// append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d")) +// append_to_form(list("a", "b", "c"), list("d"), list("a", "b", "c", "d")) +// append_to_form(list("a", "b", "c", "d"), null, list("a", "b", "c", "d")) + + +;;; Query results: +(append-to-form () (a b c d) (a b c d)) +(append-to-form (a) (b c d) (a b c d)) +(append-to-form (a b) (c d) (a b c d)) +(append-to-form (a b c) (d) (a b c d)) +(append-to-form (a b c d) () (a b c d)) + + +Query results: +append_to_form(null, list("a", "b", "c", "d"), list("a", "b", "c", "d")) +append_to_form(list("a"), list("b", "c", "d"), list("a", "b", "c", "d")) +append_to_form(list("a", "b"), list("c", "d"), list("a", "b", "c", "d")) +append_to_form(list("a", "b", "c"), list("d"), list("a", "b", "c", "d")) +append_to_form(list("a", "b", "c", "d"), null, list("a", "b", "c", "d")) + + + + + + The query system may seem to exhibit quite a bit of intelligence in + using the rules to deduce the answers to the queries above. Actually, + as we will see in the next section, the system is following a + well-determined algorithm in unraveling the rules. Unfortunately, + although the system works impressively in the + append case, the general methods may break down + in more complex cases, as we will see + in section. + + + + + + The following rules implement a + + + next-to + + + next_to_in + + + relation that finds adjacent elements of a list: + + next_to_in (rules) + +(rule (?x next-to ?y in (?x ?y . ?u))) + +(rule (?x next-to ?y in (?v . ?z)) + (?x next-to ?y in ?z)) + + +rule(next_to_in($x, $y, pair($x, pair($y, $u)))) + +rule(next_to_in($x, $y, pair($v, $z)), + next_to_in($x, $y, $z)) + + + \newpage\noindent What will the response be to the following queries? + + +(?x next-to ?y in (1 (2 3) 4)) + +(?x next-to 1 in (2 1 3 1)) + + +next_to_in($x, $y, list(1, list(2, 3), 4)) + +next_to_in($x, 1, list(2, 1, 3, 1)) + + + + + + Define rules to implement the + last_pairrules + + + last-pair + + + last_pair + + + operation of exercise, + which returns a list + containing the last element of a nonempty list. Check your rules on + + + queries such as + (last-pair (3) ?x), + (last-pair (1 2 3) ?x), + and + (last-pair (2 ?x) (3)). + + + the following queries: +
    +
  • last_pair(list(3), $x)
  • +
  • last_pair(list(1, 2, 3), $x)
  • +
  • last_pair(list(2, $x), list(3))
  • +
+
+
+ Do your rules work correctly on queries such as + + (last-pair ?x (3))? + last_pair($x, list(3))? + + + +
+ + + The following data base (see Genesis 4) traces the genealogy of the + descendants of + AdaGenesis + Ada back to Adam, by way of Cain: + + + +(son Adam Cain) +(son Cain Enoch) +(son Enoch Irad) +(son Irad Mehujael) +(son Mehujael Methushael) +(son Methushael Lamech) +(wife Lamech Ada) +(son Ada Jabal) +(son Ada Jubal) + + +son("Adam", "Cain") +son("Cain", "Enoch") +son("Enoch", "Irad") +son("Irad", "Mehujael") +son("Mehujael", "Methushael") +son("Methushael", "Lamech") +wife("Lamech", "Ada") +son("Ada", "Jabal") +son("Ada", "Jubal") + + + Formulate rules such as If S is the son of F, and + F is the son of G, then S is the grandson of + G and If W is the wife of M, and + S is the son of W, then S is the son of + M (which was supposedly more true in biblical times than + today) that will enable the query system to find the grandson of Cain; the + sons of Lamech; the grandsons of Methushael. + (See + + + exercise + + + exercise + + + for some rules to deduce more complicated relationships.) + + + + rule (query language) + query languagelogical deductions + query language + +
diff --git a/xml/cn/chapter4/section4/subsection2.xml b/xml/cn/chapter4/section4/subsection2.xml new file mode 100644 index 000000000..0b4d3c817 --- /dev/null +++ b/xml/cn/chapter4/section4/subsection2.xml @@ -0,0 +1,1442 @@ + + + How the Query System Works + + + + + query interpreteroverview + + + In section we will + present an implementation of the query interpreter as a collection of + + procedures. + functions. + + In this section we give an overview that explains the general + structure of the system independent of low-level implementation + details. After describing the implementation of the interpreter, we + will be in a position to understand some of its limitations and some + of the subtle ways in which the query languages logical operations + differ from the operations of mathematical logic. + + + + It should be apparent that the query evaluator must perform some kind + of search in order to match queries against facts and rules in the + data base. One way to do this would be to implement the query system + as a nondeterministic program, using the amb + evaluator of section + (see exercise). Another possibility + is to manage the search with the aid of streams. Our implementation follows + this second approach. + + + + The query system is organized around two central operations, called + pattern matching and unification. We first describe + pattern matching and explain how this operation, together with the + organization of information in terms of streams of frames, enables us + to implement both simple and compound queries. We next discuss + unification, a generalization of pattern matching needed to implement + rules. Finally, we show how the entire query interpreter fits + together through a + + procedure + function + + that classifies + + expressions + queries + + in a manner analogous to the way + + + eval + + + evaluate + + + classifies expressions for the interpreter described in + section. + + + + Pattern matching + + + query interpreterpattern matching + pattern matching + + + A pattern matcher is a program that tests whether some datum + fits a specified pattern. For example, the + + + data list + ((a b) c (a b)) + + + datum + list(list("a", "b"), "c", list("a", "b")) + + + matches the pattern + + (?x c ?x) + list($x, "c", $x) + + + with the pattern variable + + ?x + $x + + bound to + + (a b). + list("a", "b"). + + The same + + + data list + + + data list + + + matches the pattern + + (?x ?y ?z) + list($x, $y, $z) + + + with + + ?x + $x + + and + + ?z + $z + + both bound to + + (a b) + list("a", "b") + + + and + + ?y + $y + + bound to + + c. + "c". + + It also matches the pattern + + + ((?x ?y) c (?x ?y)) + + list(list($x, $y), "c", list($x, $y)) + + + with + + ?x + $x + + bound to + + a + "a" + + and + + ?y + $y + + bound to + + b. + "b". + + However, it does not match the pattern + + (?x a ?y), + list($x, "a", $y), + + since that pattern specifies a list whose second element is the + + symbol a. + string "a". + + + + + The pattern matcher used by the query system takes as inputs a + pattern, a datum, and a + query interpreterframe + frame (query interpreter) + frame that specifies bindings for + various pattern variables. It checks whether the datum matches the + pattern in a way that is consistent with the bindings already in the + frame. If so, it returns the given frame augmented by any bindings + that may have been determined by the match. Otherwise, it indicates + that the match has failed. + + + + + + For example, using the pattern + + + Using the pattern + + + + (?x ?y ?x) + list($x, $y, $x) + + + to match + + (a b a) + list("a", "b", "a") + + + + + given an empty frame + + + given an empty frame, for example, + + + will return a frame specifying that + + ?x + $x + + is bound to + + a + "a" + + and + + ?y + $y + + is bound to + + b. + "b". + + Trying the match with the same pattern, the same datum, and a frame + specifying that + + ?y + $y + + is bound to + + a + "a" + + will fail. Trying the match with the same pattern, the same datum, and a + frame in which + + ?y + $y + + is bound to + + b + "b" + + and + + ?x + $x + + is unbound will return the given frame augmented by a binding of + + ?x + $x + + toa."a". + + + + The pattern matcher is all the mechanism that is needed to process + simple queryprocessing + simple + queries that dont involve rules. For instance, to process the query + + +(job ?x (computer programmer)) + + + job($x, list("computer", "programmer")) + + + we scan through all assertions in the data base and select those that + match the pattern with respect to an initially empty frame. For each + match we find, we use the frame returned by the match to instantiate + the pattern with a value for + + ?x. + $x. + + + + query interpreterpattern matching + pattern matching + + + Streams of frames + + + + The testing of patterns against frames is organized through the use of + query interpreterstreams of frames + stream(s)used in query interpreter + streams. Given a single frame, the matching process runs through the + data-base entries one by one. For each data-base entry, the matcher + generates either a special symbol indicating that the match has failed + or an extension to the frame. The results for all the data-base + entries are collected into a stream, which is passed through a filter + to weed out the failures. The result is a stream of all the frames + that extend the given frame via a match to some assertion in the data + base.Because matching is generally very + efficiencydataof data-base access + expensive, we would + like to avoid applying the full matcher to every element of the data + base. This is usually arranged by breaking up the process into a + fast, coarse match and the final match. The coarse match filters the + data base to produce a small set of candidates for the final match. + With care, we can arrange our data base so that some of the work of + coarse matching can be done when the data base is constructed rather + then when we want to select the candidates. This is called + data baseindexing + indexing a data base + indexing the data base. There is a vast technology built around + data-base-indexing schemes. Our implementation, described in + section, contains a + simpleminded form of such an optimization. + + + + In our system, a query takes an input stream of frames and performs + the above matching operation for every frame in the stream, as + indicated in + + + figure. + + + figure. + + + That is, for + each frame in the input stream, the query generates a new stream consisting + of all extensions to that frame by matches to assertions in the data base. + All these streams are then combined to form one huge stream, which contains + all possible extensions of every frame in the input stream. This stream is + the output of the query. + + +
+
+ A query processes a stream of frames. + +
+
+ +
+
+ A query processes a stream of frames. + +
+
+
+
+ + + To answer a + simple queryprocessing + simple query, we use the query with an input stream + consisting of a single empty frame. The resulting output stream + contains all extensions to the empty frame (that is, all answers to + our query). This stream of frames is then used to generate a stream + of copies of the original query pattern with the variables + instantiated by the values in each frame, and this is the stream that + is finally printed. + + + + Compound queries + + + compound queryprocessing + + + The real elegance of the stream-of-frames implementation is evident + when we deal with compound queries. The processing of compound + queries makes use of the ability of our matcher to demand that a match + be consistent with a specified frame. For example, to handle the + and (query language)evaluation of + and of two queries, such as + + +(and (can-do-job ?x (computer programmer trainee)) + (job ?person ?x)) + + +and(can_do_job($x, list("computer", "programmer", "trainee")), + job($person, $x)) + + + (informally, Find all people who can do the job of a computer + programmer trainee), we first find all entries that match the + pattern + + +(can-do-job ?x (computer programmer trainee)) + + +can_do_job($x, list("computer", "programmer", "trainee")) + + + This produces a stream of frames, each of which contains a binding for + + ?x. + $x. + + Then for each frame in the stream we find all entries that + match + + +(job ?person ?x) + + +job($person, $x) + + + in a way that is consistent with the given binding for + + ?x. + $x. + + Each such match will produce a frame containing bindings for + + ?x + $x + + and + + ?person. + $person. + + The and of two queries can be viewed as a series + combination of the two component queries, as shown in + + + figure. + + + figure. + + + The frames that pass through the + first query filter are filtered and further extended by the second query. + + +
+
+ + The and combination of two queries is + produced by operating on the stream of frames in series. + + +
+
+ +
+
+ + The and combination of two queries is + produced by operating on the stream of frames in series. + + +
+
+
+ +
+ + + + + Figure + + + Figure + + + shows the analogous method for + computing the + or (query language)evaluation of + or of two queries as a parallel + combination of the two component queries. The input stream of frames is + extended separately by each query. The two resulting streams are then + merged to produce the final output stream. + + + +
+
+ + The or combination of two queries is + produced by operating on the stream of frames in parallel and + merging the results. + + +
+
+ +
+
+ + The or combination of two + queries is produced by operating on the stream of frames in parallel + and merging the results. + + +
+
+
+ +
+ + + + Even from this high-level description, it is apparent that the + processing of compound queries can be slow. + efficiencyqueryof query processing + For example, since a query may produce more than one output frame for each + input frame, and each query in an and gets its + input frames from the previous query, an and + query could, in the worst case, have to perform a number of matches that is + exponential in the number of queries (see + exercise).But this kind + of exponential explosion is not common in and + queries because the added conditions tend to reduce rather than expand + the number of frames produced. + Though systems for handling only simple queries are quite practical, dealing + with complex queries is extremely difficult.There is a large + literature on data-base-management systems that is concerned with how to + handle complex queries efficiently. + + + + From the stream-of-frames viewpoint, the + not (query language)evaluation of + not of + some query acts as a filter that removes all frames for which the query can + be satisfied. For instance, given the pattern + + +(not (job ?x (computer programmer))) + + +not(job($x, list("computer", "programmer"))) + + + we attempt, for each frame in the input stream, to produce extension + frames that satisfy + + + (job ?x (computer programmer)). + + + job($x, list("computer", "programmer")). + + + We remove from the input stream all frames for which such extensions exist. + The result is a stream consisting of only those frames in which the binding + for + + ?x + $x + + does not satisfy + + (job ?x (computer programmer)). + + + job($x, list("computer", "programmer")). + + + For example, in processing the query + + +(and (supervisor ?x ?y) + (not (job ?x (computer programmer)))) + + +and(supervisor($x, $y), + not(job($x, list("computer", "programmer")))) + + + the first clause will generate frames with bindings for + + ?x + $x + + and + + ?y. + $y. + + The not clause will then filter these by + removing all frames in which the binding for + + ?x + $x + + satisfies the restriction that + + ?x + $x + + is a computer programmer.There is a subtle difference between this + filter implementation of not and the usual + meaning of not in mathematical logic. See + section. + + + + The + javascript_predicate (query language)evaluation of + + + lisp-value + special form + + + javascript_predicate + syntactic form + + + is implemented as a similar filter on frame streams. We use each frame in + the stream to instantiate any variables in the pattern, then apply the + + Lisp + JavaScript + + predicate. We remove from the input stream all frames for which the + predicate fails. + + + compound queryprocessing + + + Unification + + + query interpreterunification + unification + + + In order to handle rules in the query language, we must be able to + find the rules whose conclusions match a given query pattern. Rule + conclusions are like assertions except that they can contain + variables, so we will need a generalization of pattern + matchingcalled unificationin which both the + pattern and the datum may contain variables. + + + + + A unifier takes two patterns, each containing constants and variables, + and determines whether it is possible to assign values to the + variables that will make the two patterns equal. If so, it returns a + frame containing these bindings. For example, unifying + + (?x a ?y) + list($x, "a", $y) + + + and + + (?y ?z a) + list($y, $z, "a") + + + will specify a frame in which + + ?x, + $x, + + + ?y, + $y, + + and + + ?z + $z + + must all be bound to + + a. + "a". + + On the other hand, unifying + + (?x ?y a) + list($x, $y, "a") + + + and + + (?x b ?y) + list($x, "b", $y) + + + will fail, because there is no value for + + ?y + $y + + that can make the two patterns equal. (For the second elements of the + patterns to be equal, + + ?y + $y + + would have to be + + b; + "b"; + + however, for the third elements to be equal, + + ?y + $y + + would have to be + a.) + "a".) + + The unifier used in the query system, like the pattern matcher, takes a + frame as input and performs unifications that are consistent with this frame. + + + + The unification algorithm is the most technically difficult part of + the query system. With complex patterns, performing unification may + seem to require deduction. + To unify + + (?x ?x) + + + + list($x, $x) + + + + + and + + ((a ?y c) (a b ?z)), + + + +list(list("a", $y, "c"), list("a", "b", $z)) + + + + + for example, + the algorithm must infer that + + ?x + $x + + should be + + (a b c), + list("a", "b", "c"), + + + + ?y + $y + + should be + + b, + "b", + + and + + ?z + $z + + should be + + c. + "c". + + We may think of this process as solving a set of equations among the pattern + components. In general, these are simultaneous equations, which may require + substantial manipulation to solve.In one-sided pattern matching, + all the equations that contain pattern variables are explicit and already + solved for the unknown (the pattern variable). For example, + unifying + + (?x ?x) + list($x, $x) + + and + + ((a ?y c) (a b ?z)) + + list(list("a", $y, "c"), list("a", "b", $z)) + + + may be thought of as specifying the simultaneous equations + + + + \[\begin{array}{lll} + \texttt{?x} & = & \texttt{(a ?y c)} \\ + \texttt{?x} & = & \texttt{(a b ?z)} + \end{array}\] + + + + + \[\begin{array}{lll} + \texttt{\$x} & = & \texttt{list("a", \$y, "c")} \\ + \texttt{\$x} & = & \texttt{list("a", "b", \$z)} \\ + \end{array}\] + + + + These equations imply that + + + + \[ (a ?y c) = (a b ?z) \] + + + + + \[\begin{array}{lll} + \texttt{list("a", \$y, "c")} & = & \texttt{list("a", "b", \$z)} + \end{array}\] + + + + which in turn implies that + + + + \[ \texttt{a} = \texttt{a},\ + \texttt{?y} = \texttt{b},\ + \texttt{c} = \texttt{?z} \] + + + + + \[\begin{array}{lllllll} + \texttt{"a"} & = & \texttt{"a"},\qquad + \texttt{\$y} & = & \texttt{"b"},\qquad + \texttt{"c"} & = & \texttt{\$z} + \end{array}\] + + + + and hence that + + + + \[\begin{array}{lll} + \texttt{?x} & = & \texttt{(a b c)} + \end{array}\] + + + + + \[\begin{array}{lll} + \texttt{\$x} & = & \texttt{list("a", "b", "c")} + \end{array}\] + + + + + + + In a successful pattern match, all pattern variables become bound, and + the values to which they are bound contain only constants. This is + also true of all the examples of unification we have seen so far. + pattern matchingunification vs. + unificationpattern matching vs. + In general, however, a successful unification may not completely + determine the variable values; some variables may remain unbound and + others may be bound to values that contain variables. + + + + + Consider the unification of + + (?x a) + list($x, "a") + + and + + ((b ?y) ?z). + list(list("b", $y), $z). + + We can deduce that + + ?x + $x + + $=$ + + (b ?y) + list("b", $y) + + and + + a + "a" + + $=$ + + ?z, + $z, + + but we cannot further solve for + + ?x + $x + + or + + ?y. + $y. + + The unification doesnt fail, since it is certainly possible to make + the two patterns equal by assigning values to + + ?x + $x + + and + + ?y. + $y. + + Since this match in no way restricts the values + + ?y + $y + + can take on, no binding for + + ?y + $y + + is put into the result frame. The match does, however, restrict the value of + + ?x. + $x. + + Whatever value + + ?y + $y + + has, + + ?x + $x + + must be + + (b ?y). + list("b", $y). + + + A binding of + + ?x + $x + + to the pattern + + (b ?y) + list("b", $y) + + is thus put into the frame. If a value for + + ?y + $y + + is later determined and added to the frame (by a pattern match or + unification that is required to be consistent with this frame), the + previously bound + + ?x + $x + + will refer to this value.Another way to think of unification is + that it generates the most general pattern that is a specialization of the + two input patterns. + + + That is, the unification of + (?x a) + + + This means that the unification of + list($x, "a") + + + and + + ((b?y)?z) + + list(list("b", $y), $z) + + + is + + ((b ?y) a), + list(list("b", $y), "a") + + + and + + + that + + + the unification of + + (?x a ?y) + list($x, "a", $y) + + + and + + (?y ?z a), + list($y, $z, "a"), + + + discussed above, is + + (a a a). + list("a", "a", "a"). + + + For our implementation, it is more convenient to think of the result + of unification as a frame rather than a pattern. + + + query interpreterunification + unification + + + Applying rules + + + rule (query language)applying + + + Unification is the key to the component of the query system that makes + inferences from rules. To see how this is accomplished, consider + processing a query that involves applying a rule, such as + + +(lives-near ?x (Hacker Alyssa P)) + + +lives_near($x, list("Hacker", "Alyssa", "P")) + + + To process this query, we first use the ordinary pattern-match + + procedure + function + + described above to see if there are any assertions in the data base that + match this pattern. (There will not be any in this case, since our data + base includes no direct assertions about who lives near whom.) The next + step is to attempt to unify the query pattern with the conclusion of each + rule. We find that the pattern unifies with the conclusion of the rule + + +(rule (lives-near ?person-1 ?person-2) + (and (address ?person-1 (?town . ?rest-1)) + (address ?person-2 (?town . ?rest-2)) + (not (same ?person-1 ?person-2)))) + + +rule(lives_near($person_1, $person_2), + and(address($person_1, pair($town, $rest_1)), + address($person_2, list($town, $rest_2)), + not(same($person_1, $person_2)))) + + + resulting in a frame specifying that + + + ?person-2 + is bound to + (Hacker Alyssa P) + and that + ?x + should be bound to (have the same value as) + ?person-1. + + + $x + should be bound to (have the same value as) + $person_1 + and that + $person_2 + is bound to + list("Hacker", "Alyssa", "P"). + + + Now, relative to this frame, we evaluate the compound query given by the body + of the rule. Successful matches will extend this frame by providing a + binding for + + ?person-1, + $person_1, + + and consequently a value for + + ?x, + $x, + + which we can use to instantiate the original query pattern. + + + + In general, the query evaluator uses the following method to apply a + rule when trying to establish a query pattern in a frame that + specifies bindings for some of the pattern variables: + +
    +
  • + Unify the query with the conclusion of the rule to form, if + successful, an extension of the original frame. +
  • +
  • + Relative to the extended frame, evaluate the query formed by + the body of the rule. +
  • +
+
+ + + + Notice how similar this is to the method for applying a + + procedure + function + + in the + query interpreterLispJavaScript interpreter vs. + + eval/apply + evaluate/\linebreak[2]apply + + + evaluator for + + Lisp: + JavaScript: + +
    +
  • + Bind the + + procedures + functions + + parameters to its arguments to form a frame that extends the original + + procedure + function + + environment. +
  • +
  • + Relative to the extended environment, evaluate the expression + formed by the body of the + + procedure. + function. + +
  • +
+ The similarity between the two evaluators should come as no surprise. + Just as + + procedure + function + + definitions are the means of abstraction in + + Lisp, + JavaScript, + + rule definitions are the means of abstraction in the query language. + In each case, we unwind the abstraction by creating appropriate + bindings and evaluating the rule or + + procedure + function + + body relative to these. +
+ + rule (query language)applying + + + Simple queries + + + simple queryprocessing + + + We saw earlier in this section how to evaluate simple queries in the + absence of rules. Now that we have seen how to apply rules, we can + describe how to evaluate simple queries by using both rules and + assertions. + + + + Given the query pattern and a stream of frames, we produce, for each + frame in the input stream, two streams: +
    +
  • a stream of extended frames obtained by matching the pattern + against all assertions in the data base (using the pattern matcher), + and + +
  • +
  • a stream of extended frames obtained by applying all + possible rules (using the unifier).Since unification is a + pattern matchingunification vs. + unificationpattern matching vs. + generalization of matching, we could simplify the system by using the + unifier to produce both streams. Treating the easy case with the + simple matcher, however, illustrates how matching (as opposed to + full-blown unification) can be useful in its own right. +
  • +
+ Appending these two streams produces a stream that consists of all the + ways that the given pattern can be satisfied consistent with the + original frame. These streams (one for each frame in the input + stream) are now all combined to form one large stream, which therefore + consists 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. +
+ + simple queryprocessing + + + The query evaluator and the driver loop + + + + Despite the complexity of the underlying matching operations, the + system is organized much like an + query interpreterquery evaluator + query interpreterLispJavaScript interpreter vs. + evaluator for any language. The + + procedure + function + + that coordinates the matching operations is called + evaluate_query + + qeval, + evaluate_query, + + + and it plays a role analogous to that of the + + eval + evaluate + + + procedure + function + + for + + Lisp. + JavaScript. + + + Qeval + The function + evaluate_query + + + takes as inputs a query and a stream of frames. Its output is a stream of + frames, corresponding to successful matches to the query pattern, that + extend some frame in the input stream, as indicated in + + + figure. + + + figure. + + + Like + + eval, + evaluate, + + + qeval + evaluate_query + + + classifies the different types of expressions (queries) and dispatches to an + appropriate + + procedure + function + + for each. There is a + + procedure + function + + for each + + special + syntactic + + form + (and, or, + not, and + + + lisp-value) + + + javascript_predicate) + + + and one for simple queries. + + We use the more verbose name + evaluate_query rather than + qeval because we were forced + to use evaluate instead of + eval in section 4.1. + + + + + The + driver loopqueryin query interpreter + query interpreterdriver loop + driver loop, which is analogous to the + + + driver-loop + + + driver_loop + + + + procedure + function + + for the other evaluators in this chapter, reads queries + + from the terminal. + typed by the user. + + For each query, it calls + + qeval + evaluate_query + + + with the query and a stream that consists of a single empty frame. This + will produce the stream of all possible matches (all possible extensions to + the empty frame). For each frame in the resulting stream, it instantiates + the original query using the values of the variables found in the frame. + This stream of instantiated queries is then printed.The reason we + use + stream(s)used in query interpreter + query interpreterstreams of frames + streams (rather than lists) of frames is that the + recursive application of rules can generate infinite numbers of values that + satisfy a query. The delayed evaluation embodied in streams is crucial + here: The system will print responses one by one as they are generated, + regardless of whether there are a finite or infinite number of + responses. + + + + The driver also checks for the special command + assert (query interpreter) + query interpreteradding rule or assertion + + assert!, + assert, + + which signals that the input is not a query but rather an assertion or rule + to be added to the data base. For instance, + + +(assert! (job (Bitdiddle Ben) (computer wizard))) + +(assert! (rule (wheel ?person) + (and (supervisor ?middle-manager ?person) + (supervisor ?x ?middle-manager)))) + + +assert(job(list("Bitdiddle", "Ben"), list("computer", "wizard"))) + +assert(rule(wheel($person), + and(supervisor($middle_manager, $person), + supervisor($x, $middle_manager)))) + + query interpreteroverview + + + + +
diff --git a/xml/cn/chapter4/section4/subsection3.xml b/xml/cn/chapter4/section4/subsection3.xml new file mode 100644 index 000000000..d96fe9c83 --- /dev/null +++ b/xml/cn/chapter4/section4/subsection3.xml @@ -0,0 +1,753 @@ + + + Is Logic Programming Mathematical Logic? + + + + + query languagemathematical logic vs. + logic programmingmathematical logic vs. + + + The means of combination used in the query language may at first seem + identical to the operations and, + or, and not of + mathematical logic, and the application of query-language rules is in + fact accomplished through a legitimate method of + inference, method of + inference.That a particular method of inference is + legitimate is not a trivial assertion. One must prove that if one + starts with true premises, only true conclusions can be derived. The + method of inference represented by rule applications is + modusmodus ponens + modus ponens, + the familiar method of inference that says that if A is + true and A implies B is true, then we may conclude that B + is true. This identification of the query language with + mathematical logic is not really valid, though, because the query language + provides a + control structure + control structure that interprets the logical statements + procedurally. We can often take advantage of this control structure. + For example, to find all of the supervisors of programmers we could + formulate a query in either of two logically equivalent forms: + + +(and (job ?x (computer programmer)) + (supervisor ?x ?y)) + + +and(job($x, list("computer", "programmer")), + supervisor($x, $y)) + + + or + + +(and (supervisor ?x ?y) + (job ?x (computer programmer))) + + +and(supervisor($x, $y), + job($x, list("computer", "programmer"))) + + + If a company has + bureaucracy + many more supervisors than programmers, + it is better to use the first form rather than the second, + because the data base must be scanned for each intermediate result + (frame) produced by the first clause of the and. + + + + + The aim of logic programming is to provide the programmer with + techniques for decomposing a computational problem into two separate + problems: + declarative vs.imperative knowledgelogic programming and + imperative vs.declarative knowledgelogic programming and + what is to be computed, and how this + should be computed. This is accomplished by selecting a subset of the + statements of mathematical logic that is powerful enough to be able to + describe anything one might want to compute, yet weak enough to have a + controllable procedural interpretation. The intention here is that, + on the one hand, a program specified in a logic programming language + should be an effective program that can be carried out by a computer. + Control (how to compute) is effected by using the order of + evaluation of the language. We should be able to arrange the order of + clauses and the order of subgoals within each clause so that the + computation is done in an order deemed to be effective and efficient. + At the same time, we should be able to view the result of the + computation (what to compute) as a simple consequence of the + laws of logic. + + + + Our query language can be regarded as just such a procedurally + interpretable subset of mathematical logic. An assertion represents a + simple fact (an atomic proposition). A rule represents the + implication that the rule conclusion holds for those cases where the + rule body holds. A rule has a natural procedural interpretation: To + establish the conclusion of the rule, establish the body of the rule. + Rules, therefore, specify computations. However, because rules can + also be regarded as statements of mathematical logic, we can justify any + inference accomplished by a logic program by asserting that + the same result could be obtained by working entirely within + mathematical logic.We must qualify this statement by + agreeing that, in speaking of the inference accomplished + by a logic program, we assume that the computation terminates. + Unfortunately, even this qualified statement is false for our + implementation of the query language (and also false for programs in + Prolog and most other current logic programming languages) because of + our use of not and + + + lisp-value. + + + javascript_predicate. + + + As we will describe below, the not implemented + in the query language is not always consistent with the + not of mathematical logic, and + + + lisp-value + + + javascript_predicate + + + introduces additional complications. We could implement a language + consistent with mathematical logic by simply removing + not and + + + lisp-value + + + javascript_predicate + + + from the language and agreeing to write programs using only simple queries, + and, and or. + However, this would greatly restrict the expressive power of the language. + One of the major concerns of research in logic programming was to find ways + to achieve more consistency with mathematical logic without unduly + sacrificing expressive power. + + + + Infinite loops + + + query interpreterinfinite loops + + + A consequence of the procedural interpretation of logic programs is + that it is possible to construct hopelessly inefficient programs for + solving certain problems. An extreme case of inefficiency occurs when + the system falls into infinite loops in making deductions. As a + simple example, suppose we are setting up a data base of famous + marriages, including + + Mouse, Minnie and Mickey + +(assert! (married Minnie Mickey)) + + +assert(married("Minnie", "Mickey")) + + + If we now ask + + +(married Mickey ?who) + + +married("Mickey", $who) + + + we will get no response, because the system doesnt know that if + $A$ is married to $B$, + then $B$ is married to + $A$. So we assert the rule + + +(assert! (rule (married ?x ?y) + (married ?y ?x))) + + +assert(rule(married($x, $y), + married($y, $x))) + + + and again query + + +(married Mickey ?who) + + +married("Mickey", $who) + + + Unfortunately, this will drive the system into an infinite loop, as + follows: +
    +
  • + + + The system finds that the married rule is + applicable; that is, the rule conclusion + (married ?x ?y) + successfully unifies with the query pattern + (married Mickey ?who) + to produce a frame in which + + + The system finds that the married 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 + + + + ?x + $x + + is bound to + + Mickey + "Mickey" + + and + + ?y + $y + + is bound to + + ?who. + $who. + + So the interpreter proceeds to evaluate the rule body + + (married ?y ?x) + + married($y, $x) + + + in this framein effect, to process the query + + (married ?who Mickey). + + married($who, "Mickey"). + + +
  • +
  • + + + One answer appears directly as an assertion in the data + base: + (married Minnie Mickey). + + + One answer, + married("Minnie", "Mickey"), + appears directly as an assertion in the data base. + + +
  • +
  • + The married rule is also applicable, so the + interpreter again evaluates the rule body, which this time is equivalent + to + + (married Mickey ?who). + + married("Mickey", $who). + + +
  • +
+ The system is now in an infinite loop. Indeed, whether the system + will find the simple answer + + (married Minnie Mickey) + + married("Minnie", "Mickey") + + + before it goes into the loop depends on implementation details concerning the + order in which the system checks the items in the data base. This is a very + simple example of the kinds of loops that can occur. Collections of + interrelated rules can lead to loops that are much harder to anticipate, and + the appearance of a loop can depend on the order of clauses in an + and (see + exercise) or on low-level details + concerning the order in which the system processes queries.This is + not a problem of the logic but one of the procedural interpretation of the + logic provided by our interpreter. We could write an interpreter that would + not fall into a loop here. For example, we could enumerate all the proofs + derivable from our assertions and our rules in a breadth-first rather than a + depth-first order. However, such a system makes it more difficult to take + advantage of the order of deductions in our programs. One attempt to + build sophisticated control into such a program is described in + de Kleer, Johan + de Kleer et al.1977. + Another technique, which does not lead to such serious control problems, is + to put in special knowledge, such as detectors for particular kinds of loops + (exercise). However, there can + be no general scheme for reliably preventing a system from going down + infinite paths in performing deductions. Imagine a diabolical rule of + the form To show $P(x)$ is true, show that + $P(f(x))$ is true, for some suitably + chosen function $f$. +
+ + query interpreterinfinite loops + + + Problems with not + + + query interpreterproblems with not and javascript_predicate + + + Another quirk in the query system concerns + not (query language) + not. + Given the data base of + section, consider the + following two queries: + + +(and (supervisor ?x ?y) + (not (job ?x (computer programmer)))) + +(and (not (job ?x (computer programmer))) + (supervisor ?x ?y)) + + +and(supervisor($x, $y), + not(job($x, list("computer", "programmer")))) + +and(not(job($x, list("computer", "programmer"))), + supervisor($x, $y)) + + + These two queries do not produce the same result. The first query + begins by finding all entries in the data base that match + + (supervisor ?x ?y), + supervisor($x, $y), + + + and then filters the resulting frames by removing the ones in which the + value of + + ?x + $x + + satisfies + + + (job ?x (computer programmer)). + + + job($x,@list("computer", "programmer")). + + + The second query begins by filtering the + incoming frames to remove those that can satisfy + + + (job ?x (computer programmer)). + + + job($x, list("computer",@"programmer")). + + + Since the only incoming frame is empty, it checks the data base + + + to see if there are any + + + for + + + patterns that satisfy + + + (job ?x (computer programmer)). + + + job($x, list("computer", "programmer")). + + + Since there generally are entries of this form, the + not clause filters out the empty frame and + returns an empty stream of frames. Consequently, the entire compound query + returns an empty stream. + + + + The trouble is that our implementation of not + really is meant to serve as a filter on values for the variables. If a + not clause is processed with a frame in which + some of the variables remain unbound (as does + + ?x + $x + + in the example above), the system will produce unexpected results. Similar + problems occur with the use of + javascript_predicate (query language) + + lisp-valuethe + + javascript_@predicatethe + + + + + Lisp + predicate cant work if some of its arguments are unbound. + + + JavaScript + predicate cant work if some of its variables are unbound. + + + See exercise. + + + + There is also a much more serious way in which the + not of the query language differs from the + not of mathematical logic. In logic, we + interpret the statement not$P$ to + mean that $P$ is not true. In the query system, + however, not $P$ means that + $P$ is not deducible from the knowledge in the + data base. For example, given the personnel data base of + section, the system would + happily deduce all sorts of not statements, + such as that Ben Bitdiddle is not a baseball fan, that it is not raining + outside, and that $2 + 2$ + is not 4.Consider the query + + (not (baseball-fan (Bitdiddle Ben))). + + + not(baseball_fan(list("Bitdiddle", "Ben"))). + + + The system finds that + + + (baseball-fan (Bitdiddle Ben)) + + + baseball_fan(list("Bitdiddle", "Ben")) + + + is not in the data base, so the empty frame does not satisfy the pattern and + is not filtered out of the initial stream of frames. The result of the + query is thus the empty frame, which is used to instantiate the input query + to produce + (not (baseball-fan (Bitdiddle Ben)))not(baseball_fan(list("Bitdiddle", "Ben"))). In other + words, the not of logic programming languages + reflects the so-called + closed world assumption + closed world assumption that all relevant information has been + included in the data base.A discussion and justification of this + treatment of not can be found in the article + negation as failure + Negation as Failure by + Clark, Keith L. + Clark (1978). + + query interpreterproblems with not and javascript_predicate + + + Louis Reasoner mistakenly deletes the + outranked_by (rule) + + outranked-by + outranked_by + + rule (section) from the + data base. When he realizes this, he quickly reinstalls it. Unfortunately, + he makes a slight change in the rule, and types it in as + + +(rule (outranked-by ?staff-person ?boss) + (or (supervisor ?staff-person ?boss) + (and (outranked-by ?middle-manager ?boss) + (supervisor ?staff-person ?middle-manager)))) + + +rule(outranked_by($staff_person, $boss), + or(supervisor($staff_person, $boss), + and(outranked_by($middle_manager, $boss), + supervisor($staff_person, $middle_manager)))) + + + Just after Louis types this information into the system, DeWitt + Aull comes by to find out who outranks Ben Bitdiddle. He issues + the query + + +(outranked-by (Bitdiddle Ben) ?who) + + +outanked_by(list("Bitdiddle", "Ben"), $who) + + + + After answering, the system goes into an infinite loop. Explain why. + + + + + Cy D. Fect, looking forward to the day when he will rise in the + organization, gives a query to find all the wheels (using the + wheel (rule) + wheel rule of + section): + + +(wheel ?who) + + +wheel($who) + + + To his surprise, the system responds + + +;;; Query results: +(wheel (Warbucks Oliver)) +(wheel (Bitdiddle Ben)) +(wheel (Warbucks Oliver)) +(wheel (Warbucks Oliver)) +(wheel (Warbucks Oliver)) + + +Query results: +wheel(list("Warbucks", "Oliver")) +wheel(list("Bitdiddle", "Ben")) +wheel(list("Warbucks", "Oliver")) +wheel(list("Warbucks", "Oliver")) +wheel(list("Warbucks", "Oliver")) + + + Why is Oliver Warbucks listed four times? + + + + + Ben has been + query languageextensions to + generalizing the query system to provide statistics about the + company. For example, to find the total salaries of all the computer + programmers one will be able to say + + +(sum ?amount + (and (job ?x (computer programmer)) + (salary ?x ?amount))) + + +sum($amount, + and(job($x, list("computer", "programmer")), + salary($x, $amount))) + + + In general, Bens new system allows expressions of the form + + +(accumulation-function variable + $\langle query$ $pattern\rangle$) + + +accumulation_function($variable$, + $query$-$pattern$) + + + where + + + accumulation-function + + + accumulation_function + + + can be things like sum, + average, or + maximum. + Ben reasons that it should be a cinch to implement this. He will simply + feed the query pattern to + + qeval. + evaluate_query. + + + This will produce a stream of frames. He will then pass this stream through + a mapping function that extracts the value of the designated variable from + each frame in the stream and feed the resulting stream of values to the + accumulation function. Just as Ben completes the implementation and is + about to try it out, Cy walks by, still puzzling over the + wheel query result in + exercise. When Cy shows Ben the + systems response, Ben groans, Oh, no, my simple accumulation + scheme wont work! +

+ What has Ben just realized? Outline a method he can use to salvage the + situation. + +
+ + + Devise a + query interpreterimprovements to + query interpreterinfinite loops + way to install a loop detector in the query system so as to + avoid the kinds of simple loops illustrated in the text and in + exercise. The general idea is + that the system should maintain some sort of history of its current chain of + deductions and should not begin processing a query that it is already + working on. Describe what kind of information (patterns and frames) + is included in this history, and how the check should be made. (After + you study the details of the query-system implementation in + section, you may + want to modify the system to include your loop detector.) + + + + + + Define rules to implement the + reverserules + reverse operation + of exercise, which returns a list containing + the same elements as a given list + in reverse order. + (Hint: Use + + + append-to-form.) + + + append_to_form.) + + + Can your rules answer both + + (reverse (1 2 3) ?x) + the query + reverse(list(1, 2, 3), $x) + + + and + (reverse ?x (1 2 3))? + the query reverse($x, list(1, 2, 3))? + + + + + + + + + + Beginning with the data base and the rules you formulated in + exercise, devise a rule for adding + greats to a grandson relationship. This should enable the + system to deduce that Irad is the great-grandson of Adam, or that Jabal + and Jubal are the great-great-great-great-great-grandsons of Adam. + (Hint: Represent the fact about Irad, for example, as + ((great grandson) Adam Irad). + Write rules that determine if a list ends in the word + grandson. + Use this to express a rule that allows one to derive the relationship + ((great . ?rel) ?x ?y), + where ?rel is a list ending in + grandson.) Check your rules on queries such + as ((great grandson) ?g ?ggs) and + (?relationship Adam Irad). + + + + + + Let us modify the data base and the rules of + exercise to add + great to a grandson relationship. This should enable the + system to deduce that Irad is the great-grandson of Adam, or that Jabal + and Jubal are the great-great-great-great-great-grandsons of Adam. +
    +
  1. + Change the assertions in the data base such that there is only one + kind of relationship information, namely + related. The first item + then describes the relationship. Thus, instead of + son("Adam", "Cain"), you would + write + related("son", "Adam", "Cain"). + Represent the fact about Irad, for example, as + + +related(list("great", "grandson"), "Adam", "Irad") + + +
  2. +
  3. + Write rules that determine if a list ends in the word + "grandson". +
  4. +
  5. + Use this to express a rule that allows one to derive the relationship + + +list(pair("great", $rel), $x, $y) + + + where $rel is a list ending in + "grandson". +
  6. +
  7. + Check your rules on the queries + related(list("great", "grandson"), $g, $ggs) + and + related($relationship, "Adam", "Irad"). +
  8. +
+ + This exercise is a bit more verbose than the original, because of the + choice of using explicit predicate symbols rather than general lists, + see comment in the beginning of section 4.4.1. So the hint here is + to revert to a list representation that makes the relationship + explicit, and therefore programmable. This trick is used in the + implementation of HiLog, a logic programming language with first-class + predicates, see + Chen, Weidong; Kifer, Michael; Warren, David S. + (February 1993). HiLog: A foundation for higher-order logic + programming. Journal of Logic Programming. 15 (3): 187–230. + +
+
+
+ + query languagemathematical logic vs. + logic programmingmathematical logic vs. + +
diff --git a/xml/cn/chapter4/section4/subsection4.xml b/xml/cn/chapter4/section4/subsection4.xml new file mode 100644 index 000000000..cca7430ab --- /dev/null +++ b/xml/cn/chapter4/section4/subsection4.xml @@ -0,0 +1,4290 @@ + + + Implementing the Query System + + + + + Section described how the query + system works. Now we fill in the details by presenting a complete + implementation of the system. + + + + The Driver Loop and Instantiation + + + + The + driver loopqueryin query interpreter + query interpreterdriver loop + driver loop for the query system repeatedly reads input expressions. + If the expression is a rule or assertion to be added to + the data base, then the information is added. Otherwise the + expression is assumed to be a query. The driver passes this query to + 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 + + + + promptsquery interpreter + query_driver_loop + 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 + abstract syntaxin query interpreter + abstract syntax for expressions of the query language. + The implementation of the + expression syntax, including the predicate + 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 + abstract syntaxin query interpreter + parsein query interpreter + 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 + query interpreterinstantiation + instantiate_expression + instantiate_expression and + unparsein query interpreter + unparse are declared in + section. + + + + + + + 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 + pattern variablerepresentation of + query interpreterpattern-variable representation + representation of pattern variables. When the query is instantiated, any + variables that remain unbound are transformed back to the input + representation before being printed. These transformations are performed + by the two procedures + query-syntax-process + and + contract-question-mark + (section). + + + + To + query interpreterinstantiation + instantiate an expression, we copy it, replacing any variables in + the expression by their values in a given frame. The values are + themselves instantiated, since they could contain variables (for + example, if + ?x + in exp is bound to + ?y + as the result of unification and + ?y + is in turn bound to5). + 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. + + + + + + query interpreterinstantiation + query interpreterdriver loop + + + The Evaluator + + + + The + query interpreterquery evaluator + + 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 + data-directed programmingin query interpreter + data-directed dispatch using 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 + 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. + + + + + + Simple queries + + + simple queryprocessing + + + 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 + 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); +} + + + + + + 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. + + + simple queryprocessing + + + Compound queries + + + compound queryprocessing + + + + + And + and (query language)evaluation of + queries are handled as illustrated in + figure + by the + + + We handle + and (query language)evaluation of + 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 + 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. + + + + + + Or + or (query language)evaluation of + queries are handled + + + We handle + or (query language)evaluation of + 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 + 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); + + + + + + The predicates and selectors for the + + syntax + representation + + of conjuncts and disjuncts + are given in section. + + + + + Filters + + + + + + Not + not (query language)evaluation of + is + + + The + not (query language)evaluation of + 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 + 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); + + + + + + + + lisp-value (query language)evaluation of + Lisp-value + + + The + javascript_predicate (query language)evaluation of + 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 (query interpreter) + 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); + + + + + + + + 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 + evalused in query interpreter + eval + and apply from the + underlying Lisp system. + + + + + + The original is more restrictive here. We think that allowing + primitive functions to be applied is at the same simpler and + more powerful. (We could do away with the syntax functions + predicate and + args.) + + + 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))) + + + + + + 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 + + + rule (query language)without body + to provide bodies for rules that were defined without bodies (that is, + rules whose bodies are always satisfied). + + 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. + + + + compound queryprocessing + query interpreterquery evaluator + + + Finding Assertions by Pattern Matching + + + + + Find-assertions, + + + The function find_assertions, + + + query interpreterpattern matching + pattern matchingimplementation + called by + + 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 + 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)); +} + + + + + + + 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 + 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 + 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"; +} + + + + + + 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 + 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. + + + + The + + procedures + functions + + used by + + extend-if-consistent + + extend_if_consistent + + to manipulate bindings are defined in + section. + + + + + + Patterns with dotted tails + + + + 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 + dotted-tail notationqueryin query pattern + dotted-tail notation described in + exercise. Although the pattern + matcher we have just implemented doesnt 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 + readdotted-tail notation handling by + dotted-tail notationreadread and + dot, instead of making + the next item be the + next element of a list (the 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 cars and + cdrs 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). + + + + + Dotted tail notation is unnecessary in the JavaScript edition. We + simply use the pair constructor + rather than the list constructor + when we want to refer to the rest of a list, as shown in + section. + See also section. + + + + + + + query interpreterpattern matching + pattern matchingimplementation + + + Rules and Unification + + + rule (query language)applying + + + + 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 + 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)); +} + + + + + + + + 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. + + + + 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 + + ?xs + $xs + + 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 + 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. + + + + 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 + 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); +} + + + + + rule (query language)applying + + + The + query interpreterunification + unificationimplementation + unification algorithm is implemented as a + + 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 + symmetricalvariables 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 + 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"; +} + + + + + + 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. + + + + + 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.In general, + unifying + + + ?y + with an expression involving + + + $y + with an expression involving + + + + ?y + $y + + would require our being able to find a + fixed pointunification and + fixed point of the equation + + + ?y + $ = \textit{expression involving}$ ?y. + + + $y + $ = \langle$expression involving $y$\rangle$. + + + + + It is sometimes possible to syntactically form an expression that appears + to be the solution. For example, + ?y + + + It is sometimes possible to syntactically form an expression that appears + to be the solution. For example, + $y + + + $=$ + + (f ?y) + list("f", $y) + + + seems to have the fixed point + + (f (f (f + ))), + + + list("f", list("f", list("f", + + ))), + + + which we can produce by beginning with the expression + + (f ?y) + list("f", $y) + + + and repeatedly substituting + + (f ?y) + list("f", $y) + + + for + + ?y. + $y. + + Unfortunately, not every such equation has a meaningful fixed point. The + issues that arise here are similar to the issues of manipulating + infinite series + infinite series in mathematics. For example, we know that 2 is the + solution to the equation $y = 1 + y/2$. + Beginning with the expression $1 + y/2$ and + repeatedly substituting $1 + y/2$ for + $y$ gives + + \[ + 2 \ = \ y \ = \ 1 + y/2 \ = \ 1 + (1+y/2)/2 \ = \ 1 + 1/2 + y/4 \ = \ \cdots, + \] + + which leads to + + \[ 2 \ = \ 1 + 1/2 + 1/4 + 1/8 +\cdots. \] + + However, if we try the same manipulation beginning with the + observation that $-1$ is the solution to the + equation $y \ = \ 1 + 2y$, we obtain + + \[ -1 \ = \ y \ = \ 1 + 2y \ = \ 1 + 2(1 + 2y) \ = \ 1 + 2 + 4y \ = \ \cdots, \] + + which leads to + + \[ -1 \ = \ 1 + 2 + 4 + 8 +\cdots. \] + + Although the formal manipulations used in deriving these two equations + are identical, the first result is a valid assertion about infinite + series but the second is not. Similarly, for our unification results, + reasoning with an arbitrary syntactically constructed expression may + lead to errors. + + + + 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 + Jaffar, Joxan + Stuckey, Peter J. + treerational + rational tree + (Jaffar and Stuckey 1986). + Accepting + a cyclic data structure allows self-referential data, such + as an employee data structure that refers to the employer, + which in turn refers to the employee. + + + + + On the other hand, we do not want to reject attempts + to bind a variable to itself. For example, consider unifying + + (?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 + 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); + } +} + + + + + + + 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 + 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); +} + + + + + query interpreterunification + unificationimplementation + + + + + + Maintaining the Data Base + + + + + One important problem in designing logic programming languages is that + of arranging things so that as few irrelevant + query interpreterdata base + data baseindexing + indexing a data base + data-base entries as + possible will be examined in checking a given pattern. For this purpose, + we will represent an assertion as a list whose head is a string that + represents the kind of information of the assertion. + + + Then, + in addition to storing all assertions in one big stream, + we store all assertions whose + cars + 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 patterns + 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 + 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; +} + + + + + + + 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 + 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"); +} + + + + + + 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 + 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"; +} + + + + + + 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)); +} + + + + + + + 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))) + + + + + + + + + + What is the purpose of the + let bindings + in the + procedures + add-assertion! + and + add-rule!? + What would be wrong with the following implementation of + add-assertion!? + Hint: Recall the definition of the infinite stream of ones in + section: + (define ones (cons-stream 1 ones)). + + add_assertion + add_assertion + store_assertion_in_index + fetch_assertions + append_to_form_example_5 + +(define (add-assertion! assertion) + (store-assertion-in-index assertion) + (set! THE-ASSERTIONS + (cons-stream assertion THE-ASSERTIONS)) + 'ok) + + + + + + + + query interpreterdata base + + + Stream Operations + + + query interpreterstream operations + + + The query system uses a few stream operations that were not presented + in chapter. + + + + + 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 + interleave_delayed + 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))); +} + + + + + + + + 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 + flatten_stream + 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))); +} + + + + + + The evaluator also uses the following simple + + procedure + function + + to generate a stream consisting of a single element: + + singleton_stream + 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); +} + + + + + + query interpreterstream operations + + + + Query + + Syntax Procedures + Syntax Functions and Instantiation + + + + + query interpretersyntax of query language + query-language-specific representation + + + + + 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 + abstraction barriersin query language + abstraction barriers used by the query system and how the transformation functions + parse, + unparse, and + convert_to_query_syntax bridge them. +
+
+ Syntax abstraction in the query system. + +
+
+ + + Handling pattern variables + + + + 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. + query interpreterpattern-variable representation + pattern variablerepresentation of + We assume there is a function char_at that + returns a string containing only the character of the given string + at the given position.The actual way to get the string that + contains the first + character of a string s in JavaScript is + s.charAt(0). + + + is_variablein query system + is_variable_2 + +function is_variable(exp) { + return is_name(exp) && char_at(symbol_of_name(exp), 0) === "$"; +} + + +const is_variable = is_name; + + + + + 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.Creating + new variables with string concatenation and identifying + variables by checking their first character during query + processing is somewhat wasteful. A more efficient solution + would mark pattern variables with a separate tag in the + query-language-specific representation and use pair + construction rather than string concatenation to create + new variables. We chose the less efficient solution + to simplify the presentation. + + make_new_variable + 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)); +} + + + + + + The function convert_to_query_syntax + + + The function convert_to_query_syntax + recursively + query-language-specific representationtransforming JavaScript syntax into + transforms the JavaScript syntax representation into + the query-language-specific representation by simplifying + assertions, rules, and queries such that the symbol of a name in a + function expression of an application becomes a tag, except that if + the symbol is "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 + 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); + } +} + + + + + + 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. + + + + + Instantiating an expression + + + query interpreterinstantiation + + + 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_expression + 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 to3, + the result of applying + instantiate_term to + list("name", "$x") and + $f$ is the pair + $[3, 5]$. + + instantiate_term + 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 + 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"))' + + + + + query interpreterinstantiation + + + The function unparse + + + + + 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 + stringifying its value, and + a name is transformed into its + symbol(s)in unparse + symbol. + An application is formatted by unparsing the function expression, + which we can assume to be a name here, followed by the comma-separated + argument expression strings enclosed in parentheses. + Binary operator combinations are formatted using infix notation. + + unparsein query interpreter + 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 + 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 + 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))))); +} + + + + + + + Predicates and selectors for the query-language-specific representation + + + + 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 in query system + contents + 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"); +} + + + + + 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 + assertion_body + is_assertion + type + append_to_form_example_5 + +function is_assertion(exp) { + return type(exp) === "assert"; +} +function assertion_body(exp) { return head(contents(exp)); } + + + + + Here are the declarations of the predicates and selectors + for the and, + or, not, and + javascript_predicate + syntactic forms + (section): + + is_empty_conjunction + first_conjunct + rest_conjuncts + is_empty_disjunction + first_disjunct + rest_disjuncts + negated_query + javascript_predicate_expression + 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); } + + + + + + The following three + functions + define the query-language-specific representation of rules: + + is_rule + conclusion + rule_body + 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))); +} + + + +
+
+ + query interpretersyntax of query language + query-language-specific representation + + + + + 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))) + + + + + 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))) + + + + + 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)) + + + + + 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))) + + + + + + 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:Most Lisp systems give the user + the ability to modify the ordinary read + procedure to perform such transformations by defining + reader macro character + readmacro characters + ' (single quote)readread and + quotereadread and + reader macro characters. Quoted + expressions are already handled in this way: The reader automatically + translates 'expression into + (quote expression) before the + evaluator sees it. We could arrange for + ?expression to be transformed into + (? expression) in the same way; however, + for the sake of clarity we have included the transformation + procedure here explicitly. +

+ character stringsprimitive procedures for + Expand-question-mark and + contract-question-mark use several + procedures with string in their names. + These are Scheme primitives.
+ + map-over-symbols + 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))) + + +
+ + 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)) + + + + + 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)))) + + + + + 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)))))) + + + +
+
+
+ + + Frames and Bindings + + + query interpreterframe + frame (query interpreter)representation + + + Frames are represented as lists of bindings, which are + variable-value pairs: + + make_binding + binding_variable + binding_value + binding_in_frame + extend + 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); +} + + + + + query interpreterframe + + + Louis Reasoner wonders why the + + simple-query + simple_query + + + and disjoin + + procedures + functions + + (section) are implemented using + + + explicit delay + operations, + + delayed expressions + + rather than being defined as follows: + + + simple_querywithout delayed expression + disjoinwithout delayed expression + simple_query_pattern + stream_flatmap + find_assertions + apply_rules + is_empty_conjunction + +(define (simple-query query-pattern frame-stream) + (stream-flatmap + (lambda (frame) + (stream-append (find-assertions query-pattern frame) + (apply-rules query-pattern frame))) + frame-stream)) + +(define (disjoin disjuncts frame-stream) + (if (empty-disjunction? disjuncts) + the-empty-stream + (interleave + (qeval (first-disjunct disjuncts) frame-stream) + (disjoin (rest-disjuncts disjuncts) frame-stream)))) + + +function simple_query(query_pattern, frame_stream) { + return stream_flatmap( + frame => + stream_append(find_assertions(query_pattern, frame), + apply_rules(query_pattern, frame)), + frame_stream); +} +function disjoin(disjuncts, frame_stream) { + return is_empty_disjunction(disjuncts) + ? null + : interleave( + evaluate_query(first_disjunct(disjuncts), frame_stream), + disjoin(rest_disjuncts(disjuncts), frame_stream)); +} + + + Can you give examples of queries where these simpler definitions would + lead to undesirable behavior? + + + + + Why do disjoin and + + stream-flatmap + stream_flatmap + + + interleave the streams rather than simply append them? Give examples that + illustrate why interleaving works better. (Hint: Why did we use + interleave in + section?) + + + + + + Why does + + flatten-stream + flatten_stream + + + use + + delay explicitly? + a delayed expression in its body? + + What would be wrong with defining it as follows: + + flatten_stream + +(define (flatten-stream stream) + (if (stream-null? stream) + the-empty-stream + (interleave + (stream-car stream) + (flatten-stream (stream-cdr stream))))) + + +function flatten_stream(stream) { + return is_null(stream) + ? null + : interleave(head(stream), + flatten_stream(stream_tail(stream))); +} + + + + + + Alyssa P. Hacker proposes to use a simpler version of + stream_flatmap + + stream-flatmap + stream_flatmap + + + in negate, + + lisp-value, + javascript_predicate, + + + and + + find-assertions. + find_assertions. + + + She observes that the + + procedure + function + + that is mapped over the frame stream in these cases always produces either + the empty stream or a singleton stream, so no interleaving is needed when + combining these streams. +
    +
  1. + Fill in the missing expressions in Alyssas program. + + simple_stream_flatmap + +(define (simple-stream-flatmap proc s) + (simple-flatten (stream-map proc s))) + +(define (simple-flatten stream) + (stream-map ?? + (stream-filter ?? stream))) + + +function simple_stream_flatmap(fun, s) { + return simple_flatten(stream_map(fun, s)); +} +function simple_flatten(stream) { + return stream_map(??, + stream_filter(??, stream)); +} + + +
  2. +
  3. + Does the query systems behavior change if we change it in this + way? +
  4. +
+ +
+ + + Implement for the query language a + query languageextensions to + compound queryprocessing + unique (query language) + + new special form called + unique. + syntactic form called + unique. + + + + Unique + + + Applictions of unique + + + should succeed if there is precisely one item in the data base satisfying + a specified query. For example, + + unique_example_1 + +(unique (job ?x (computer wizard))) + + +unique(job($x, list("computer", "wizard"))) + + + should print the one-item stream + + unique_example_2 + +(unique (job (Bitdiddle Ben) (computer wizard))) + + +unique(job(list("Bitdiddle", "Ben"), list("computer", "wizard"))) + + + since Ben is the only computer wizard, and + + unique_example_3 + +(unique (job ?x (computer programmer))) + + +unique(job($x, list("computer", "programmer"))) + + + should print the empty stream, since there is more than one computer + programmer. Moreover, + + unique_example_4 + +(and (job ?x ?j) (unique (job ?anyone ?j))) + + +and(job($x, $j), unique(job($anyone, $j))) + + + should list all the jobs that are filled by only one person, and the + people who fill them. +

+ There are two parts to implementing unique. + The first is to write a + + procedure + function + + that handles this + + special + syntactic + + form, and the second is to make + + qeval + evaluate_query + + + dispatch to that + + procedure. + function. + + The second part is trivial, since + + qeval + evaluate_query + + + does its dispatching in a data-directed way. If your + + procedure + function + + is called + + uniquely-asserted, + uniquely_asserted, + + + all you need to do is + + put_unique + operation_table_from_chapter_3 + operation_table + +(put 'unique 'qeval uniquely-asserted) + + +put("unique", "evaluate_query", uniquely_asserted); + + + and + + qeval + evaluate_query + + + will dispatch to this + + procedure + function + + for every query whose + type + + (car) + (head) + + is the + + symbol + string + + + unique. + "unique". + +

+ The real problem is to write the + + procedure + function + + + uniquely-asserted. + uniquely_asserted. + + + This should take as input the contents + + (cdr) + (tail) + + of the unique query, together with a stream + of frames. For each frame in the stream, it should use + + qeval + evaluate_query + + + to find the stream of all extensions to the frame that satisfy the given + query. Any stream that does not have exactly one item in it should be + eliminated. The remaining streams should be passed back to be accumulated + into one big stream that is the result of the + unique query. This is similar to the + implementation of the not + + special + syntactic + + form. +

+ Test your implementation by forming a query that lists all people who + supervise precisely one person. + +
+ + + Our implementation of and as a series + combination of queries + query interpreterimprovements to + and (query language)evaluation of + compound queryprocessing + + + (figure) + + + (figure) + + + is + elegant, but it is inefficient because in processing the second query of + the and we must scan the data base for each + frame produced by the first query. If the data base has + $N$ elements, and a typical query produces a + number of output frames proportional to $N$ + (say $N/k$), then scanning the data base for + each frame produced by the first query will require + $N^{2}/k$ calls to the pattern matcher. + Another approach would be to process the two clauses of the + and separately, then look for all pairs of + output frames that are compatible. If each query produces + $N/k$ output frames, then this means that we + must perform $N^{2}/k^{2}$ compatibility + checksa factor of $k$ fewer than the + number of matches required in our current method. +

+ Devise an implementation of and that uses + this strategy. You must implement a + + procedure + function + + that takes two frames as inputs, checks whether the bindings in the + frames are compatible, and, if so, produces a frame that merges the two + sets of bindings. This operation is similar to unification. + +
+ + + In section we saw that + query interpreterimprovements to + query interpreterproblems with not and javascript_predicate + compound queryprocessing + not (query language)evaluation of + not and + javascript_predicate (query language)evaluation of + + lisp-value + javascript_predicate + + + can cause the query language to give wrong answers if + these filtering operations are applied to frames in which variables + are unbound. Devise a way to fix this shortcoming. One idea is to + perform the filtering in a delayed manner by appending to + the frame a promise to filter that is fulfilled only when + enough variables have been bound to make the operation possible. We could + wait to perform filtering until all other operations have been performed. + However, for efficiencys sake, we would like to perform filtering + as soon as possible so as to cut down on the number of intermediate frames + generated. + + + + + + Redesign the query language as a + query interpreternondeterministicas nondeterministic program + nondeterministic program to be + implemented using the evaluator of + section, rather than as a stream + process. In this approach, each query will produce a single answer + (rather than the stream of all answers) and the user can type + + try-again + retry + + + to see more answers. You should find that much of the mechanism we built + in this section is subsumed by nondeterministic search and backtracking. + You will probably also find, however, that your new query language has + subtle differences in behavior from the one implemented here. Can you + find examples that illustrate this difference? + + + + + + When we implemented the + + Lisp + JavaScript + + evaluator in section, we saw how to use + local environments to avoid + environmentrenaming vs. + query interpreterLispJavaScript interpreter vs. + name conflicts between the parameters of + + procedures. + functions. + + For example, in evaluating + + sum_of_squares_in_lp + +(define (square x) + (* x x)) + +(define (sum-of-squares x y) + (+ (square x) (square y))) + +(sum-of-squares 3 4) + + +function square(x) { + return x * x; +} +function sum_of_squares(x, y) { + return square(x) + square(y); +} +sum_of_squares(3, 4); + + + there is no confusion between the x in + square and the x + in + + sum-of-squares, + sum_of_squares, + + + because we evaluate the body of each + + procedure + function + + in an environment that is specially constructed to contain + bindings for the local + + variables. + names. + + In the query system, we used a + different strategy to avoid name conflicts in applying rules. Each + time we apply a rule we rename the variables with new names that are + guaranteed to be unique. The analogous strategy for the + + Lisp + JavaScript + + evaluator would be to do away with local environments and simply + rename the variables in the body of a + + procedure + function + + each time we apply the + + procedure. + function. + +

+ Implement for the query language a rule-application method that uses + environments rather than renaming. See if you can build on your + environment structure to create constructs in the query language for + dealing with large systems, such as the rule analog of + block structurequeryin query language + environmentqueryin query interpreter + query interpreterenvironment structure in + rule (query language)applying + block-structured + + procedures. + functions. + + Can you relate any of this to the problem of making deductions in a + context (e.g., If I supposed that $P$ + were true, then I would be able to deduce $A$ + and $B$.) as a method of problem + solving? (This problem is open-ended.) + +
+
+
diff --git a/xml/cn/chapter5/chapter5.xml b/xml/cn/chapter5/chapter5.xml new file mode 100644 index 000000000..65401986d --- /dev/null +++ b/xml/cn/chapter5/chapter5.xml @@ -0,0 +1,236 @@ + + Computing with Register Machines + + \addtocontents{toc}{\protect\enlargethispage{\baselineskip}} + + + % Feb '98 Add tex '\label's for manual to reference. + % 4/13-4/15 Indexing [after final printout of chapters] + % 4/13/96 Last-minute pagination change to get compiled-code figs on + % facing pages + % 4/12 Pagination adjustments from proofreading + % 4/11/96 minor fixes; checked against before-indexing + % 4/9/96 fix a word + % 4/6-... Julie index fixes (and 4/8 change 'ns') + % 4/5/96 Hal indexing + % 4/3/96 indexing changes + % 4/2/96 adjusted pagination in 5.1-5.3, paginated rest of 5.5 + % 4/1/96 Julie: paginated 5.4 and part of 5.5 + % 3/29/96 Julie: indexing fixes + % 3/22/96 Julie: paginated 5.2-5.3 + % 3/21/96 Julie: paginated 5.1 + % 3/20-3/21/96 Julie & Hal indexing + % 3/17/96 Julie: a couple of index entries consistent with ch4 + % 3/4/96 reword to 'fix' most triple and quadruple hyphenation + % 3/3/96 change countleaves to count-leaves (to match ch2 change) + % 2/26-3/3/96 fix some bad line breaks + % 2/24/96 flush \noindent after {lisp} by closing up space with + % 2/24/96 use smalltt to make some stuff fit nicely in 5.2 + % 2/23/96 use Stabular instead of point-size changes + % 2/22/96 new spec for epigraph + % 2/19/96 change exp-iter - -> expt-iter + + + + + My aim is to show that the heavenly machine is not a kind of divine, + live being, but a kind of clockwork (and he who believes that a clock + has soul attributes the makers glory to the work), insofar as nearly + all the manifold motions are caused by a most simple and material + force, just as all motions of the clock are caused by a single weight. + + Kepler, Johannes + Johannes Kepler + letter to Herwart von Hohenburg, 1605 + + + + + + + + + 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. + With our metacircular evaluator, a recursive function + always gives rise to a recursive process, even when + the process should be iterative according to the + distinction of section. + See footnote in + section. + + This chapter addresses both of these issues. + + + + + + + + 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 + register machine, sequentially executes + instructions that + manipulate the contents of a fixed set of storage elements called + register(s) + 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. + + + + 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. + + + + + + &section5.1; + + + &section5.2; + + + &section5.3; + + + &section5.4; + + + &section5.5; + + diff --git a/xml/cn/chapter5/section1/section1.xml b/xml/cn/chapter5/section1/section1.xml new file mode 100644 index 000000000..2254a15be --- /dev/null +++ b/xml/cn/chapter5/section1/section1.xml @@ -0,0 +1,270 @@ +
+ Designing Register Machines + + + + + + register machinedesign of + register machinedata paths + register machinecontroller + data paths for register machine + controller for register machine + operationin register machine + + + To design a register machine, we must design its data paths + (registers and operations) and the controller that sequences + these operations. To illustrate the design of a simple register + machine, let us examine Euclids Algorithm, which is used to compute + gcdregister machine for + the greatest common divisor (GCD) of two integers. As we saw in + section, + Euclids Algorithm + Euclids Algorithm can be + carried out by an iterative process, as specified by the following + + procedure: + function: + + + gcd_example + +(define (gcd a b) + (if (= b 0) + a + (gcd b (remainder a b)))) + + +function gcd(a, b) { + return b === 0 ? a : gcd(b, a % b); +} + + + + + + A machine to carry out this algorithm must keep track of two numbers, + $a$ and$b$, so let us + assume that these numbers are stored in two registers with those names. The + basic operations required are testing whether the contents of register + b is zero and computing the remainder of the + contents of register a divided by the contents + of register b. + + The remainder operation is a complex process, but assume for the moment that + we have a primitive device that computes remainders. On each cycle of the + GCD algorithm, the contents of register a must + be replaced by the contents of register b, and + the contents of b must be replaced by the + remainder of the old contents of a divided by + the old contents of b. It would be convenient + if these replacements could be done simultaneously, but in our model of + register machines we will assume that only one register can be assigned a + new value at each step. To accomplish the replacements, our machine will use + a third temporary register, which we call + t. (First the remainder will be placed in + t, then the contents of + b will be placed in + a, and finally the remainder stored in + t will be placed in + b.) + + + + We can illustrate the registers and operations required for this + machine by using the + data paths for register machinedata-path diagram + register machinedata-path diagram + data-path diagram shown in + figure. In this + diagram, the registers (a, + b, and t) are + represented by rectangles. Each way to assign a value to a register is + indicated by an arrow with an Xa buttondrawn as $\otimes$ behind the + head, pointing from the source of data to the register. + We can think of the X as a button that, when pushed,When pushed, the button allows + the value at the source to flow into the designated register. + The label next to each button is the name we will use to refer to the + button. The names are arbitrary, and can be chosen to have mnemonic value + (for example, a<-b denotes pushing the + button that assigns the contents of register b + to register a). The source of data for a + register can be another register (as in the + a<-b assignment), an operation result (as in + the t<-r assignment), or a constant + (a built-in value that cannot be changed, represented in a data-path + diagram by a triangle containing the constant). + + + + An operation that computes a value from constants and the contents + of registers is represented in a data-path diagram by a trapezoid + containing a name for the operation. For example, the box marked + rem in + figure represents an operation that + computes the remainder of the contents of the registers + a and b to which + it is attached. Arrows (without buttons) point from the input registers and + constants to the box, and arrows connect the operations output value + to registers. A test is represented by a circle containing a name for the + test. For example, our GCD machine has an operation that tests whether the + contents of register b is zero. A + test operation in register machine + register machinetest operation + test also has arrows from its input + registers and constants, but it has no output + arrows; its value is used by the controller rather than by the data + paths. Overall, the data-path diagram shows the registers and + operations that are required for the machine and how they must be + connected. If we view the arrows as wires and the + X$\otimes$ buttons as switches, the data-path diagram + is very like the wiring diagram for a machine that could be constructed + from electrical components. +
+
+ + Data paths for a GCD machine. +
+
+ + + In order for the data paths to actually compute GCDs, the buttons must + be pushed in the correct sequence. We will describe this sequence in + terms of a + register machinecontroller diagram + controller for register machinecontroller diagram + controller diagram, as illustrated in + figure. The elements of the + controller diagram indicate how the data-path components should be operated. + The rectangular boxes in the controller diagram identify data-path buttons + to be pushed, and the arrows describe the sequencing from one step to the + next. The diamond in the diagram represents a decision. One of the two + sequencing arrows will be followed, depending on the value of the data-path + test identified in the diamond. We can interpret the controller in terms + of a physical analogy: Think of the diagram as a maze in which a marble is + rolling. When the marble rolls into a box, it pushes the data-path button + that is named by the box. When the marble rolls into a decision node (such + as the test for + b$\, =0$), it leaves + the node on the path determined by the result of the indicated test. + Taken together, the data paths and the controller completely describe + a machine for computing GCDs. We start the controller (the rolling + marble) at the place marked start, after + placing numbers in registers a and + b. When the controller reaches + done, we will find the value of the GCD in + register a. + + +
+
+ + Controller for a GCD machine. +
+
+
+ + gcdregister machine for + + + + Design a register machine to compute + factorialregister machine for (iterative) + factorials using the iterative + algorithm specified by the following + + procedure. + function. + + Draw data-path and + controller diagrams for this machine. + + factorial_example + +(define (factorial n) + (define (iter product counter) + (if (> counter n) + product + (iter (* counter product) + (+ counter 1)))) + (iter 1 1)) + + +function factorial(n) { + function iter(product, counter) { + return counter > n + ? product + : iter(counter * product, + counter + 1); + } + return iter(1, 1); +} + + + + (Solution by GitHub user escolmebartlebooth) + In factorial(n), the function + iter(product, counter) is seeded by the + arguments (1, 1) and runs until + counter > n, at which point the product is + returned. Unlike gcd(a, b), the register + machine has no need of a temporary register assuming that the correct order of + register assignment is followed. + + For the data path, we see three registers: + product, + counter, and + n. There are two operations: + multiply and + plus with two assignments. + There is one test: greater than. + +
+ +
+ + For the controller, we start by filling the three registers with the desired + factorial n and the value + 1 (seeding + product and + counter). The function + iter runs and tests whether + counter > n. If + counter <= n, then + product is updated to + product * counter, followed by counter being + updated to + counter + 1 and the process is repeated until + counter > n. Once counter is > n, the product + is + factorial(n) and can be returned. + +
+ +
+
+
+ + data paths for register machine + controller for register machine + register machinedata paths + register machinecontroller + operationin register machine + + + &subsection5.1.1; + + + &subsection5.1.2; + + + &subsection5.1.3; + + + &subsection5.1.4; + + + &subsection5.1.5; + +
diff --git a/xml/cn/chapter5/section1/subsection1.xml b/xml/cn/chapter5/section1/subsection1.xml new file mode 100644 index 000000000..c92bbd58c --- /dev/null +++ b/xml/cn/chapter5/section1/subsection1.xml @@ -0,0 +1,619 @@ + + + A Language for Describing Register Machines + + + + + register machinelanguage for describing + + + Data-path and controller diagrams are adequate for representing simple + machines such as GCD, but they are unwieldy for describing large + machines such as a + + Lisp + JavaScript + + interpreter. To make it possible to deal with complex machines, we will + create a language that presents, in textual form, all the + information given by the data-path and controller + diagrams. We will start with a notation that directly + mirrors the diagrams. + + + + We define the data paths of a machine by describing the registers and + the operations. To describe a register, we give it a name + and specify the buttons that control assignment to it. We give each + of these buttons a name and specify the source of the data that enters + the register under the buttons control. (The source is a register, + a constant, or an operation.) To describe an operation, we give + it a name and specify its inputs (registers or constants). + + + + We define the controller of a machine as a sequence of + register-machine languageinstructions + instructions together with + register-machine languagelabel + labels that identify + register-machine languageentry point + entry points in the sequence. An instruction is one of the following: +
    +
  • + The name of a data-path button to push to assign a value to + a register. (This corresponds to a box in the controller diagram.) +
  • +
  • + A + test (in register machine) + register-machine languagetest + test + instruction, which performs a + specified test. +
  • +
  • + A + register-machine languagebranch + branch (in register machine) + register-machine languagelabel + label (in register machine) + conditional branch (branch instruction) + to a location indicated by a controller label, based on the result of + the previous test. (The test and branch together correspond to a + diamond in the controller diagram.) If the test is false, the + controller should continue with the next instruction in the sequence. + Otherwise, the controller should continue with the instruction after + the label. +
  • +
  • + An + + + register-machine languagegotogoto + goto (in register machine) + + + register-machine languagego_to + go_to (in register machine) + + + unconditional branch + + (goto + (go_to + + instruction) naming a controller label at which to continue execution. +
  • +
+ + + + +
+
+ + Controller for a GCD machine. +
+
+ + The machine starts at the beginning of the controller instruction + sequence and stops when execution reaches the end of the sequence. + Except when a branch changes the flow of control, instructions are + executed in the order in which they are listed. + + +
+ + +(data-paths + (registers + ((name a) + (buttons ((name a<-b) (source (register b))))) + ((name b) + (buttons ((name b<-t) (source (register t))))) + ((name t) + (buttons ((name t<-r) (source (operation rem)))))) + + (operations + ((name rem) + (inputs (register a) (register b))) + ((name =) + (inputs (register b) (constant 0))))) + +(controller + test-b ; label + (test =) ; test + (branch (label gcd-done)) ; conditional branch + (t<-r) ; button push + (a<-b) ; button push + (b<-t) ; button push + (goto (label test-b)) ; unconditional branch + gcd-done) ; label + + +data_paths( + registers( + list( + pair(name("a"), + buttons(name("a<-b"), source(register("b")))), + pair(name("b"), + buttons(name("b<-t"), source(register("t")))), + pair(name("t"), + buttons(name("t<-r"), source(operation("rem")))))), + operations( + list( + pair(name("rem"), + inputs(register("a"), register("b"))), + pair(name("="), + inputs(register("b"), constant(0)))))); + +controller( + list( + "test_b", // label + test("="), // test + branch(label("gcd_done")), // conditional branch + "t<-r", // button push + "a<-b", // button push + "b<-t", // button push + go_to(label("test_b")), // unconditional branch + "gcd_done")); // label + + + A specification of the GCD machine. + +
+
+
+ + + Figure shows the GCD machine + described in this way. This example only hints at the generality of these + descriptions, since the GCD machine is a very simple case: Each register has + only one button, and each button and test is used only once in the + controller. + + + + Unfortunately, it is difficult to read such a description. In order + to understand the controller instructions we must constantly refer + back to the definitions of the button names and the operation names, + and to understand what the buttons do we may have to refer to the + definitions of the operation names. We will thus transform our + notation to combine the information from the data-path and controller + descriptions so that we see it all together. + + + + To obtain this form of description, we will replace the arbitrary + button and operation names by the definitions of their behavior. That + is, instead of saying (in the controller) Push button + t<-r and separately saying (in the + data paths) Button t<-r assigns the + value of the rem operation to register + t and The + rem operations inputs are the contents + of registers + register-machine languageassign + assign (in register machine) + register-machine languageop + op (in register machine) + register-machine languagereg + reg (in register machine) + a and b, + we will say (in the controller) Push the button that assigns to + register t the value of the + rem operation on the contents of registers + a and b. + Similarly, instead of saying (in the controller) Perform the + = test and separately saying (in the + data paths) The = test operates on the + contents of register b and the + constant 0, we will say Perform the + = test on the + register-machine languageconstant + constant (in register machine) + contents of register b and the + constant 0. We will omit the data-path description, leaving only + the controller sequence. Thus, the GCD machine is described as follows: + + + + gcd_controller_example + controller + make_machine + start + gcd_controller_declaration + +; + +const gcd_machine = + make_machine( + list("a", "b", "t"), + list(list("rem", (a, b) => a % b), + list("=", (a, b) => a === b)), + controller_sequence(gcd_controller)); + +set_register_contents(gcd_machine, "a", 206); +set_register_contents(gcd_machine, "b", 40); +start(gcd_machine); +get_register_contents(gcd_machine, "a"); + + + + gcd_controller_declaration + +const gcd_controller = + + + + gcd_controller_construction + gcd_controller_example + 2 + +(controller + test-b + (test (op =) (reg b) (const 0)) + (branch (label gcd-done)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label test-b)) + gcd-done) + + +controller( + list( + "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")) + + + + controller + +function controller(sequence) { + return list("controller", sequence); +} +function controller_sequence(controller) { + return head(tail(controller)); +} + + + + + + This form of description is easier to read than the kind illustrated + in figure, but it also has disadvantages: +
    +
  • It is more verbose for large machines, + because complete descriptions of the data-path elements are repeated + whenever the elements are mentioned in the controller instruction + sequence. (This is not a problem in the GCD example, because each + operation and button is used only once.) Moreover, repeating the + data-path descriptions obscures the actual data-path structure of the + machine; it is not obvious for a large machine how many registers, + operations, and buttons there are and how they are interconnected. +
  • +
  • + Because the controller instructions in a machine definition look like + + Lisp + JavaScript + + expressions, it is easy to forget that they are + not arbitrary + + Lisp + JavaScript + + expressions. They can notate only legal machine operations. For + example, operations can operate directly only on constants and the + contents of registers, not on the results of other operations. +
  • +
+ + In spite of these disadvantages, we will use this register-machine + language throughout this chapter, because we will be more concerned with + understanding controllers than with understanding the elements and + connections in data paths. We should keep in mind, + however, that data-path design is crucial in designing real machines. +
+ + + Use the register-machine language to describe the + factorialregister machine for (iterative) + iterative factorial + machine of exercise. + + + + + + Actions + + + actions, in register machine + register machineactions + + + Let us modify the GCD machine so that we can type in the numbers + whose GCD we want and get the answer + + printed at our terminal. + printed. + + We will not discuss how to make a machine that can read and print, + but will assume (as we do when we use + + read + prompt + + and display in + + Scheme) + JavaScript) + + that they are available as primitive + operations.This assumption glosses over a + great deal of complexity. Usually a large portion of the implementation of + a Lisp system is dedicated to making reading + and printing work. + This assumption glosses over a great deal of complexity. Implementation of reading + and printing requires significant effort, for example to handle character encodings + for different languages. + + + + + + + + Read + + The operation + prompt operation in register machine + prompt + + + is like the operations we have been using in that it produces a value that + can be stored in a register. But + + read + + + prompt + + + does not take inputs from any registers; its value depends on + something that happens outside the parts of the machine we are + designing. We will allow our machines operations to have such + behavior, and thus will draw and notate the use of + + read + + + prompt + + + just as we do any other operation that computes a value. + + + + + +
+ + +(data-paths + (registers + ((name a) + (buttons ((name a<-b) (source (register b))))) + ((name b) + (buttons ((name b<-t) (source (register t))))) + ((name t) + (buttons ((name t<-r) (source (operation rem)))))) + + (operations + ((name rem) + (inputs (register a) (register b))) + ((name =) + (inputs (register b) (constant 0))))) + +(controller + test-b ; label + (test =) ; test + (branch (label gcd-done)) ; conditional branch + (t<-r) ; button push + (a<-b) ; button push + (b<-t) ; button push + (goto (label test-b)) ; unconditional branch + gcd-done) ; label + + +data_paths( + registers( + list( + pair(name("a"), + buttons(name("a<-b"), source(register("b")))), + pair(name("b"), + buttons(name("b<-t"), source(register("t")))), + pair(name("t"), + buttons(name("t<-r"), source(operation("rem")))))), + operations( + list( + pair(name("rem"), + inputs(register("a"), register("b"))), + pair(name("="), + inputs(register("b"), constant(0)))))); + +controller( + list( + "test_b", // label + test("="), // test + branch(label("gcd_done")), // conditional branch + "t<-r", // button push + "a<-b", // button push + "b<-t", // button push + go_to(label("test_b")), // unconditional branch + "gcd_done")); // label + + + A specification of the GCD machine. + +
+
+ + + Print, + + The operation + display operation in register machine + display, + + + on the other hand, differs from the operations we have + been using in a fundamental way: It does not produce an output value + to be stored in a register. Though it has an effect, this effect is + not on a part of the machine we are designing. We will refer to this + kind of operation as an action. We will represent an action in + a data-path diagram just as we represent an operation that computes a + valueas a trapezoid that contains the name of the action. + Arrows point to the action box from any inputs (registers or + constants). We also associate a button with the action. Pushing the + button makes the action happen. To make a controller push an action + button we use a new kind of instruction called + register-machine languageperform + perform (in register machine) + perform. Thus, + the action of printing + the contents of register + a is represented + in a controller sequence by the instruction + + +(perform (op print) (reg a)) + + +perform(list(op("display"), reg("a"))) + + + + + + + + Figure + + + Figure + + + shows the data paths and controller for + the new GCD machine. Instead of having the machine stop after printing + the answer, we have made it start over, so that it repeatedly + reads a pair of numbers, computes their GCD, and prints + the result. + This structure is like the driver loops we used in the interpreters of + chapter. + + + +
+
+ + +(controller + gcd-loop + (assign a (op read)) + (assign b (op read)) + test-b + (test (op =) (reg b) (const 0)) + (branch (label gcd-done)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label test-b)) + gcd-done + (perform (op print) (reg a)) + (goto (label gcd-loop))) + + + A GCD machine that reads inputs and prints + results. + +
+
+ +
+
+ + gcd_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. + +
+
+
+
+ + register machinelanguage for describing + actions, in register machine + register machineactions + + +
diff --git a/xml/cn/chapter5/section1/subsection2.xml b/xml/cn/chapter5/section1/subsection2.xml new file mode 100644 index 000000000..528fe85c6 --- /dev/null +++ b/xml/cn/chapter5/section1/subsection2.xml @@ -0,0 +1,216 @@ + + + Abstraction in Machine Design + + + abstractionregisterin register-machine design + + + We will often define a machine to include primitive + operations that are actually very complex. For example, in + sections and + we will treat + + Schemes + JavaScripts + + environment manipulations as primitive. Such abstraction is valuable + because it allows us to ignore the details of parts of a machine so that we + can concentrate on other aspects of the design. The fact that we have + swept a lot of complexity under the rug, however, does not mean that a + machine design is unrealistic. We can always replace the complex + primitives by simpler primitive operations. + + + + Consider the GCD machine. The machine has an instruction that computes + the remainder of the contents of registers a + and b and assigns the result to register + t. If we want to construct the GCD machine + without using a primitive remainder operation, we must specify how to + compute remainders in terms of simpler operations, such as subtraction. + Indeed, we can write a + + Scheme procedure + JavaScript function + + that finds remainders in this way: + + remainder_example + +remainder(29, 5); + + + + remainder + remainder_example + +(define (remainder n d) + (if (< n d) + n + (remainder (- n d) d))) + + +function remainder(n, d) { + return n < d + ? n + : remainder(n - d, d); +} + + + + We can thus replace the remainder operation in the GCD machines + data paths with a subtraction operation and a comparison test. + Figure shows the data paths and + controller for the elaborated machine. The instruction +
+
+ + + Data paths and controller for the elaborated GCD machine. + +
+ + +(assign t (op rem) (reg a) (reg b)) + + +assign("t", list(op("rem"), reg("a"), reg("b"))) + + + in the GCD controller definition is replaced by a sequence of + instructions that contains a loop, as shown in + figure. + +
+ + gcd_elaborated + gcd_elaborated_example + 2 + +(controller + test-b + (test (op =) (reg b) (const 0)) + (branch (label gcd-done)) + (assign t (reg a)) + rem-loop + (test (op <) (reg t) (reg b)) + (branch (label rem-done)) + (assign t (op -) (reg t) (reg b)) + (goto (label rem-loop)) + rem-done + (assign a (reg b)) + (assign b (reg t)) + (goto (label test-b)) + gcd-done) + + +controller( + list( + "test_b", + test(list(op("="), reg("b"), constant(0))), + branch(label("gcd_done")), + assign("t", reg("a")), + "rem_loop", + test(list(op("<"), reg("t"), reg("b"))), + branch(label("rem_done")), + assign("t", list(op("-"), reg("t"), reg("b"))), + go_to(label("rem_loop")), + "rem_done", + assign("a", reg("b")), + assign("b", reg("t")), + go_to(label("test_b")), + "gcd_done")) + + + + gcd_elaborated_example + controller + make_machine + start + gcd_elaborated_declaration + +; + +const gcd_elaborated_machine = + make_machine( + list("a", "b", "t"), + list(list("=", (a, b) => a === b), + list("<", (a, b) => a < b), + list("-", (a, b) => a - b)), + controller_sequence(gcd_elaborated_controller)); + +set_register_contents(gcd_elaborated_machine, "a", 206); +set_register_contents(gcd_elaborated_machine, "b", 40); +start(gcd_elaborated_machine); +get_register_contents(gcd_elaborated_machine, "a"); + + + + gcd_elaborated_declaration + +const gcd_elaborated_controller = + + + + Controller instruction sequence for the GCD machine in + figure. + +
+
+ + + Design a machine to compute + sqrtregister machine for + square roots using Newtons method, as + described in section and implemented with the following code in section: + + square_definition + average_definition + sqrt_example_2 + 2.2360688956433634 + +(define (sqrt x) + (define (good-enough? guess) + (< (abs (- (square guess) x)) 0.001)) + (define (improve guess) + (average guess (/ x guess))) + (define (sqrt-iter guess) + (if (good-enough? guess) + guess + (sqrt-iter (improve guess)))) + (sqrt-iter 1.0)) + + +function sqrt(x) { + function is_good_enough(guess) { + return math_abs(square(guess) - x) < 0.001; + } + function improve(guess) { + return average(guess, x / guess); + } + function sqrt_iter(guess) { + return is_good_enough(guess) + ? guess + : sqrt_iter(improve(guess)); + } + return sqrt_iter(1); +} + + + Begin by assuming that + + good-enough? + is_good_enough + + and improve operations are available as + primitives. Then show how to expand these in terms of arithmetic + operations. Describe each version of the sqrt + machine design by drawing a data-path diagram and writing a controller + definition in the register-machine language. + + + + abstractionregisterin register-machine design + +
diff --git a/xml/cn/chapter5/section1/subsection3.xml b/xml/cn/chapter5/section1/subsection3.xml new file mode 100644 index 000000000..208742804 --- /dev/null +++ b/xml/cn/chapter5/section1/subsection3.xml @@ -0,0 +1,357 @@ + + + Subroutines + + + + + register machinesubroutine + subroutine in register machine + + + When designing a machine to perform a computation, we would often + prefer to arrange for components to be shared by different parts of + the computation rather than duplicate the components. Consider a + machine that includes two GCD computationsone that finds the GCD of + the contents of registers a and + b and one that finds the + GCD of the contents of registers c and + d. We might start + by assuming we have a primitive gcd operation, + then expand the two instances of gcd in terms + of more primitive operations. + + + Figure + + + Figure + + + shows just the GCD portions of the resulting machines data paths, + without showing how they connect to the rest of the machine. The figure + also shows the corresponding portions of the machines controller + sequence. + + +
+ Portions of the data paths and controller sequence for + a machine with two GCD computations. + +
+
+ +
+ Portions of the data paths and controller sequence for + a machine with two GCD computations. + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + This machine has two remainder operation boxes and two boxes for + testing equality. If the duplicated components are complicated, as is the + remainder box, this will not be an economical way to build the + machine. We can avoid duplicating the data-path components by using + the same components for both GCD computations, provided that doing so + will not affect the rest of the larger machines computation. If the + values in registers a and + b are not needed by the time the + controller gets to gcd-2gcd_2 (or if these values + can be moved to other registers for safekeeping), we can change the machine + so that it uses registers a and + b, rather than registers + c and d, in + computing the second GCD as well as the first. If we do this, we obtain the + controller sequence shown in + figure. + + + + We have removed the duplicate data-path components (so that the data paths + are again as in figure), but the + controller now has two GCD sequences that differ only in their entry-point + labels. It would be better to replace these two sequences by branches to a + single sequencea gcd + subroutineat the end of which we branch back to the + correct place in the main instruction sequence. We can accomplish this as + follows: Before branching to gcd, we place a + distinguishing value (such as 0 or1) into a special register, + continue register + continue. At the end of the + gcd subroutine we return either to + after-gcd-1after_gcd_1 or to after-gcd-2after_gcd_2, depending + on the value of the continue register. + Figure shows the relevant portion + of the resulting controller sequence, which includes only a single copy of + the gcd instructions. +
+ + +gcd-1 + (test (op =) (reg b) (const 0)) + (branch (label after-gcd-1)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label gcd-1)) +after-gcd-1 + $\vdots$ +gcd-2 + (test (op =) (reg b) (const 0)) + (branch (label after-gcd-2)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label gcd-2)) +after-gcd-2 + + +"gcd_1", + test(list(op("="), reg("b"), constant(0))), + branch(label("after_gcd_1")), + assign("t", list(op("rem"), reg("a"), reg("b"))), + assign("a", reg("b")), + assign("b", reg("t")), + go_to(label("gcd_1")), +"after_gcd_1", + $\vdots$ +"gcd_2", + test(list(op("="), reg("b"), constant(0))), + branch(label("after_gcd_2")), + assign("t", list(op("rem"), reg("a"), reg("b"))), + assign("a", reg("b")), + assign("b", reg("t")), + go_to(label("gcd_2")), +"after_gcd_2" + + + Portions of the controller sequence for a machine that + uses the same data-path components for two different GCD + computations. + +
+
+ + +gcd + (test (op =) (reg b) (const 0)) + (branch (label gcd-done)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label gcd)) +gcd-done + (test (op =) (reg continue) (const 0)) + (branch (label after-gcd-1)) + (goto (label after-gcd-2)) + $\vdots$ + ;; Before branching to gcd from the first place where + ;; it is needed, we place $0$ in the continue register + (assign continue (const 0)) + (goto (label gcd)) +after-gcd-1 + $\vdots$ + ;; Before the second use of gcd, we place $1$ in the continue register + (assign continue (const 1)) + (goto (label gcd)) +after-gcd-2 + + +"gcd", + 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("gcd")), +"gcd_done", + test(list(op("="), reg("continue"), constant(0))), + branch(label("after_gcd_1")), + go_to(label("after_gcd_2")), + $\vdots$ + // Before branching to $\texttt{gcd}$ from the first place where + // it is needed, we place 0 in the $\texttt{continue}$ register + assign("continue", constant(0)), + go_to(label("gcd")), +"after_gcd_1", + $\vdots$ + // Before the second use of $\texttt{gcd}$, we place 1 in the $\texttt{continue}$ register + assign("continue", constant(1)), + go_to(label("gcd")), +"after_gcd_2" + + + + Using a continue register to avoid + the duplicate controller sequence in + figure. + + +
+
+ + + This is a reasonable approach for handling small problems, but it would be + awkward if there were many instances of GCD computations in the controller + sequence. To decide where to continue executing after the + gcd subroutine, we would need tests in the data + paths and branch instructions in the controller for all the places that use + gcd. A more powerful method for implementing + subroutines is to have the continue register + hold the label of the entry point in the controller sequence at which + execution should continue when the subroutine is finished. Implementing this + strategy requires a new kind of connection between the data paths and the + controller of a register machine: There must be a way to assign to a + register a label in the controller sequence in such a way that this value + can be fetched from the register and used to continue execution at the + designated entry point. + + + + To reflect this ability, we will extend the + assign (in register machine)storing label in register + assign + instruction of the register-machine language to allow a register to be + assigned as value a label from the controller sequence (as a special + kind of constant). We will also extend the + + goto + + go_to (in register machine)destination in register + go_to + + + instruction to allow execution to continue at the entry point described by + the contents of a register rather than only at an entry point described by + a constant label. Using these new constructs we can terminate the + gcd subroutine with a branch to the location + stored in the continue register. This leads + to the controller sequence shown in + figure. + +
+ + +gcd + (test (op =) (reg b) (const 0)) + (branch (label gcd-done)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label gcd)) +gcd-done + (goto (reg continue)) + $\vdots$ + ;; Before calling gcd, we assign to continue + ;; the label to which gcd should return. + (assign continue (label after-gcd-1)) + (goto (label gcd)) +after-gcd-1 + $\vdots$ + ;; Here is the second call to gcd, with a different continuation. + (assign continue (label after-gcd-2)) + (goto (label gcd)) +after-gcd-2 + + +"gcd", + 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("gcd")), +"gcd_done", + go_to(reg("continue")), + $\vdots$ + // Before calling $\texttt{gcd}$, we assign to $\texttt{continue}$ + // the label to which $\texttt{gcd}$ should return. + assign("continue", label("after_gcd_1"))), + go_to(label("gcd")), +"after_gcd_1", + $\vdots$ + // Here is the second call to $\texttt{gcd}$, with a different continuation. + assign("continue", label("after_gcd_2")), + go_to(label("gcd")), +"after_gcd_2" + + + + Assigning labels to the continue register + simplifies and generalizes the strategy shown in + figure. + + +
+ + + A machine with more than one subroutine could use multiple + continuation registers (e.g., gcd-continuegcd_continue, + factorial-continuefactorial_continue) or we could have all + subroutines share a single + continue register. Sharing is more economical, + but we must be careful if we have a subroutine + (sub1) that calls another subroutine + (sub2). Unless + sub1 saves the contents of + continue in some other register before setting + up continue for the call to + sub2, sub1 will + not know where to go when it is finished. The mechanism developed in the + next section to handle recursion also provides a better solution to this + problem of nested subroutine calls. + + + register machinesubroutine + subroutine in register machine + + +
diff --git a/xml/cn/chapter5/section1/subsection4.xml b/xml/cn/chapter5/section1/subsection4.xml new file mode 100644 index 000000000..b4b008481 --- /dev/null +++ b/xml/cn/chapter5/section1/subsection4.xml @@ -0,0 +1,668 @@ + + + Using a Stack to Implement Recursion + + + + + stackrecursionfor recursion in register machine + register machinestack + recursive processregister machine for + + + With the ideas illustrated so far, we can implement any + iterative processregister machine for + iterative + process by specifying a register machine that has a register + corresponding to each state variable of the process. The machine + repeatedly executes a controller loop, changing the contents + of the registers, until some termination condition is satisfied. At + each point in the controller sequence, the state of the machine + (representing the state of the iterative process) is completely + determined by the contents of the registers (the values of the state + variables). + + + + Implementing + recursive processiterative process vs. + iterative processrecursive process vs. + factorialregister machine for (recursive) + recursive processes, however, requires an additional + mechanism. Consider the following recursive method for computing + factorials, which we first examined in + section: + + factorial_5_1_4 + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n))) + + +function factorial(n) { + return n === 1 + ? 1 + : n * factorial(n - 1); +} + + + + As we see from the + + procedure, + function, + + computing $n!$ requires computing + $(n-1)!$. Our GCD machine, modeled on the + + procedure + function + + + gcd_5_1_4_example + +(gcd 20 12) + + +gcd(20, 12); + + + + gcd_5_1_4 + gcd_5_1_4_example + 4 + +(define (gcd a b) + (if (= b 0) + a + (gcd b (remainder a b)))) + + +function gcd(a, b) { + return b === 0 ? a : gcd(b, a % b); +} + + + + similarly had to compute another GCD. But there is an important + difference between the gcd + + procedure, + function, + + which reduces the original computation to a new GCD computation, and + factorial, which requires computing another + factorial as a subproblem. In GCD, the answer to the new GCD computation is + the answer to the original problem. To compute the next GCD, we simply + place the new arguments in the input registers of the GCD machine and reuse + the machines data paths by executing the same controller sequence. + When the machine is finished solving the final GCD problem, it has completed + the entire computation. + + + + In the case of factorial (or any recursive process) the answer to the + new factorial subproblem is not the answer to the original problem. + The value obtained for $(n-1)!$ must be + multiplied by $n$ to get the final answer. If + we try to imitate the GCD design, and solve the factorial subproblem by + decrementing the n register and rerunning the + factorial machine, we will no longer have available the old value of + n by which to multiply the result. We thus + need a second factorial machine to work on the subproblem. This second + factorial computation itself has a factorial subproblem, which + requires a third factorial machine, and so on. Since each factorial + machine contains another factorial machine within it, the total + machine contains an infinite nest of similar machines and hence cannot + be constructed from a fixed, finite number of parts. + + + + Nevertheless, we can implement the factorial process as a register + machine if we can arrange to use the same components for each nested + instance of the machine. Specifically, the machine that computes + $n!$ + should use the same components to work on the subproblem of computing + $(n-1)!$, on the subproblem for + $(n-2)!$, and so on. This is + plausible because, although the factorial process dictates that an + unbounded number of copies of the same machine are needed to perform a + computation, only one of these copies needs to be active at any given + time. When the machine encounters a recursive subproblem, it can + suspend work on the main problem, reuse the same physical parts to + work on the subproblem, then continue the suspended computation. + + + + In the subproblem, the contents of the registers will be different + than they were in the main problem. (In this case the + n register is decremented.) In order to be + able to continue the suspended computation, the machine must save the + contents of any registers that will be needed after the subproblem is + solved so that these can be restored to continue the suspended computation. + In the case of factorial, we will save the old value of + n, to be restored when we are finished + computing the factorial of the decremented n + register.One might argue that we dont need to save the old + n; after we decrement it and solve the + subproblem, we could simply increment it to recover the old value. Although + this strategy works for factorial, it cannot work in general, since the old + value of a register cannot always be computed from the new one. + + + + + Since there is no a priori limit on the depth of nested + recursive calls, we may need to save an arbitrary number of register + values. These values must be restored in the reverse of the order in + which they were saved, since in a nest of recursions the last + subproblem to be entered is the first to be finished. This dictates + the use of a stack, or last in, first out data + structure, to save register values. We can extend the register-machine + language to include a stack by adding two kinds of instructions: Values are + placed + on the stack using a + register-machine languagesave + save (in register machine) + save instruction and + restored from the stack using a + register-machine languagerestore + restore (in register machine) + restore + instruction. After a sequence of values has been + saved on the stack, a sequence of + restores will retrieve these values in reverse + order.In section we + will see how to implement a stack in terms of more primitive + operations. + + + + With the aid of the stack, we can reuse a single copy of the factorial + machines data paths for each factorial subproblem. There is a + similar design issue in reusing the controller sequence that operates + the data paths. To reexecute the factorial computation, the + controller cannot simply loop back to the beginning, as with + an iterative process, because after solving the + $(n-1)!$ subproblem + the machine must still multiply the result by + $n$. The controller + must suspend its computation of $n!$, solve the + $(n-1)!$ subproblem, + then continue its computation of $n!$. This + view of the factorial computation suggests the use of the subroutine + mechanism described in section, which + has the controller use a + continue registerrecursion and + continue register to transfer to the part of + the sequence that solves a subproblem and then continue where it left off on + the main problem. We can thus make a factorial subroutine that returns to + the entry point stored in the continue + register. Around each subroutine call, we save and restore + continue just as we do the + n register, since each level of + the factorial computation will use the same + continue register. That is, the factorial + subroutine must put a new value in continue + when it calls itself for a subproblem, but it will need the old value in + order to return to the place that called it to solve a subproblem. + + +
+
+\flushleft + + factorial_recursive_controller + factorial_recursive_example + 24 + +controller( + list( + assign("continue", label("fact_done")), // set up final return address + "fact_loop", + test(list(op("="), reg("n"), constant(1))), + branch(label("base_case")), + // Set up for recursive call by saving $\texttt{n}$ and $\texttt{continue}$. + // Set up $\texttt{continue}$ so that the computation will continue + // at $\texttt{after_fact}$ when the subroutine returns. + save("continue"), + save("n"), + assign("n", list(op("-"), reg("n"), constant(1))), + assign("continue", label("after_fact")), + go_to(label("fact_loop")), + "after_fact", + restore("n"), + restore("continue"), + assign("val", // $\texttt{val}$ now contains $n(n-1)!$ + list(op("*"), reg("n"), reg("val"))), + go_to(reg("continue")), // return to caller + "base_case", + assign("val", constant(1)), // base case: 1! = 1 + go_to(reg("continue")), // return to caller + "fact_done")) + + + + factorial_recursive_example + controller + make_machine + start + factorial_recursive_declaration + +; + +const factorial_recursive_machine = + make_machine( + list("n", "val", "continue"), + list(list("=", (a, b) => a === b), + list("*", (a, b) => a * b), + list("-", (a, b) => a - b)), + controller_sequence(factorial_recursive_controller)); + +set_register_contents(factorial_recursive_machine, "n", 4); +start(factorial_recursive_machine); +get_register_contents(factorial_recursive_machine, "val"); + + + + factorial_recursive_declaration + +const factorial_recursive_controller = + + + + A recursive + factorialregister machine for (recursive) + factorial machine. + + +
+ + + + Figure + + + Figure + + + shows the data paths and controller for + a machine that implements the recursive + factorial + + procedure. + function. + + The machine has a stack and three registers, called + n, val, and + continue. To simplify the data-path diagram, + we have not named the register-assignment buttons, only the stack-operation + buttons (sc and sn + to save registers, rc and + rn to restore registers). To operate the + machine, we put in register n the number whose + factorial we wish to compute and start the machine. When the machine + reaches fact-donefact_done, the computation is finished + and the answer will be found in the val + register. In the controller sequence, n and + continue are saved before each recursive call + and restored upon return from the call. Returning from a call is + accomplished by branching to the location stored in + continue. + + Continue + + The register + continue + + + is initialized when the machine starts so that the last return will go to + fact-donefact_done. The + val + register, which holds the result of the factorial computation, is not + saved before the recursive call, because the old contents of + val is not useful after the subroutine returns. + Only the new value, which is the value produced by the subcomputation, is + needed. + + + factorialregister machine for (recursive) + + + Although in principle the factorial computation requires an infinite + machine, the machine in + + + figure + + + figure + + + is actually finite except for the stack, which is potentially unbounded. Any + particular physical implementation of a stack, however, will be of finite + size, and this will limit the depth of recursive calls that can be handled + by the machine. This implementation of factorial illustrates the general + strategy for realizing recursive algorithms as ordinary register machines + augmented by stacks. When a recursive subproblem is encountered, we save on + the stack the registers whose current values will be required after the + subproblem is solved, solve the recursive subproblem, then restore the saved + registers and continue execution on the main problem. The + continue register must always be saved. + Whether there are other registers that need to be saved depends on the + particular machine, since not all recursive computations need the original + values of registers that are modified during solution of the subproblem + (see exercise). + + + + A double recursion + + + + Let us examine a more complex recursive process, the tree-recursive + computation of the + fibregister machine for (tree-recursive) + Fibonacci numbers, which we introduced in + section: + + fib_5_1_4_example + +(fib 6) + + +fib(6); + + + + fib_5_1_4 + fib_5_1_4_example + 8 + +(define (fib n) + (if (< n 2) + n + (+ (fib (- n 1)) (fib (- n 2))))) + + +function fib(n) { + return n === 0 + ? 0 + : n === 1 + ? 1 + : fib(n - 1) + fib(n - 2); +} + + + + Just as with factorial, we can implement the recursive Fibonacci + computation as a register machine with registers + n, val, + and continue. The machine is more complex than + the one for factorial, because there are two places in the controller + sequence where we need to perform recursive callsonce to compute + Fib$(n-1)$ and once to compute + Fib$(n-2)$. To set up for each of these calls, + we save the registers whose values will be needed later, set the + n + register to the number whose Fib we need to compute recursively + ($n-1$ or $n-2$), and + assign to continue the entry point in the main + sequence to which to return (afterfib-n-1afterfib_n_1 or + afterfib-n-2afterfib_n_2, respectively). We then go to + fib-loopfib_loop. When we return from the + recursive call, the answer is in val. + Figure shows the controller sequence + for this machine. + + + +
+ + +(controller + (assign continue (label fact-done)) ; set up final return address + fact-loop + (test (op =) (reg n) (const 1)) + (branch (label base-case)) + ;; Set up for the recursive call by saving n and continue. + ;; Set up continue so that the computation will continue + ;; at after-fact when the subroutine returns. + (save continue) + (save n) + (assign n (op -) (reg n) (const 1)) + (assign continue (label after-fact)) + (goto (label fact-loop)) + after-fact + (restore n) + (restore continue) + (assign val (op *) (reg n) (reg val)) ; val now contains $n(n-1)!$ + (goto (reg continue)) ; return to caller + base-case + (assign val (const 1)) ; base case: $1!=1$ + (goto (reg continue)) ; return to caller + fact-done) + + + A recursive + factorialregister machine for (recursive) + factorial machine. + +
+
+ + +
+ +
+ + fib_recursive_controller + fib_recursive_example + 8 + +(controller + (assign continue (label fib-done)) + fib-loop + (test (op <) (reg n) (const 2)) + (branch (label immediate-answer)) + ;; set up to compute ${\textrm{Fib}}(n-1)$ + (save continue) + (assign continue (label afterfib-n-1)) + (save n) ; save old value of $n$ + (assign n (op -) (reg n) (const 1)) ; clobber $n$ to $n-1$ + (goto (label fib-loop)) ; perform recursive call + afterfib-n-1 ; upon return, val contains ${\textrm{Fib}}(n-1)$ + (restore n) + (restore continue) + ;; set up to compute ${\textrm{Fib}}(n-2)$ + (assign n (op -) (reg n) (const 2)) + (save continue) + (assign continue (label afterfib-n-2)) + (save val) ; save ${\textrm{Fib}}(n-1)$ + (goto (label fib-loop)) + afterfib-n-2 ; upon return, val contains ${\textrm{Fib}}(n-2)$ + (assign n (reg val)) ; $n$ now contains ${\textrm{Fib}}(n-2)$ + (restore val) ; val now contains ${\textrm{Fib}}(n-1)$ + (restore continue) + (assign val ; ${\textrm{Fib}}(n-1)+{\textrm{Fib}}(n-2)$ + (op +) (reg val) (reg n)) + (goto (reg continue)) ; return to caller, answer is in val + immediate-answer + (assign val (reg n)) ; base case: ${\textrm{Fib}}(n)=n$ + (goto (reg continue)) + fib-done) + + +controller( + list( + assign("continue", label("fib_done")), + "fib_loop", + test(list(op("<"), reg("n"), constant(2))), + branch(label("immediate_answer")), + // set up to compute $\textrm{Fib}(n-1)$ + save("continue"), + assign("continue", label("afterfib_n_1")), + save("n"), // save old value of $\texttt{n}$ + assign("n", list(op("-"), reg("n"), constant(1))), // clobber $\texttt{n}$ to $n-1$ + go_to(label("fib_loop")), // perform recursive call + "afterfib_n_1", // upon return, $\texttt{val}$ contains $\textrm{Fib}(n-1)$ + restore("n"), + restore("continue"), + // set up to compute $\textrm{Fib}(n-2)$ + assign("n", list(op("-"), reg("n"), constant(2))), + save("continue"), + assign("continue", label("afterfib_n_2")), + save("val"), // save $\textrm{Fib}(n-1)$ + go_to(label("fib_loop")), + "afterfib_n_2", // upon return, $\texttt{val}$ contains $\textrm{Fib}(n-2)$ + assign("n", reg("val")), // $\texttt{n}$ now contains $\textrm{Fib}(n-2)$ + restore("val"), // $\texttt{val}$ now contains $\textrm{Fib}(n-1)$ + restore("continue"), + assign("val", // $\textrm{Fib}(n-1) + \textrm{Fib}(n-2)$ + list(op("+"), reg("val"), reg("n"))), + go_to(reg("continue")), // return to caller, answer in $\texttt{val}$ + "immediate_answer", + assign("val", reg("n")), // base case: $\textrm{Fib}(n) = n$ + go_to(reg("continue")), + "fib_done")) + + + + + fib_recursive_example + controller + make_machine + start + fib_recursive_declaration + +; + +const fib_recursive_machine = + make_machine( + list("n", "val", "continue"), + list(list("<", (a, b) => a < b), + list("-", (a, b) => a - b), + list("+", (a, b) => a + b)), + controller_sequence(fib_recursive_controller)); + +set_register_contents(fib_recursive_machine, "n", 6); +start(fib_recursive_machine); +get_register_contents(fib_recursive_machine, "val"); + + + + fib_recursive_declaration + +const fib_recursive_controller = + + + + + Controller for a machine to compute + fibregister machine for (tree-recursive) + Fibonacci numbers. + + +
+
+ + + + Specify register machines that implement each of the following + + procedures. + functions. + + For each machine, write a controller instruction sequence + and draw a diagram showing the data paths. +
    +
  1. + Recursive exponentiation: + + exptregister machine for + expt_5_1_4 + expt_5_1_4_example + 81 + +(define (expt b n) + (if (= n 0) + 1 + (* b (expt b (- n 1))))) + + +function expt(b, n) { + return n === 0 + ? 1 + : b * expt(b, n - 1); +} + + +
  2. +
  3. + Iterative exponentiation: + + expt_5_1_4_example + +(expt 3 4) + + +expt(3, 4); + + + + expt_iterative_5_1_4 + expt_5_1_4_example + 81 + +(define (expt b n) + (define (expt-iter counter product) + (if (= counter 0) + product + (expt-iter (- counter 1) (* b product)))) + (expt-iter n 1)) + + +function expt(b, n) { + function expt_iter(counter, product) { + return counter === 0 + ? product + : expt_iter(counter - 1, b * product); + } + return expt_iter(n, 1); +} + + +
  4. +
+
+ + + + Hand-simulate the factorial and Fibonacci machines, using some + nontrivial input (requiring execution of at least one recursive call). + Show the contents of the stack at each significant point in the + execution. + + + + + Ben Bitdiddle observes that the Fibonacci machines controller sequence + has an extra save and an extra + restore, which can be removed to make a faster + machine. Where are these instructions? + + + + + stackrecursionfor recursion in register machine + register machinestack + recursive processregister machine for + +
diff --git a/xml/cn/chapter5/section1/subsection5.xml b/xml/cn/chapter5/section1/subsection5.xml new file mode 100644 index 000000000..cfa7d8194 --- /dev/null +++ b/xml/cn/chapter5/section1/subsection5.xml @@ -0,0 +1,173 @@ + + + Instruction Summary + + + + + register-machine languageinstructions + register-machine language + + + A controller instruction in our register-machine language + has one of the following forms, where each + input$_i$ is + + + either + + + register-machine languagereg + register-machine languageconstant + + (reg register-name) + + reg(register-name) + + + or + + (const constant-value). + + constant(constant-value). + + + + + + These instructions were introduced in + section: + + + register-machine languagegoto + + + register-machine languagego_to + + + + register-machine languageassign + register-machine languageop + register-machine languageperform + register-machine languagetest + register-machine languagebranch + register-machine languagelabel + +(assign $register-name$ (reg $register-name$)) + +(assign $register-name$ (const $constant-value$)) + +(assign $register-name$ (op $operation-name$) $input_{1}$ $\ldots$ $input_{n}$) + +(perform (op $operation-name$) $input_{1}$ $\ldots$ $input_{n}$) + +(test (op $operation-name$) $input_{1}$ $\ldots$ $input_{n}$) + +(branch (label $label-name$)) + +(goto (label $label-name$)) + + +assign(register-name, reg(register-name)) + +assign(register-name, constant(constant-value)) + +assign(register-name, list(op(operation-name), input$_1$, $\ldots$, input$_n$)) + +perform(list(op(operation-name), input$_1$, $\ldots$, input$_n$)) + +test(list(op(operation-name), input$_1$, $\ldots$, input$_n$)) + +branch(label(label-name)) + +go_to(label(label-name)) + + + + + + The use of registers to hold labels was introduced in + section: + + +(assign $register-name$ (label $label-name$)) + +(goto (reg $register-name$)) + + +assign(register-name, label(label-name)) + +go_to(reg(register-name)) + + + + + + Instructions to use the stack were introduced in + section: + + register-machine languagesave + register-machine languagerestore + +(save $register-name$) + +(restore $register-name$) + + +save(register-name) + +restore(register-name) + + + + + + The only kind of + register-machine languageconstant + constant (in register machine)syntax of + + + $\langle constant-value \rangle$ + + + constant-value + + + we have seen so far is a number, but later we will + + use strings, symbols, + also use strings + + and lists. + + + For example, + (const "abc") is the string + "abc", + (const abc) is the symbol + abc, + (const (a b c)) + is the list + (a b c), + and + (const ()) + is the empty list. + + + For example, + constant("abc") + is the string "abc", + constant(null) + is the empty list, and + constant(list("a", "b", "c")) + is the list + list("a", "b", "c"). + + + + + register machinedesign of + register-machine language + + + diff --git a/xml/cn/chapter5/section2/section2.xml b/xml/cn/chapter5/section2/section2.xml new file mode 100644 index 000000000..0c60d4007 --- /dev/null +++ b/xml/cn/chapter5/section2/section2.xml @@ -0,0 +1,292 @@ +
+ + A Register-Machine Simulator + + + + + + register machinesimulator + register-machine simulator + simulationregisterof register machine + + + In order to gain a good understanding of the design of register machines, + we must test the machines we design to see if they perform as expected. + One way to test a design is to hand-simulate the operation of the + controller, as in exercise. But this is + extremely tedious for all but the simplest machines. In this section we + construct a simulator for machines described in the register-machine + language. The simulator is a + + Scheme + JavaScript + + program with + four interface + + procedures. + functions. + + The first uses a description of a register + machine to construct a model of the machine (a data structure whose + parts correspond to the parts of the machine to be simulated), and the + other three allow us to simulate the machine by manipulating the + model: +
    +
  • + + (make-machine register-names operations controller) + + make_machine(register-names, operations, controller) + + + make_machine +

    + constructs and returns a model of the machine with the given + registers, operations, and controller. +
  • +
  • + + (set_register_contents machine-model register-name value) + + set_register_contents(machine-model, register-name, value) + + + set_register_contents +

    + stores a value in a simulated register in the given machine. +
  • +
  • + + (get-register-contents machine-model, register-name) + + get_register_contents(machine-model, register-name) + + + get_register_contents +

    + returns the contents of a simulated register in the given machine. +
  • +
  • + + (start machine-model) + + start(machine-model) + + + start register machine +

    + simulates the execution of the given machine, starting from the + beginning of the controller sequence and stopping when it reaches the + end of the sequence. +
  • +
+
+ + + As an example of how these + + procedures + functions + + are used, we can define + + gcd-machine + gcd_machine + + + to be a model of the GCD machine + of section as follows: + + gcd_machine_example + gcd_machine + start + +set_register_contents(gcd_machine, "a", 206); +set_register_contents(gcd_machine, "b", 40); +start(gcd_machine); +get_register_contents(gcd_machine, "a"); + + + + gcdregister machine for + gcd_machine + gcd_machine + make_machine + gcd_machine_example + 2 + +(define gcd-machine + (make-machine + '(a b t) + (list (list 'rem remainder) (list '= =)) + '(test-b + (test (op =) (reg b) (const 0)) + (branch (label gcd-done)) + (assign t (op rem) (reg a) (reg b)) + (assign a (reg b)) + (assign b (reg t)) + (goto (label test-b)) + gcd-done))) + + +const gcd_machine = + make_machine( + list("a", "b", "t"), + list(list("rem", (a, b) => a % b), + list("=", (a, b) => a === b)), + list( + "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")); + + + The first argument to + + make-machine + make_machine + + + is a list of register names. The next argument is a table (a list of + two-element lists) that pairs each operation name with a + + Scheme procedure + JavaScript function + + that implements the operation (that is, produces the same output value + given the same input values). The last argument specifies the controller + as a list of labels and machine instructions, as in + section. + + + + To compute GCDs with this machine, we set the input registers, start the + machine, and examine the result when the simulation terminates: + + set_register_contents_a + gcd_machine + start + 'done' + +(set-register-contents! gcd-machine 'a 206) + + +done + + +set_register_contents(gcd_machine, "a", 206); + + +"done" + + + + set_register_contents_b + set_register_contents_a + 'done' + +(set-register-contents! gcd-machine 'b 40) + + +done + + +set_register_contents(gcd_machine, "b", 40); + + +"done" + + + + start_gcd_machine + set_register_contents_b + 'done' + +(start gcd-machine) + + +done + + +start(gcd_machine); + + +"done" + + + + get_register_contents_a + start_gcd_machine + 2 + +(get-register-contents gcd-machine 'a) + + +2 + + +get_register_contents(gcd_machine, "a"); + + +2 + + + + This computation will run much more slowly than a + gcd + + procedure + function + + written in + + Scheme, + JavaScript, + + because we will simulate low-level machine instructions, such as + assign, by much more complex operations. + + + + Use the simulator to test the machines you designed in + exercise. + + + + + &subsection5.2.1; + + + &subsection5.2.2; + + + &subsection5.2.3; + + + &subsection5.2.4; + +
diff --git a/xml/cn/chapter5/section2/subsection1.xml b/xml/cn/chapter5/section2/subsection1.xml new file mode 100644 index 000000000..b624ad76f --- /dev/null +++ b/xml/cn/chapter5/section2/subsection1.xml @@ -0,0 +1,793 @@ + + + The Machine Model + + + + + + The machine model generated by + + make-machine + make_machine + + is represented as a + + procedure + function + + with local state using the message-passing techniques + developed in chapter. To build this model, + + make-machine + make_machine + + begins by calling the + + procedure + function + + + make-new-machine + make_new_machine + + + to construct + the parts of the machine model that are common to all register + machines. This basic machine model constructed by + + make-new-machine + make_new_machine + + + is essentially a container for some registers and a stack, together with an + execution mechanism that processes the controller instructions one by one. + + + + + Make-machine + + The function + make_machine + + + then extends this basic model (by sending it + messages) to include the registers, operations, and controller of the + particular machine being defined. First it allocates a register in + the new machine for each of the supplied register names and installs + the designated operations in the machine. Then it uses an + assembler + assembler (described below in + section) to transform the controller list + into instructions for the new machine and installs these as the + machines instruction sequence. + + Make-machine + + The function + make_machine + + + returns as its value the modified machine model. + + gcd_machine_complete_example + make_machine + start + +const gcd_machine = + make_machine( + list("a", "b", "t"), + list(list("rem", (a, b) => a % b), + list("=", (a, b) => a === b)), + list( + "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")); +set_register_contents(gcd_machine, "a", 206); +set_register_contents(gcd_machine, "b", 40); +start(gcd_machine); +get_register_contents(gcd_machine, "a"); + + + + make_machine + make_machine + assemble + make_new_machine + gcd_machine_complete_example + +(define (make-machine register-names ops controller-text) + (let ((machine (make-new-machine))) + (for-each (lambda (register-name) + ((machine 'allocate-register) register-name)) + register-names) + ((machine 'install-operations) ops) + ((machine 'install-instruction-sequence) + (assemble controller-text machine)) + machine)) + + +function make_machine(register_names, ops, controller) { + const machine = make_new_machine(); + for_each(register_name => + machine("allocate_register")(register_name), + register_names); + machine("install_operations")(ops); + machine("install_instruction_sequence") + (assemble(controller, machine)); + return machine; +} + + + + + + Registers + + + register(s)representing + + + We will represent a register as a + + procedure + function + + with local state, as in + chapter. The + + procedure + function + + + make-register + make_register + creates a register that + holds a value that can be accessed or changed: + + make_register + make_register + gcd_machine_complete_example + +(define (make-register name) + (let ((contents '*unassigned*)) + (define (dispatch message) + (cond ((eq? message 'get) contents) + ((eq? message 'set) + (lambda (value) (set! contents value))) + (else + (error "Unknown request - - REGISTER" message)))) + dispatch)) + + +function make_register(name) { + let contents = "*unassigned*"; + function dispatch(message) { + return message === "get" + ? contents + : message === "set" + ? value => { contents = value; } + : error(message, "unknown request -- make_register"); + } + return dispatch; +} + + + + The following + + procedures + functions + + are used to access registers: + + get_contents + set_contents + get_contents + gcd_machine_complete_example + +(define (get-contents register) + (register 'get)) + +(define (set-contents! register value) + ((register 'set) value)) + + +function get_contents(register) { + return register("get"); +} +function set_contents(register, value) { + return register("set")(value); +} + + + + + + The stack + + + stackrepresenting + + + We can also represent a stack as a + + procedure + function + + with local state. The + + procedure + function + + + make-stack + make_@stack + + creates a stack whose local state consists + of a list of the items on the stack. A stack accepts requests to + push an item onto the stack, to + pop the top item off the stack + and return it, and to + initialize the stack to empty. + + make_stack + gcd_machine_complete_example + +(define (make-stack) + (let ((s '())) + (define (push x) + (set! s (cons x s))) + (define (pop) + (if (null? s) + (error "Empty stack - - POP") + (let ((top (car s))) + (set! s (cdr s)) + top))) + (define (initialize) + (set! s '()) + 'done) + (define (dispatch message) + (cond ((eq? message 'push) push) + ((eq? message 'pop) (pop)) + ((eq? message 'initialize) (initialize)) + (else (error "Unknown request - - STACK" + message)))) + dispatch)) + + +function make_stack() { + let stack = null; + let frame = null; + function push_marker() { + frame = pair(stack, frame); + return "done"; + } + function pop_marker() { + stack = head(frame); + frame = tail(frame); + return "done"; + } + function push(x) { + stack = pair(x, stack); + return "done"; + } + function pop() { + if (is_null(stack)) { + error("empty stack -- pop"); + } else { + const top = head(stack); + stack = tail(stack); + return top; + } + } + function initialize() { + stack = null; + return "done"; + } + function dispatch(message) { + return message === "push" + ? push + : message === "pop" + ? pop() + : message === "push_marker" + ? push_marker() + : message === "pop_marker" + ? pop_marker() + : message === "initialize" + ? initialize() + : error(message, "unknown request -- stack"); + } + return dispatch; +} + +function make_push_marker_to_stack_ef(machine, stack, pc) { + return () => { + push_marker(stack); + advance_pc(pc); + }; +} +function make_revert_stack_to_marker_ef(machine, stack, pc) { + return () => { + pop_marker(stack); + advance_pc(pc); + }; +} + +function push_marker_to_stack() { return list("push_marker_to_stack"); } +function revert_stack_to_marker() { return list("revert_stack_to_marker"); } + +function pop_marker(stack) { + return stack("pop_marker"); +} +function push_marker(stack) { + return stack("push_marker"); +} + + + + make_stack + +(define (make-stack) + (let ((s '())) + (define (push x) + (set! s (cons x s))) + (define (pop) + (if (null? s) + (error "Empty stack - - POP") + (let ((top (car s))) + (set! s (cdr s)) + top))) + (define (initialize) + (set! s '()) + 'done) + (define (dispatch message) + (cond ((eq? message 'push) push) + ((eq? message 'pop) (pop)) + ((eq? message 'initialize) (initialize)) + (else (error "Unknown request - - STACK" + message)))) + dispatch)) + + +function make_stack() { + let stack = null; + function push(x) { + stack = pair(x, stack); + return "done"; + } + function pop() { + if (is_null(stack)) { + error("empty stack -- pop"); + } else { + const top = head(stack); + stack = tail(stack); + return top; + } + } + function initialize() { + stack = null; + return "done"; + } + function dispatch(message) { + return message === "push" + ? push + : message === "pop" + ? pop() + : message === "initialize" + ? initialize() + : error(message, "unknown request -- stack"); + } + return dispatch; +} + + + + The following + + procedures + functions + + are used to access stacks: + + pop + push + pop + gcd_machine_complete_example + +(define (pop stack) + (stack 'pop)) + +(define (push stack value) + ((stack 'push) value)) + + +function pop(stack) { + return stack("pop"); +} +function push(stack, value) { + return stack("push")(value); +} + + + + + + The basic machine + + + + The + + make-new-machine + make_new_machine + + + + procedure, + function, + + shown in figure, constructs an + object whose local state consists of a stack, an initially empty instruction + sequence, a list of operations that initially contains an operation to + initialize_stack operation in register machine + initialize the stack, and a + register table, in simulator + register table that initially contains two + registers, named + flag register + flag and + pc register + pc + program counter + (for program counter). The internal + + procedure + function + + + allocate-register + allocate_register + + + adds new entries to the register table, and the internal + + procedure + function + + + lookup-register + lookup_register + + + looks up registers in the table. + + + + + +(define (make-new-machine) + (let ((pc (make-register 'pc)) + (flag (make-register 'flag)) + (stack (make-stack)) + (the-instruction-sequence '())) + (let ((the-ops + (list (list 'initialize-stack + (lambda () (stack 'initialize))))) + (register-table + (list (list 'pc pc) (list 'flag flag)))) + (define (allocate-register name) + (if (assoc name register-table) + (error "Multiply defined register: " name) + (set! register-table + (cons (list name (make-register name)) + register-table))) + 'register-allocated) + (define (lookup-register name) + (let ((val (assoc name register-table))) + (if val + (cadr val) + (error "Unknown register:" name)))) + (define (execute) + (let ((insts (get-contents pc))) + (if (null? insts) + 'done + (begin + ((instruction-execution-proc (car insts))) + (execute))))) + (define (dispatch message) + (cond ((eq? message 'start) + (set-contents! pc the-instruction-sequence) + (execute)) + ((eq? message 'install-instruction-sequence) + (lambda (seq) (set! the-instruction-sequence seq))) + ((eq? message 'allocate-register) allocate-register) + ((eq? message 'get-register) lookup-register) + ((eq? message 'install-operations) + (lambda (ops) (set! the-ops (append the-ops ops)))) + ((eq? message 'stack) stack) + ((eq? message 'operations) the-ops) + (else (error "Unknown request - - MACHINE" message)))) + dispatch))) + + + + +
+ + make_new_machine + make_stack + make_register + lookup1 + get_contents + make_inst + gcd_machine_complete_example + +function make_new_machine() { + const pc = make_register("pc"); + const flag = make_register("flag"); + const stack = make_stack(); + let the_instruction_sequence = null; + let the_ops = list(list("initialize_stack", () => stack("initialize"))); + let register_table = list(list("pc", pc), list("flag", flag)); + function allocate_register(name) { + if (is_undefined(assoc(name, register_table))) { + register_table = pair(list(name, make_register(name)), + register_table); + } else { + error(name, "multiply defined register"); + } + return "register allocated"; + } + function lookup_register(name) { + const val = assoc(name, register_table); + return is_undefined(val) + ? error(name, "unknown register") + : head(tail(val)); + } + function execute() { + const insts = get_contents(pc); + if (is_null(insts)) { + return "done"; + } else { + inst_execution_fun(head(insts))(); + return execute(); + } + } + function dispatch(message) { + function start() { + set_contents(pc, the_instruction_sequence); + return execute(); + } + return message === "start" + ? start() + : message === "install_instruction_sequence" + ? seq => { the_instruction_sequence = seq; } + : message === "allocate_register" + ? allocate_register + : message === "get_register" + ? lookup_register + : message === "install_operations" + ? ops => { the_ops = append(the_ops, ops); } + : message === "stack" + ? stack + : message === "operations" + ? the_ops + : error(message, "unknown request -- machine"); + } + return dispatch; +} + + + + The + make_new_machine + make_new_machine + function implements the basic machine model. + + +
+
+
+ + + The flag register is used to control branching + in the simulated machine. + + Test + Our test + + + instructions set the contents of + flag to the result of the test (true or false). + + Branch + Our branch + + + instructions decide whether or not to branch by examining the contents of + flag. + + + + The pc register determines the sequencing of + instructions as the machine runs. This sequencing is implemented by the + internal + + procedure + function + + execute. + In the simulation model, each machine instruction is a data structure + that includes a + + procedure + function + + of no arguments, called the + instruction execution procedurefunction + execution procedurefunctionin register-machine simulator + + instruction execution procedure, + instruction execution function, + + such that calling this + + procedure + function + + simulates executing the instruction. As the simulation runs, + pc points to the place in the instruction + sequence beginning with the next instruction to be executed. + execute + + Execute + The function execute + + + gets that instruction, executes it by calling the instruction execution + + procedure, + function, + + and repeats this cycle until there are no more instructions to execute + (i.e., until pc points to the end of the + instruction sequence). + + + + As part of its operation, each instruction execution + + procedure + function + + modifies + pc to indicate the next instruction to be + executed. + + + Branch and + goto instructions + + + The instructions + branch + and + go_to + + + change pc to point to the new destination. + All other instructions simply advance pc, + making it point to the next instruction in the sequence. Observe that + each call to execute calls + execute again, but this does not produce an + infinite loop because running the instruction execution + + procedure + function + + changes the contents of pc. + + + + + Make-new-machine + + The function + make_new_machine + + + returns a + + dispatch + dispatch + + + procedure + function + + that implements message-passing access to the internal state. Notice that + starting the machine is accomplished by setting + pc to the beginning of the instruction sequence + and calling execute. + + + + + For convenience, we provide an alternate + procedural + interface to a machines + start operation, as well as + + procedures + functions + + to set and examine register contents, as specified at the beginning of + section: + + start register machine + get_register_contents + set_register_contents + start + gcd_machine_complete_example + +(define (start machine) + (machine 'start)) + +(define (get-register-contents machine register-name) + (get-contents (get-register machine register-name))) + +(define (set-register-contents! machine register-name value) + (set-contents! (get-register machine register-name) value) + 'done) + + +function start(machine) { + return machine("start"); +} +function get_register_contents(machine, register_name) { + return get_contents(get_register(machine, register_name)); +} +function set_register_contents(machine, register_name, value) { + set_contents(get_register(machine, register_name), value); + return "done"; +} + + + + These + + procedures + functions + + (and many + + procedures + functions + + in sections and ) + use the following to look up the register with a given name in a given + machine: + + get_register + get_register + gcd_machine_complete_example + +(define (get-register machine reg-name) + ((machine 'get-register) reg-name)) + + +function get_register(machine, reg_name) { + return machine("get_register")(reg_name); +} + + + +
+ diff --git a/xml/cn/chapter5/section2/subsection2.xml b/xml/cn/chapter5/section2/subsection2.xml new file mode 100644 index 000000000..71cc87f69 --- /dev/null +++ b/xml/cn/chapter5/section2/subsection2.xml @@ -0,0 +1,558 @@ + + + The Assembler + + + + + assembler + + The assembler transforms the sequence of controller + + expressions + instructions + + for a machine into a corresponding list of machine instructions, each with its + execution + + procedure. + function. + + Overall, the assembler is much like the evaluators we studied in + chapterthere is an input language (in this case, the + register-machine language) and we must perform an appropriate action for each + type of component in the language. + + + The technique of producing an execution + + procedure + function + + for each instruction is just what we used in + section to speed + up the evaluator by separating analysis from runtime execution. As we + saw in chapter, much useful + syntactic analysis, separated from executionin register-machine simulator + analysis of + + Scheme + JavaScript + + expressions could + be performed without knowing the actual values of + + variables. + names. + + Here, analogously, much useful analysis of register-machine-language + expressions can be performed without knowing the actual contents of + machine registers. For example, we can replace references to + registers by pointers to the register objects, and we can + replace references to labels by pointers to the place in the + instruction sequence that the label designates. + + + + Before it can generate the instruction execution + + procedures, + functions, + the assembler must know what all the labels refer to, so it begins by + scanning the controller textsequence to separate the labels from the + instructions. As it scans the textcontroller, it constructs both a list of + instructions and a table that associates each label with a pointer + into that list. Then the assembler augments the instruction list by + inserting the execution + + procedure + function + + for each instruction. + + + + The assemble + + procedure + function + + is the main entry to the assembler. It takes the controller + textsequence and the + machine model as arguments and returns the instruction sequence to be stored + in the model. + + Assemble + + The function + assemble + + + calls + + extract-labels + + extract_labels + + + to build the initial instruction list and label table from the supplied + controller text. The second argument + to + + extract-labels + + extract_labels + + + is a + + procedure + function + + to be called to process these results: This + + procedure + function + + uses + + update-insts! + + update_insts + + + to generate the instruction execution + + procedures + functions + + and insert them into the instruction list, and returns the modified list. + + assemble + assemble + update_insts + extract_labels + gcd_machine_complete_example + +(define (assemble controller-text machine) + (extract-labels controller-text + (lambda (insts labels) + (update-insts! insts labels machine) + insts))) + + +function assemble(controller, machine) { + return extract_labels(controller, + (insts, labels) => { + update_insts(insts, labels, machine); + return insts; + }); +} + + + + + + + + Extract-labels takes as arguments a list + text (the sequence of controller instruction + expressions) and a receive procedure. + Receive will be called with two values: (1) a + list insts of instruction data structures, + each containing an instruction from text; and + (2) a table called labels, which associates + each label from text with the position in the + list insts that the label designates. + + + The function extract_labels takes + a list controller + and a function receive + as arguments. The function receive will + be called with two values: (1)a list insts + of instruction data structures, each containing an instruction from + controller; and (2)a table called + labels, which associates each label from + controller with the position in the list + insts that the label designates. + + + + + extract_labels + extract_labels + make_label_entry + gcd_machine_complete_example + +(define (extract-labels text receive) + (if (null? text) + (receive '() '()) + (extract-labels (cdr text) + (lambda (insts labels) + (let ((next-inst (car text))) + (if (symbol? next-inst) + (receive insts + (cons (make-label-entry next-inst + insts) + labels)) + (receive (cons (make-instruction next-inst) + insts) + labels))))))) + + +function extract_labels(controller, receive) { + return is_null(controller) + ? receive(null, null) + : extract_labels( + tail(controller), + (insts, labels) => { + const next_element = head(controller); + return is_string(next_element) + ? receive(insts, + pair(make_label_entry(next_element, + insts), + labels)) + : receive(pair(make_inst(next_element), + insts), + labels); + }); +} + + + + + Extract-labels + The function + extract_labels + + + works by sequentially scanning the elements of the + textcontroller + and accumulating the + insts and the + labels. If an element is a + + symbol + string + + (and thus a label) an appropriate entry is added to the + labels table. Otherwise the element is + accumulated onto the insts + list. + Using the + receive procedurefunction + receive + + procedure + function + + here is a way to get + + extract-labels + + extract_labels + + + to effectively return two + valueslabels and + instswithout explicitly making a + compound data structure to hold them. An alternative implementation, which + returns an explicit pair of values, is + + extract_labels + extract_labels_alternative + gcd_machine_complete_example + +(define (extract-labels text) + (if (null? text) + (cons '() '()) + (let ((result (extract-labels (cdr text)))) + (let ((insts (car result)) (labels (cdr result))) + (let ((next-inst (car text))) + (if (symbol? next-inst) + (cons insts + (cons (make-label-entry next-inst insts) labels)) + (cons (cons (make-instruction next-inst) insts) + labels))))))) + + +function extract_labels(controller) { + if (is_null(controller)) { + return pair(null, null); + } else { + const result = extract_labels(tail(controller)); + const insts = head(result); + const labels = tail(result); + const next_element = head(controller); + return is_string(next_element) + ? pair(insts, + pair(make_label_entry(next_element, insts), labels)) + : pair(pair(make_inst(next_element), insts), + labels); + } +} + + + which would be called by assemble as follows: + + assemble + assemble_alternative + gcd_machine_complete_example + +(define (assemble controller-text machine) + (let ((result (extract-labels controller-text))) + (let ((insts (car result)) (labels (cdr result))) + (update-insts! insts labels machine) + insts))) + + +function assemble(controller, machine) { + const result = extract_labels(controller); + const insts = head(result); + const labels = tail(result); + update_insts(insts, labels, machine); + return insts; +} + + + + You can consider our use of receive as + demonstrating an elegant way to + returning multiple values + returning multiple values + return multiple values, or simply an excuse + to show off a programming trick. An argument like + receive that is the next + + procedure + function + + to be invoked is called a + continuationin register-machine simulator + continuation. Recall that we + also used continuations to implement the backtracking control + structure in the amb evaluator in + section. + + + + + + Update-insts! + + The function + update_insts + + modifies the instruction list, which initially contains only + + the text of the instructions, + the controller instructions, + + to include the corresponding execution + + procedures: + functions: + + + update_insts + update_insts + get_register + make_execution_function + make_inst + gcd_machine_complete_example + +(define (update-insts! insts labels machine) + (let ((pc (get-register machine 'pc)) + (flag (get-register machine 'flag)) + (stack (machine 'stack)) + (ops (machine 'operations))) + (for-each + (lambda (inst) + (set-instruction-execution-proc! + inst + (make-execution-procedure + (instruction-text inst) labels machine + pc flag stack ops))) + insts))) + + +function update_insts(insts, labels, machine) { + const pc = get_register(machine, "pc"); + const flag = get_register(machine, "flag"); + const stack = machine("stack"); + const ops = machine("operations"); + return for_each(inst => set_inst_execution_fun( + inst, + make_execution_function( + inst_controller_instruction(inst), + labels, machine, pc, + flag, stack, ops)), + insts); +} + + + + + + The machine instruction data structure simply pairs the + controller + instruction text with the corresponding execution + + procedure. + function. + + The execution + + procedure + function + + is not yet available when + + extract-labels + + extract_labels + + + constructs the instruction, and is inserted later by + + update-insts!. + + update_insts. + + + + make_inst + inst_controller_instruction + inst_execution_fun + set_inst_execution_fun + make_inst + gcd_machine_complete_example + +(define (make-instruction text) + (cons text ())) + +(define (instruction-text inst) + (car inst)) + +(define (instruction-execution-proc inst) + (cdr inst)) + +(define (set-instruction-execution-proc! inst proc) + (set-cdr! inst proc)) + + +function make_inst(inst_controller_instruction) { + return pair(inst_controller_instruction, null); +} +function inst_controller_instruction(inst) { + return head(inst); +} +function inst_execution_fun(inst) { + return tail(inst); +} +function set_inst_execution_fun(inst, fun) { + set_tail(inst, fun); +} + + + + The + + instruction text + controller instruction + + is not used by our simulator, but is handy to keep + around for debugging (see + exercise). + + + + Elements of the label table are pairs: + + make_label_entry + make_label_entry + gcd_machine_complete_example + +(define (make-label-entry label-name insts) + (cons label-name insts)) + + +function make_label_entry(label_name, insts) { + return pair(label_name, insts); +} + + + Entries will be looked up in the table with + + lookup_label + lookup_label + gcd_machine_complete_example + +(define (lookup-label labels label-name) + (let ((val (assoc label-name labels))) + (if val + (cdr val) + (error "Undefined label - - ASSEMBLE" label-name)))) + + +function lookup_label(labels, label_name) { + const val = assoc(label_name, labels); + return is_undefined(val) + ? error(label_name, "undefined label -- assemble") + : tail(val); +} + + + + + + The following register-machine code is ambiguous, because the label + here is defined more than once: + + +start + (goto (label here)) +here + (assign a (const 3)) + (goto (label there)) +here + (assign a (const 4)) + (goto (label there)) +there + + +"start", + go_to(label("here")), +"here", + assign("a", constant(3)), + go_to(label("there")), +"here", + assign("a", constant(4)), + go_to(label("there")), +"there", + + + With the simulator as written, what will the contents of register + a be when control reaches + there? Modify the + + extract-labels + + extract_labels + + + + procedure + function + + so that the assembler will signal an error if the same label + name is used to indicate two different locations. + + + + assembler + + + + diff --git a/xml/cn/chapter5/section2/subsection3.xml b/xml/cn/chapter5/section2/subsection3.xml new file mode 100644 index 000000000..65c0ba06b --- /dev/null +++ b/xml/cn/chapter5/section2/subsection3.xml @@ -0,0 +1,1452 @@ + + + + Generating Execution Procedures for Instructions + Instructions and Their Execution Functions + + + + + + execution procedurefunctionin register-machine simulator + register-machine languageinstructions + + + The assembler calls + + make-execution-procedure + + make_execution_function + + + to generate the execution + + procedure + function + + for ana controller instruction. + Like the analyze + + procedure + function + + in the evaluator of section, + this dispatches on the type of instruction to generate the appropriate + execution + + procedure. + function. + + + + The details of these execution functions determine the + meaning of the individual instructions in the register-machine language. + + + + + make_execution_function + gcd_machine_complete_example + +(define (make-execution-procedure inst labels machine + pc flag stack ops) + (cond ((eq? (car inst) 'assign) + (make-assign inst machine labels ops pc)) + ((eq? (car inst) 'test) + (make-test inst machine labels ops flag pc)) + ((eq? (car inst) 'branch) + (make-branch inst machine labels flag pc)) + ((eq? (car inst) 'goto) + (make-goto inst machine labels pc)) + ((eq? (car inst) 'save) + (make-save inst machine stack pc)) + ((eq? (car inst) 'restore) + (make-restore inst machine stack pc)) + ((eq? (car inst) 'perform) + (make-perform inst machine labels ops pc)) + (else (error "Unknown instruction type - - ASSEMBLE" + inst)))) + + +function make_execution_function(inst, labels, machine, + pc, flag, stack, ops) { + const inst_type = type(inst); + return inst_type === "assign" + ? make_assign_ef(inst, machine, labels, ops, pc) + : inst_type === "test" + ? make_test_ef(inst, machine, labels, ops, flag, pc) + : inst_type === "branch" + ? make_branch_ef(inst, machine, labels, flag, pc) + : inst_type === "go_to" + ? make_go_to_ef(inst, machine, labels, pc) + : inst_type === "save" + ? make_save_ef(inst, machine, stack, pc) + : inst_type === "restore" + ? make_restore_ef(inst, machine, stack, pc) + : inst_type === "perform" + ? make_perform_ef(inst, machine, labels, ops, pc) + : error(inst, "unknown instruction type -- assemble"); +} + + + + make_execution_function + make_assign + make_test + test_instruction_syntax + make_branch_5 + branch_branch_dest + make_go_to + go_to_go_to_dest + make_save + save_restore + make_perform + perform_perform_action + gcd_machine_complete_example + +(define (make-execution-procedure inst labels machine + pc flag stack ops) + (cond ((eq? (car inst) 'assign) + (make-assign inst machine labels ops pc)) + ((eq? (car inst) 'test) + (make-test inst machine labels ops flag pc)) + ((eq? (car inst) 'branch) + (make-branch inst machine labels flag pc)) + ((eq? (car inst) 'goto) + (make-goto inst machine labels pc)) + ((eq? (car inst) 'save) + (make-save inst machine stack pc)) + ((eq? (car inst) 'restore) + (make-restore inst machine stack pc)) + ((eq? (car inst) 'perform) + (make-perform inst machine labels ops pc)) + (else (error "Unknown instruction type - - ASSEMBLE" + inst)))) + + +function make_execution_function(inst, labels, machine, + pc, flag, stack, ops) { + return type(inst) === "assign" + ? make_assign_ef(inst, machine, labels, ops, pc) + : type(inst) === "test" + ? make_test_ef(inst, machine, labels, ops, flag, pc) + : type(inst) === "branch" + ? make_branch_ef(inst, machine, labels, flag, pc) + : type(inst) === "go_to" + ? make_go_to_ef(inst, machine, labels, pc) + : type(inst) === "save" + ? make_save_ef(inst, machine, stack, pc) + : type(inst) === "restore" + ? make_restore_ef(inst, machine, stack, pc) + : type(inst) === "push_marker_to_stack" + ? make_push_marker_to_stack_ef(machine, stack, pc) + : type(inst) === "revert_stack_to_marker" + ? make_revert_stack_to_marker_ef(machine, stack, pc) + : type(inst) === "perform" + ? make_perform_ef(inst, machine, labels, ops, pc) + : error(inst, "unknown instruction type -- assemble"); +} + + + + + For each type of instruction in the register-machine language, + there is a generator that builds an appropriate execution + procedure. The details of these procedures determine the + meaning of the individual instructions in the register-machine + language. We use data abstraction to isolate the detailed + syntax of register-machine expressions from the general + execution mechanism, as we did for evaluators in + section, by + using syntax procedures to extract and classify the parts of + an instruction. + + + The elements of the controller sequence + received by make_machine and passed + to assemble are strings (for + labels) and tagged lists (for instructions). The tag in an instruction + is a string that identifies the instruction type, such as + "go_to", and the remaining elements + of the list contains the arguments, such as the destination of the + go_to. + The dispatch in make_execution_function uses + + type in register machine + type_function + gcd_machine_complete_example + +function type(instruction) { return head(instruction); } + + + + + + + + + The tagged lists are constructed when the + list expression that is the third + argument to make_machine is + evaluated. Each argument to that + list is either a string (which + evaluates to itself) or a call to a constructor for an instruction + tagged list. For example, assign("b", reg("t")) calls the constructor + assign with arguments + "b" and the result of calling the + constructor reg with the argument + "t". The constructors and their + arguments determine the syntax of the individual instructions in the + register-machine language. The instruction constructors and selectors + are shown below, along with the execution-function generators that use + the selectors. + + + + + + + + + + + + + + + + + + Assign instructions + The instruction assign + + + + + + assign (in register machine)simulating + + + + The + + make-assign + make_assign_ef + + + procedure + function + + + handles assign + makes execution functions for assign + + instructions: + + make_assign_ef + make_assign + type_function + make_operation_exp + assign_reg_name + gcd_machine_complete_example + +(define (make-assign inst machine labels operations pc) + (let ((target + (get-register machine (assign-reg-name inst))) + (value-exp (assign-value-exp inst))) + (let ((value-proc + (if (operation-exp? value-exp) + (make-operation-exp + value-exp machine labels operations) + (make-primitive-exp + (car value-exp) machine labels)))) + (lambda () ; execution procedure for assign + (set-contents! target (value-proc)) + (advance-pc pc))))) + + +function make_assign_ef(inst, machine, labels, operations, pc) { + const target = get_register(machine, assign_reg_name(inst)); + const value_exp = assign_value_exp(inst); + const value_fun = + is_operation_exp(value_exp) + ? make_operation_exp_ef(value_exp, machine, labels, operations) + : make_primitive_exp_ef(value_exp, machine, labels); + return () => { + set_contents(target, value_fun()); + advance_pc(pc); + }; +} + + + + + Make-assign + extracts the target register name (the second element of the instruction) + and the value expression (the rest of the list that forms the instruction) + from the assign instruction using the selectors + assign-reg-name + assign-value-exp + + + The function assign constructs + assign instructions. + The selectors assign_@reg_@name and + assign_value_exp extract the register name + and value expression from an assign instruction. + + + + assign (in register machine)instruction constructor + register-machine languageassign + assign_reg_name + assign_value_exp + assign_reg_name + + gcd_machine_complete_example + +(define (assign-reg-name assign-instruction) + (cadr assign-instruction)) + +(define (assign-value-exp assign-instruction) + (cddr assign-instruction)) + + +function assign(register_name, source) { + return list("assign", register_name, source); +} +function assign_reg_name(assign_instruction) { + return head(tail(assign_instruction)); +} +function assign_value_exp(assign_instruction) { + return head(tail(tail(assign_instruction))); +} + + + + + + The register name is looked up + + + The function make_assign_ef looks up the register name + + + with + + get-register + + get_register + + + to produce the target register object. The value expression is passed to + + make-operation-exp + make_@operation_@exp_@ef + + + if the value is the result of an operation, and + + + it is passed + + + to + + make-primitive-exp + + make_@primitive_@exp_@ef + + + otherwise. These + + procedures + functions + + (shown below) + + parse + analyze + + the value expression and produce an execution + + procedure + function + + for the value. This is a + + procedure + function + + of no arguments, called + value_fun + + value-proc, + + value_fun, + + which will be evaluated during the simulation to produce the actual + value to be assigned to the register. Notice that the work of looking + up the register name and + + parsing + analyzing + + the value expression is performed + just once, at assembly time, not every time the instruction is + simulated. This saving of work is the reason we use execution + syntactic analysis, separated from executionin register-machine simulator + + procedures, + functions, + + and corresponds directly to the saving in work we obtained by separating + program analysis from execution in the evaluator of + section. + + + + The result returned by + + make-assign + make_assign_ef + + is the execution + + procedure + function + + for the assign instruction. When this + + procedure + function + + is called (by the machine models execute + + procedure), + function), + + it sets the contents of the target register to the result obtained by + executing + + value-proc. + value_fun. + + Then it advances the pc to the next instruction + by running the + + procedure + function + + + advance_pc + advance_pc + gcd_machine_complete_example + +(define (advance-pc pc) + (set-contents! pc (cdr (get-contents pc)))) + + +function advance_pc(pc) { + set_contents(pc, tail(get_contents(pc))); +} + + + + Advance-pc + + The function + advance_pc + + + is the normal termination for all instructions except + branch and + + + goto. + + + go_to. + + + + + + + + + Test, + branch, and + goto + instructions + + + The instructions + test, + branch, and + go_to + + + + + + + + + test (in register machine)simulating + Make-test + + + The function + test (in register machine)simulating + make_test_ef + + + handles test instructions in a similar way. + It extracts the expression that specifies the condition to be tested and + generates an execution + + procedure + function + + for it. At simulation time, the + + procedure + function + + for the condition is called, the result is assigned to the + flag register, and the + pc is advanced: + + make_test_ef + make_test + advance_pc + gcd_machine_complete_example + +(define (make-test inst machine labels operations flag pc) + (let ((condition (test-condition inst))) + (if (operation-exp? condition) + (let ((condition-proc + (make-operation-exp + condition machine labels operations))) + (lambda () + (set-contents! flag (condition-proc)) + (advance-pc pc))) + (error "Bad TEST instruction - - ASSEMBLE" inst)))) + +(define (test-condition test-instruction) + (cdr test-instruction)) + + +function make_test_ef(inst, machine, labels, operations, flag, pc) { + const condition = test_condition(inst); + if (is_operation_exp(condition)) { + const condition_fun = make_operation_exp_ef( + condition, machine, + labels, operations); + return () => { + set_contents(flag, condition_fun()); + advance_pc(pc); + }; + } else { + error(inst, "bad test instruction -- assemble"); + } +} + + + + + The function + test constructs + test instructions. The selector + test_condition extracts the condition + from a test. + + test_condition + test (in register machine)instruction constructor + register-machine languagetest + test_instruction_syntax + gcd_machine_complete_example + +function test(condition) { return list("test", condition); } + +function test_condition(test_instruction) { + return head(tail(test_instruction)); +} + + + + + + + + The execution + + procedure + function + + for a branch instruction checks the contents of + the flag register and either sets the contents + of the pc to the branch destination (if the + branch is taken) or else just advances the pc + (if the branch is not taken). Notice that the indicated destination in a + branch (in register machine)simulating + branch instruction must be a label, and the + + + make-branch + + + make_branch_ef + + + + procedure + function + + enforces this. Notice also that the label is looked up at assembly time, + not each time the branch instruction is + simulated. + + make_branch_ef + make_branch_5 + gcd_machine_complete_example + +(define (make-branch inst machine labels flag pc) + (let ((dest (branch-dest inst))) + (if (label-exp? dest) + (let ((insts + (lookup-label labels (label-exp-label dest)))) + (lambda () + (if (get-contents flag) + (set-contents! pc insts) + (advance-pc pc)))) + (error "Bad BRANCH instruction - - ASSEMBLE" inst)))) + +(define (branch-dest branch-instruction) + (cadr branch-instruction)) + + +function make_branch_ef(inst, machine, labels, flag, pc) { + const dest = branch_dest(inst); + if (is_label_exp(dest)) { + const insts = lookup_label(labels, label_exp_label(dest)); + return () => { + if (get_contents(flag)) { + set_contents(pc, insts); + } else { + advance_pc(pc); + } + }; + } else { + error(inst, "bad branch instruction -- assemble"); + } +} + + + + + + The function branch + constructs branch instructions. The + selector + branch_dest extracts + the destination from a branch. + + register-machine languagebranch + branch (in register machine)instruction constructor + branch_dest + branch_branch_dest + gcd_machine_complete_example + +function branch(label) { return list("branch", label); } + +function branch_dest(branch_instruction) { + return head(tail(branch_instruction)); +} + + + + + + + + A + go_to (in register machine)simulating + + goto + 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 is no condition to + checkthe pc is always set to the + new destination. + + make_go_to_ef + make_go_to + is_register_exp + gcd_machine_complete_example + +(define (make-goto inst machine labels pc) + (let ((dest (goto-dest inst))) + (cond ((label-exp? dest) + (let ((insts + (lookup-label labels + (label-exp-label dest)))) + (lambda () (set-contents! pc insts)))) + ((register-exp? dest) + (let ((reg + (get-register machine + (register-exp-reg dest)))) + (lambda () + (set-contents! pc (get-contents reg))))) + (else (error "Bad GOTO instruction - - ASSEMBLE" + inst))))) + +(define (goto-dest goto-instruction) + (cadr goto-instruction)) + + +function make_go_to_ef(inst, machine, labels, pc) { + const dest = go_to_dest(inst); + if (is_label_exp(dest)) { + const insts = lookup_label(labels, label_exp_label(dest)); + return () => set_contents(pc, insts); + } else if (is_register_exp(dest)) { + const reg = get_register(machine, register_exp_reg(dest)); + return () => set_contents(pc, get_contents(reg)); + } else { + error(inst, "bad go_to instruction -- assemble"); + } +} + + + + + + The function go_to constructs + go_to instructions. The selector + go_to_dest extracts the destination from a + go_to instruction. + + go_to (in register machine)instruction constructor + register-machine languagego_to + go_to_dest + 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)); +} + + + + + + + + + Other instructions + + + + The stack instructions + save and restore + simply use the stack with the designated register and advance the + pc: + + make_save_ef + make_restore_ef + save (in register machine)simulating + restore (in register machine)simulating + make_save + pop + gcd_machine_complete_example + +(define (make-save inst machine stack pc) + (let ((reg (get-register machine + (stack-inst-reg-name inst)))) + (lambda () + (push stack (get-contents reg)) + (advance-pc pc)))) + +(define (make-restore inst machine stack pc) + (let ((reg (get-register machine + (stack-inst-reg-name inst)))) + (lambda () + (set-contents! reg (pop stack)) + (advance-pc pc)))) + +(define (stack-inst-reg-name stack-instruction) + (cadr stack-instruction)) + + +function make_save_ef(inst, machine, stack, pc) { + const reg = get_register(machine, stack_inst_reg_name(inst)); + return () => { + push(stack, get_contents(reg)); + advance_pc(pc); + }; +} +function make_restore_ef(inst, machine, stack, pc) { + const reg = get_register(machine, stack_inst_reg_name(inst)); + return () => { + set_contents(reg, pop(stack)); + advance_pc(pc); + }; +} + + + + + + The functions save and + restore construct + save and restore instructions. The + selector + stack_inst_reg_name + extracts the register name from such instructions. + + save (in register machine)instruction constructor + register-machine languagesave + restore (in register machine)instruction constructor + register-machine languagerestore + stack_inst_reg_name + 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)); +} + + + + + + + + The final instruction type, handled by + perform (in register machine)simulating + + make-perform, + + make_perform_ef, + + generates an execution + + procedure + function + + for the action to be performed. At simulation time, the action + + procedure + function + + is executed and the pc advanced. + + make_perform_ef + make_perform + is_register_exp + gcd_machine_complete_example + +(define (make-perform inst machine labels operations pc) + (let ((action (perform-action inst))) + (if (operation-exp? action) + (let ((action-proc + (make-operation-exp + action machine labels operations))) + (lambda () + (action-proc) + (advance-pc pc))) + (error "Bad PERFORM instruction - - ASSEMBLE" inst)))) + +(define (perform-action inst) (cdr inst)) + + +function make_perform_ef(inst, machine, labels, operations, pc) { + const action = perform_action(inst); + if (is_operation_exp(action)) { + const action_fun = make_operation_exp_ef(action, machine, + labels, operations); + return () => { + action_fun(); + advance_pc(pc); + }; + } else { + error(inst, "bad perform instruction -- assemble"); + } +} + + + + + + The function perform + constructs perform instructions. The + selector + perform_@action extracts + the action from a perform instruction. + + perform (in register machine)instruction constructor + register-machine languageperform + perform_action + 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)); +} + + + + + + + + + Execution + + procedures + functions + + for subexpressions + + + + The value of a + reg (in register machine)simulating + reg, + label (in register machine)simulating + label, or + constant (in register machine)simulating + + const + constant + + expression may be needed for assignment to a register + + (make-assign) + (make_assign_ef, above) + + or for input to an operation + + (make-operation-exp, + + (make_@operation_@exp_@ef, + + below). The following + + procedure + function + + generates execution + + procedures + functions + + to produce values for these expressions during the simulation: + + make_primitive_exp_ef + make_primitive_exp + lookup_label + gcd_machine_complete_example + +(define (make-primitive-exp exp machine labels) + (cond ((constant-exp? exp) + (let ((c (constant-exp-value exp))) + (lambda () c))) + ((label-exp? exp) + (let ((insts + (lookup-label labels + (label-exp-label exp)))) + (lambda () insts))) + ((register-exp? exp) + (let ((r (get-register machine + (register-exp-reg exp)))) + (lambda () (get-contents r)))) + (else + (error "Unknown expression type - - ASSEMBLE" exp)))) + + +function make_primitive_exp_ef(exp, machine, labels) { + if (is_constant_exp(exp)) { + const c = constant_exp_value(exp); + return () => c; + } else if (is_label_exp(exp)) { + const insts = lookup_label(labels, label_exp_label(exp)); + return () => insts; + } else if (is_register_exp(exp)) { + const r = get_register(machine, register_exp_reg(exp)); + return () => get_contents(r); + } else { + error(exp, "unknown expression type -- assemble"); + } +} + + + + + + The syntax of reg, + label, and const + expressions is determined by + + register-exp + register-exp-reg + constant-exp + constant-exp-value + label-exp + label-exp-label + is_register_exp + tagged_list + gcd_machine_complete_example + +(define (register-exp? exp) (tagged-list? exp 'reg)) + +(define (register-exp-reg exp) (cadr exp)) + +(define (constant-exp? exp) (tagged-list? exp 'const)) + +(define (constant-exp-value exp) (cadr exp)) + +(define (label-exp? exp) (tagged-list? exp 'label)) + +(define (label-exp-label exp) (cadr exp)) + + + + + The syntax of reg, + label, and constant + expressions is determined by the following constructor functions, along with + corresponding predicates and selectors. + + reg (in register machine) + register-machine languagereg + is_register_exp + register_exp_reg + constant (in register machine) + register-machine languageconstant + is_constant_exp + constant_exp_value + label (in register machine) + register-machine languagelabel + is_label_exp + label_exp_label + is_register_exp0 + tagged_list + gcd_machine_complete_example + +function reg(name) { return list("reg", name); } + +function is_register_exp(exp) { return is_tagged_list(exp, "reg"); } + +function register_exp_reg(exp) { return head(tail(exp)); } + +function constant(value) { return list("constant", value); } + +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 is_tagged_list(exp, "label"); } + +function label_exp_label(exp) { return head(tail(exp)); } + + + + + + + + + + Assign, + perform, and + test + instructions + + + The instructions + assign, + perform, and + test + + + may include the application of a machine operation (specified by an + op (in register machine)simulating + op expression) to some operands (specified + by reg and + + const + constant + + expressions). The following + + procedure + function + + produces an execution + + procedure + function + + for an operation expressiona list containing the + operation and operand expressions from the instruction: + + make_operation_exp_ef + make_operation_exp + lookup_prim + make_primitive_exp + gcd_machine_complete_example + +(define (make-operation-exp exp machine labels operations) + (let ((op (lookup-prim (operation-exp-op exp) operations)) + (aprocs + (map (lambda (e) + (make-primitive-exp e machine labels)) + (operation-exp-operands exp)))) + (lambda () + (apply op (map (lambda (p) (p)) aprocs))))) + + +function make_operation_exp_ef(exp, machine, labels, operations) { + const op = lookup_prim(operation_exp_op(exp), operations); + const afuns = map(e => make_primitive_exp_ef(e, machine, labels), + operation_exp_operands(exp)); + return () => apply_in_underlying_javascript( + op, map(f => f(), afuns)); +} + + + + + + The syntax of operation expressions is determined by + + operation-exp + operation-exp-op + operation-exp-operands + is_operation_exp + tagged_list + gcd_machine_complete_example + +(define (operation-exp? exp) + (and (pair? exp) (tagged-list? (car exp) 'op))) + +(define (operation-exp-op operation-exp) + (cadr (car operation-exp))) + +(define (operation-exp-operands operation-exp) + (cdr operation-exp)) + + + + + The syntax of operation expressions is determined by + + op (in register machine)simulating + register-machine languageop + is_operation_exp + operation_exp_op + operation_exp_operands + 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); } + + + + + + Observe that the treatment of operation expressions is very much like + the treatment of + + procedure + function + + applications by the + + analyze-application + + analyze_application + + + + procedure + function + + in the evaluator of section in + that we generate an execution + + procedure + function + + for each operand. + + + At simulation time, we call the operand + procedures + and apply the Scheme procedure + + + At simulation time, we call the operand + functions + and apply the JavaScript function + + + that simulates the operation to the resulting values. + + + + We make use of the function + apply_in_underlying_javascript, as we did + in apply_primitive_function in + section. This 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. Without this, + op would have been restricted to be a unary + function. + + + + + The simulation + + procedure + function + + is found by looking up the operation name in the operation table for the + machine: + + lookup_prim + lookup_prim + gcd_machine_complete_example + +(define (lookup-prim symbol operations) + (let ((val (assoc symbol operations))) + (if val + (cadr val) + (error "Unknown operation - - ASSEMBLE" symbol)))) + + +function lookup_prim(symbol, operations) { + const val = assoc(symbol, operations); + return is_undefined(val) + ? error(symbol, "unknown operation -- assemble") + : head(tail(val)); +} + + + + + + The treatment of machine operations above permits them to operate + on labels as well as on constants and the contents of registers. + Modify the expression-processing + + procedures + functions + + to enforce the condition that operations can be used only with registers + and constants. + + + + + + + + Design a new syntax for register-machine instructions and modify the + simulator to use your new syntax. Can you implement your new + syntax without changing any part of the simulator except the + + syntax procedures in this section? + constructor and selector functions in this section? + + + + + + + + When we introduced + save (in register machine) + save and + restore (in register machine) + restore in + section, we didnt specify + what would happen if you tried to restore a register that was not the last + one saved, as in the sequence + + +(save y) +(save x) +(restore y) + + +save(y); +save(x); +restore(y); + + + There are several reasonable possibilities for the meaning of + restore: +
    +
  1. + + (restore y) + restore(y) + + + puts into y the last value saved on the + stack, regardless of what register that value came from. This is the + way our simulator behaves. Show how to take advantage of this + behavior to eliminate one instruction from the Fibonacci machine of + section + (figure). +
  2. +
  3. + + (restore y) + restore(y) + + + puts into y the last value saved on the + stack, but only if that value was saved from + y; otherwise, it signals an error. Modify + the simulator to behave this way. You will have to change + save to put the register name on the stack + along with the value. +
  4. +
  5. + + (restore y) + restore(y) + + + puts into y the last value saved from + y regardless of what other registers were + saved after y and not restored. Modify the + simulator to behave this way. You will have to associate a separate + stack with each register. You should make the + + initialize-stack + + initialize_stack + + + operation initialize all the register stacks. +
  6. +
+ +
+ + + The simulator can be used to help determine the data paths required + for implementing a machine with a given controller. Extend + the assembler to store the following information in the machine model: +
    +
  • + a list of all instructions, with duplicates removed, sorted by + instruction type + (assign, + + goto, + go_to, + + and so on); +
  • +
  • + a list (without duplicates) of the registers used to hold entry points + (these are the registers referenced by + + goto + go_to + + instructions); +
  • +
  • + a list (without duplicates) of the registers that are + saved + or restored; +
  • +
  • + for each register, a list (without duplicates) of the sources from + which it is assigned (for example, the sources for register + val in the factorial machine of + figure are + + (const 1) + + constant(1) + + + and + + + ((op *) (reg n) (reg val))). + + + list(op("*"), reg("n"), reg("val"))). + + +
  • +
+ Extend the message-passing interface to the machine to provide access to + this new information. To test your analyzer, define the Fibonacci machine + from figure and examine the lists you + constructed. + +
+ + + Modify the simulator so that it uses the controller sequence to determine + what registers the machine has rather than requiring a list of registers as + an argument to + + make-machine. + make_machine. + + Instead of preallocating the registers in + + make-machine, + + make_machine, + + you can allocate them one at a time when they are first seen during assembly + of the instructions. + + + + register-machine languageinstructions + execution procedurefunctionin register-machine simulator + +
diff --git a/xml/cn/chapter5/section2/subsection4.xml b/xml/cn/chapter5/section2/subsection4.xml new file mode 100644 index 000000000..17c92f1cd --- /dev/null +++ b/xml/cn/chapter5/section2/subsection4.xml @@ -0,0 +1,351 @@ + + + Monitoring Machine Performance + + + + + register machinemonitoring performance + + + Simulation is useful not only for verifying the correctness of a + proposed machine design but also for measuring the machines + simulationmonitorfor monitoring performance of register machine + performance. For example, we can install in our simulation program a + meter that measures the number of stack operations used in a + computation. To do this, we modify our simulated stack to keep track + of the number of times registers are saved on the stack and the + maximum depth reached by the stack, and add a message to the stacks + interface that prints the statistics, as shown below. + We also add an operation to the basic machine model to print the + stack statistics, by initializing + + the-ops + the_ops + + in + + make-new-machine + + make_new_machine + + to + + initialize_stack operation in register machine + print_stack_statistics operation in register machine + +(list (list 'initialize-stack + (lambda () (stack 'initialize))) + (list 'print-stack-statistics + (lambda () (stack 'print-statistics)))) + + +list(list("initialize_stack", + () => stack("initialize")), + list("print_stack_statistics", + () => stack("print_statistics"))); + + + + Here is the new version of + + make-stack: + + make_stack: + + + make_stackwith monitored stack + +(define (make-stack) + (let ((s '()) + (number-pushes 0) + (max-depth 0) + (current-depth 0)) + (define (push x) + (set! s (cons x s)) + (set! number-pushes (+ 1 number-pushes)) + (set! current-depth (+ 1 current-depth)) + (set! max-depth (max current-depth max-depth))) + (define (pop) + (if (null? s) + (error "Empty stack - - POP") + (let ((top (car s))) + (set! s (cdr s)) + (set! current-depth (- current-depth 1)) + top))) + (define (initialize) + (set! s '()) + (set! number-pushes 0) + (set! max-depth 0) + (set! current-depth 0) + 'done) + (define (print-statistics) + (newline) + (display (list 'total-pushes '= number-pushes + 'maximum-depth '= max-depth))) + (define (dispatch message) + (cond ((eq? message 'push) push) + ((eq? message 'pop) (pop)) + ((eq? message 'initialize) (initialize)) + ((eq? message 'print-statistics) + (print-statistics)) + (else + (error "Unknown request - - STACK" message)))) + dispatch)) + + +function make_stack() { + let stack = null; + let number_pushes = 0; + let max_depth = 0; + let current_depth = 0; + function push(x) { + stack = pair(x, stack); + number_pushes = number_pushes + 1; + current_depth = current_depth + 1; + max_depth = math_max(current_depth, max_depth); + return "done"; + } + function pop() { + if (is_null(stack)) { + error("empty stack -- pop"); + } else { + const top = head(stack); + stack = tail(stack); + current_depth = current_depth - 1; + return top; + } + } + function initialize() { + stack = null; + number_pushes = 0; + max_depth = 0; + current_depth = 0; + return "done"; + } + function print_statistics() { + display("total pushes = " + stringify(number_pushes)); + display("maximum depth = " + stringify(max_depth)); + } + function dispatch(message) { + return message === "push" + ? push + : message === "pop" + ? pop() + : message === "initialize" + ? initialize() + : message === "print_statistics" + ? print_statistics() + : error(message, "unknown request -- stack"); + } + return dispatch; +} + + + + + + + + Exercises + through + describe other useful monitoring and debugging features that can be + added to the register-machine simulator. + + + + Measure the number of pushes and the maximum stack depth required to + compute + factorialstack usage, register machine + $n!$ for various small values of + $n$ using the factorial + machine shown in Figure. From your + data determine formulas in terms of $n$ for the + total number of push operations and the maximum stack depth used in + computing $n!$ for any + $n > 1$. Note that each of these is a linear + function of $n$ and is thus determined by two + constants. In order to get the statistics printed, you will have to augment + the factorial machine with instructions to initialize the stack and print + the statistics. You may want to also modify the machine so that it + repeatedly reads a value for $n$, computes the + factorial, and prints + the result (as we did for the GCD machine in + figure), so that you will not have to + repeatedly invoke + + get-register-contents, + get_register_contents, + + + + set-register-contents!, + + set_register_contents, + + + and + + start. + start. + + + + + + Add + instruction counting + instruction counting + to the register machine simulation. + That is, have the machine model keep track of the number of + instructions executed. Extend the machine models interface to + accept a new message that prints the value of the instruction count and + resets the count to zero. + + + + + Augment the simulator to provide for + instruction tracing + tracinginstruction execution + instruction tracing. + That is, before each instruction is executed, the simulator should print + the text of the instruction. Make the machine model accept + + trace-on + + trace_on + + + and + + trace-off + trace_off + + messages to turn tracing on and off. + + + + + Extend the instruction tracing of + exercise so that + before printing an instruction, the simulator prints any labels that + immediately precede that instruction in the controller sequence. Be + careful to do this in a way that does not interfere with instruction + counting (exercise). + You will have to make the simulator retain the necessary label information. + + + + + Modify the + + make-register + make_register + + + procedure + function + + of section so that registers can be + register(s)tracing + tracingregister assignment + traced. Registers should accept messages that turn tracing on and off. When + a register is traced, assigning a value to the register should print the + name of the register, the old contents of the register, and the new contents + being assigned. Extend the interface to the machine model to permit you to + turn tracing on and off for designated machine registers. + + + + + + Alyssa P. Hacker wants a + breakpoint + breakpoint feature in the simulator to help her debug her machine + designs. You have been hired to install this feature for her. She wants to + be able to specify a place in the controller sequence where the simulator + will stop and allow her to examine the state of the machine. You are to + implement a + + procedure + function + + + +(set-breakpoint $machine$ $label$ $n$) + + +set_breakpoint($machine$, $label$, $n$) + + + that sets a breakpoint just before the $n$th + instruction after the given label. For example, + + +(set-breakpoint gcd-machine 'test-b 4) + + +set_breakpoint(gcd_machine, "test_b", 4) + + + installs a breakpoint in + + gcd-machine + + gcd_machine + + + just before the assignment to register a. + When the simulator reaches the breakpoint it should print the label and the + offset of the breakpoint and stop executing instructions. Alyssa can then + use + + get-register-contents + + get_register_contents + + + and + + set-register-contents! + + set_register_contents + + + to manipulate the state of the simulated machine. She should then be able + to continue execution by saying + + +(proceed-machine $machine$) + + +proceed_machine($machine$) + + + She should also be able to remove a specific breakpoint by means of + + +(cancel-breakpoint $machine$ $label$ $n$) + + +cancel_breakpoint($machine$, $label$, $n$) + + + or to remove all breakpoints by means of + + +(cancel-all-breakpoints $machine$) + + +cancel_all_breakpoints($machine$) + + + + + register machinesimulator + register-machine simulator + simulationregisterof register machine + register machinemonitoring performance + + diff --git a/xml/cn/chapter5/section3/section3.xml b/xml/cn/chapter5/section3/section3.xml new file mode 100644 index 000000000..921497e8c --- /dev/null +++ b/xml/cn/chapter5/section3/section3.xml @@ -0,0 +1,89 @@ +
+ Storage Allocation and Garbage Collection + + + + + + list-structured memory + memorylist-structured + + + In section, we will show how to implement a + + Lisp + JavaScript + + evaluator as a register machine. In order to simplify the discussion, we + will assume that our register machines can be equipped with a + list-structured memory, in which the basic operations for + manipulating list-structured data are primitive. Postulating the existence + of such a memory is a useful abstraction when one is focusing on the + mechanisms of control in + + a Lisp + an + + interpreter, but this does not reflect a realistic view of the actual + primitive data operations of contemporary computers. To obtain a more + complete picture of how + + a Lisp system operates, + systems can support list-structured memory + efficiently, + + we must investigate how list structure can be represented in a way that is + compatible with conventional computer memories. + + + + There are two considerations in implementing list structure. The first is + purely an issue of representation: how to represent the + box-and-pointer structure of + + Lisp + + pairs, using only the storage and addressing capabilities of typical computer + memories. The second issue concerns the management of memory as a + computation proceeds. The operation of a + + Lisp + JavaScript + + system depends crucially on the ability to + continually create new data objects. These include objects that are + explicitly created by the + + Lisp + JavaScript + + + procedures + functions + + being interpreted as well as structures created by the interpreter itself, + such as environments and argument lists. Although the constant creation of + new data objects would pose no problem on a computer with an infinite amount + of rapidly addressable memory, computer memories are available only in + finite sizes (mores the pity). + + Lisp + JavaScript + + thus provide an + automatic storage allocation + automatic storage allocation facility to + support the illusion of an infinite memory. When a data object is no longer + needed, the memory allocated to it is automatically recycled and used to + construct new data objects. There are various techniques for providing such + automatic storage allocation. The method we shall discuss in this section + is called garbage collection. + + + + &subsection5.3.1; + + + &subsection5.3.2; + +
diff --git a/xml/cn/chapter5/section3/subsection1.xml b/xml/cn/chapter5/section3/subsection1.xml new file mode 100644 index 000000000..7b3bd4ec2 --- /dev/null +++ b/xml/cn/chapter5/section3/subsection1.xml @@ -0,0 +1,886 @@ + + + Memory as Vectors + + + + + A conventional computer memory can be thought of as an array of + cubbyholes, each of which can contain a piece of information. Each + cubbyhole has a unique name, called its + address + address or + location + location. Typical memory systems provide two primitive operations: + one that fetches the data stored in a specified location and one that + assigns new data to a specified location. Memory addresses can be + incremented to support sequential access to some set of the + cubbyholes. More generally, many important data operations require + that memory addresses be treated as data, which can be stored in + memory locations and manipulated in machine registers. The + representation of list structure is one application of such + address arithmetic + arithmeticaddress arithmetic + address arithmetic. + + + + To model computer memory, we use a new kind of data structure called a + vector (data structure) + vector. Abstractly, a vector is a compound data object whose + individual elements can be accessed by means of an integer index in an + amount of time that is independent of the index.We could represent + memory as lists of items. However, the access time would then not be + independent of the index, since accessing the + $n$th element of a list requires + $n-1$ + + cdr + tail + + operations. In order to describe memory operations, we use two + + primitive Scheme procedures + functions + + for manipulating vectors: + As mentioned in section + + (footnote), + + + (footnote2), + + JavaScript supports vectors as data + structures and calls them arrays. We use the term vector in + this book, as it is the more common terminology. + The vector functions above are easily implemented using + JavaScript's primitive array support. +
    +
  • + + + (vector-ref + vector n) + + + vector_ref(vector, n) + + + vector_ref (primitive function) + vector_ref (\textit{ns}) + returns the nth element of the vector. +
  • +
  • + + + (vector-set! + vector n value + ) + + + vector_set(vector, n, value) + + + vector_set (primitive function) + vector_set (\textit{ns}) + sets the $n$th element of the vector to the + designated value. +
  • +
+ + For example, if v is a vector, then + + (vector-ref v 5) + vector_ref(v, 5) + + + gets the fifth entry in the vector v and + + (vector-set! v 5 7) + + vector_set(v, 5, 7) + + + changes the value of the fifth entry of the vector + v + to 7.For completeness, we should specify a + + make-vector + make_vector + + operation that constructs vectors. However, in the present application we + will use vectors only to model fixed divisions of the computer + memory. For computer memory, this access can be implemented + through the use of address arithmetic to combine a base address + that specifies the beginning location of a vector in memory with an + index that specifies the offset of a particular element of the + vector. +
+ + + Representing + + Lisp + + data + + + pair(s)represented using vectors + list structurerepresented using vectors + + + + + + +
+ Box-and-pointer and memory-vector representations + of the list ((1 2) 3 4). + +
+
+ +
+
+ Box-and-pointer and memory-vector representations + of the list + list(list(1, 2), 3, 4). + +
+
+
+
+ + + We can use vectors to implement the basic pair structures required for a + list-structured memory. Let us imagine that computer memory is divided into + two vectors: + the_headsvector + + the-cars + the_heads + + and + the_tailsvector + + the-cdrs. + the_tails. + + We will represent list structure as follows: A pointer to a pair is an index + into the two vectors. The + + car + head + + of the pair is the entry in + + the-cars + the_heads + + with the designated index, and the + + cdr + tail + + of the pair is the entry in + the-cdrs + the_tails + + with the designated index. We also need a representation for objects other + than pairs (such as numbers and + + symbols) + strings) + + and a way to distinguish one kind of data from another. There are many + methods of accomplishing this, but they all reduce to using + typed pointer + pointertyped + typed pointers, that is, to extending the notion of + pointer to include information on data type.This is + precisely the same + tagged data + datatagged + tagged data idea we introduced in chapter for + dealing with generic operations. Here, however, the data types are + included at the primitive machine level rather than constructed + through the use of lists. + + Type information may be encoded in + a variety of ways, depending on the details of the machine on which the + + Lisp + JavaScript + + system is to be implemented. The execution efficiency of + + Lisp + JavaScript + + programs will be strongly dependent on how cleverly this choice is made, but + it is difficult to formulate general design rules for good choices. The + most straightforward way to implement typed pointers is to allocate a fixed + set of bits in each pointer to be a + type field + type field that encodes the data type. Important questions to be + addressed in designing such a representation include the following: + How many type bits are required? How large must the vector indices + be? How efficiently can the primitive machine instructions be used to + manipulate the type fields of pointers? Machines that include special + hardware for the efficient handling of type fields are said to have + tagged architecture + tagged architectures. + + The data type enables the system to + distinguish a pointer to a pair (which consists of the pair + data type and an index into the memory vectors) from pointers to other + kinds of data (which consist of some other data type and whatever is + being used to represent data of that type). Two data objects are + ===equalityas equality of pointers + considered to be the same + + (eq? + (===) + + if their pointers are identical. + + + Figure + + + Figure + + + illustrates the use of this method to represent + + ((1 2) 3 4), + list(list(1, 2), 3, 4), + + + whose box-and-pointer diagram is also shown. We use letter prefixes to + denote the data-type information. Thus, a pointer to the pair with + index5 is denoted p5, the empty list + is denoted by the pointer e0, and a pointer to + the number4 is denoted n4. In the + box-and-pointer diagram, we have indicated at the lower left of each pair + the vector index that specifies where the + + car + head + + and + + cdr + tail + + of the pair are stored. The blank locations in + + the-cars + the_heads + + and + + the-cdrs + the_tails + + + may contain parts of other list structures (not of interest here). + + + + + +
+ Box-and-pointer and memory-vector representations + of the list ((1 2) 3 4). + +
+
+ +
+
+ Box-and-pointer and memory-vector representations + of the list + list(list(1, 2), 3, 4). + +
+
+
+
+
+ + + A pointer to a number, such as n4, + might consist of a type indicating numeric data together with the + actual representation of the number 4.This decision on the + representation of numbers determines whether + ===numericas numeric equality operator + equalitynumbersof numbers + number(s)equality of + + eq?, + ===, + + which tests equality of pointers, can be used to test for equality of + numbers. If the pointer contains the number itself, then equal numbers will + have the same pointer. But if the pointer contains the index of a location + where the number is stored, equal numbers will be guaranteed to have + equal pointers only if we are careful never to store the same number + in more than one location. + To deal with numbers that are too large to be represented in the fixed + amount of space allocated for a single pointer, we could use a distinct + bignum + number(s)bignum + bignum data type, for which the pointer designates a list in which + the parts of the number are stored.This is just like writing a + number as a sequence of digits, except that each digit is a + number between 0 and the largest number that can be stored in a single + pointer. + + + + + + A + symbol(s)representation of + symbol might be represented as a typed pointer that designates a + sequence of the characters that form the symbols + printed representation. This sequence is constructed by the + Lisp reader + when the character string is initially encountered in input. Since + we want two instances of a symbol + to be recognized as the same symbol by + eq? + and we want + eq?symbolas symbol comparison operator + equalitysymbolof symbols + eq? + to be a simple test for equality of pointers, we must ensure that if the + reader sees the same character string twice, it will use the same pointer + (to the same sequence of characters) to represent both occurrences. + To accomplish this, the reader maintains a table, traditionally called the + obarray + obarray, of all the symbols it has ever encountered. When the + reader encounters a character string and is about to construct a + symbol, it checks the obarray to see if it + has ever before seen the same character string. If it has not, it + uses the characters to construct a new symbol (a typed pointer to a + new character sequence) and enters this pointer in the obarray. + If the reader has seen the string before, it returns the + symbol pointer stored in the obarray. + This process of replacing character strings + by unique pointers is called + interning strings + string(s)interning + interning symbols. + + + + + A string + string(s)representation of + might be represented as a typed pointer that designates a + sequence of the characters that form the strings printed + representation. The parser constructs such a sequence + when it encounters a string literal, and the + string-concatenation operator + and + string-producing + primitive functions such as + stringify + construct such a sequence. + Since we want two instances of a string to + be recognized as the same string by + === and we want + ===stringas string comparison operator + equalitystringsof strings + === + to + be a simple test for equality of pointers, we must ensure that if the + system sees the same string twice, it will use the same pointer (to + the same sequence of characters) to represent both occurrences. To + accomplish this, the system maintains a table, called the + string pool + string pool, + of all the strings it has ever encountered. When the system + is about to construct a string, it checks the string pool to see if it has ever + before seen the same string. If it has not, it + constructs a new string (a typed pointer to a new + character sequence) and enters this pointer in the string pool. If the + system has seen the string before, it returns the string pointer + stored in the string pool. This process of replacing strings by unique + pointers is called + interning strings + string(s)interning + string interning. + + + + + + + Implementing the primitive list operations + + + + Given the above representation scheme, we can replace each + primitive list operation of a register machine with one or + more primitive vector operations. We will use two registers, + the_headsregister + + the-cars + the_heads + + and + the_tailsregister + + the-cdrs, + the_tails, + + to identify the memory vectors, and will + assume that + + vector-ref + vector_ref + + and + + vector-set! + vector_set + + are available as primitive operations. We also assume that numeric + operations on pointers (such as incrementing a pointer, using a pair pointer + to index a vector, or adding two numbers) use only the index portion of + the typed pointer. + + + + For example, we can make a register machine support the instructions + + head (primitive function)implemented with vectors + tail (primitive function)implemented with vectors + +(assign reg$_{1}$ (op car) (reg reg$_{2}$)) + +(assign reg$_{1}$ (op cdr) (reg reg$_{2}$)) + + +assign(reg$_1$, list(op("head"), reg(reg$_2$))) + +assign(reg$_1$, list(op("tail"), reg(reg$_2$))) + + + if we implement these, respectively, as + + +(assign reg$_{1}$ (op vector-ref) (reg the-cars) (reg reg$_{2}$)) + +(assign reg$_{1}$ (op vector-ref) (reg the-cdrs) (reg reg$_{2}$)) + + +assign(reg$_1$, list(op("vector_ref"), reg("the_heads"), reg(reg$_2$))) + +assign(reg$_1$, list(op("vector_ref"), reg("the_tails"), reg(reg$_2$))) + + + + The instructions + + set_head (primitive function)implemented with vectors + set_tail (primitive function)implemented with vectors + +(perform (op set-car!) (reg reg$_{1}$) (reg reg$_{2}$)) + +(perform (op set-cdr!) (reg reg$_{1}$) (reg reg$_{2}$)) + + +perform(list(op("set_head"), reg(reg$_1$), reg(reg$_2$))) + +perform(list(op("set_tail"), reg(reg$_1$), reg(reg$_2$))) + + + are implemented as + + +(perform + (op vector-set!) (reg the-cars) (reg reg$_{1}$) (reg reg$_{2}$)) + + (perform + (op vector-set!) (reg the-cdrs) (reg reg$_{1}$) (reg reg$_{2}$)) + + +perform(list(op("vector_set"), reg("the_heads"), reg(reg$_1$), reg(reg$_2$))) + +perform(list(op("vector_set"), reg("the_tails"), reg(reg$_1$), reg(reg$_2$))) + + + + + + + + Cons + + + The operation + pair (primitive function)implemented with vectors + pair + + + is performed by allocating an unused index and storing the arguments to + + cons + pair + + in + + the-cars + the_heads + + and + + the-cdrs + the_tails + + at that indexed vector position. We presume that there is a special + register, + free register + free, that always holds a pair pointer + containing the next available index, and that we can increment the index + part of that pointer to find the next free location.There are + other ways of finding free storage. For example, we could link together + all the unused pairs into a + free list + free list. Our free locations are consecutive (and hence can be + accessed by incrementing a pointer) because we are using a compacting + garbage collector, as we will see in + section. + For example, the instruction + + +(assign reg$_{1}$ (op cons) (reg reg$_{2}$) (reg reg$_{3}$)) + + +assign(reg$_1$, list(op("pair"), reg(reg$_2$), reg(reg$_3$))) + + + is implemented as the following sequence of vector + operations:This is essentially the implementation of + + cons + pair + + in terms of + + set-car! + set_head + + and + + set-cdr!, + set_tail, + + as described in section. + The operation + + get-new-pair + get_new_pair + + + used in that implementation is realized here by the + free pointer. + + +(perform + (op vector-set!) (reg the-cars) (reg free) (reg reg$_{2}$)) +(perform + (op vector-set!) (reg the-cdrs) (reg free) (reg reg$_{3}$)) + (assign reg$_{1}$ (reg free)) + (assign free (op +) (reg free) (const 1)) + + +perform(list(op("vector_set"), + reg("the_heads"), reg("free"), reg(reg$_2$))), +perform(list(op("vector_set"), + reg("the_tails"), reg("free"), reg(reg$_3$))), +assign(reg$_1$, reg("free")), +assign("free", list(op("+"), reg("free"), constant(1))) + + + + + + The + + eq? + === + + operation + + +(op eq?) (reg reg$_{1}$) (reg reg$_{2}$) + + +list(op("==="), reg(reg$_1$), reg(reg$_2$)) + + + simply tests the equality of all fields in the registers, and + predicates such as + is_pair (primitive function)implemented with typed pointers + + pair?, + is_pair, + + is_null (primitive function)implemented with typed pointers + + null?, + is_null, + + is_string (primitive function)implemented with typed pointers + + symbol?, + is_string, + + and + is_number (primitive function)implemented with typed pointers + + number?, + is_number + + need only check the type field. + + + + Implementing stacks + + + + stackrepresenting + + + Although our register machines use stacks, we need do nothing special + here, since stacks can be modeled in terms of lists. The stack can be + + a list of the saved values, pointed to by a special register + + the-stack. + the_stack. + + Thus, + + (save reg) + save(reg) + + can be implementedas + + save (in register machine)implementing + +(assign the-stack (op cons) (reg reg) (reg the-stack)) + + +assign("the_stack", list(op("pair"), reg(reg), reg("the_stack"))) + + + Similarly, + + (restore reg) + + restore(reg) + + can be implemented as + + restore (in register machine)implementing + +(assign reg (op car) (reg the-stack)) +(assign the-stack (op cdr) (reg the-stack)) + + +assign(reg, list(op("head"), reg("the_stack"))) +assign("the_stack", list(op("tail"), reg("the_stack"))) + + + and + + (perform (op initialize-stack)) + + + perform(list(op("initialize_stack"))) + + + can be implemented as + + +(assign the-stack (const ())) + + +assign("the_stack", constant(null)) + + + + These operations can be further expanded in terms of the vector + operations given above. In conventional computer architectures, + however, it is usually advantageous to allocate the stack as a + separate vector. Then pushing and popping the stack can be + accomplished by incrementing or decrementing an index into that + vector. + + + + Draw the box-and-pointer representation and the memory-vector representation + + + (as in figure) + + + (as in figure) + + + of the list structure produced by + + + +(define x (cons 1 2)) +(define y (list x x)) + + +const x = pair(1, 2); +const y = list(x, x); + + + with the free pointer initially + p1. What is the final value of + free$\,$? What + pointers represent the values of x and + y? + + + + + + Implement register machines for the following + count_leavesas register machine + + procedures. + functions. + + Assume that the list-structure memory operations are available as + machine primitives. +
    +
  1. + Recursive + + count-leaves: + count_leaves: + + + + count_leaves_5_3_1 + count_leaves_example + 4 + +(define (count-leaves tree) + (cond ((null? tree) 0) + ((not (pair? tree)) 1) + (else (+ (count-leaves (car tree)) + (count-leaves (cdr tree)))))) + + +function count_leaves(tree) { + return is_null(tree) + ? 0 + : ! is_pair(tree) + ? 1 + : count_leaves(head(tree)) + + count_leaves(tail(tree)); +} + + +
  2. +
  3. + Recursive + + count-leaves + count_leaves + + + with explicit counter: + + count_leaves_5_3_1_iter + count_leaves_example + 4 + +(define (count-leaves tree) + (define (count-iter tree n) + (cond ((null? tree) n) + ((not (pair? tree)) (+ n 1)) + (else (count-iter (cdr tree) + (count-iter (car tree) n))))) + (count-iter tree 0)) + + +function count_leaves(tree) { + function count_iter(tree, n) { + return is_null(tree) + ? n + : ! is_pair(tree) + ? n + 1 + : count_iter(tail(tree), + count_iter(head(tree), n)); + } + return count_iter(tree, 0); +} + + +
  4. +
+
+ + + Exercise of + section + presented an + append + + procedure + function + + that appends two lists to form a new list and an + + append! + procedure + + + append_mutator + function + + + that splices two lists together. Design a register machine to + appendregisteras register machine + append_mutatoras register machine + implement + each of these + + procedures. + functions. + + Assume that the list-structure memory operations are + available as primitive operations. + + + + pair(s)represented using vectors + list structurerepresented using vectors + +
diff --git a/xml/cn/chapter5/section3/subsection2.xml b/xml/cn/chapter5/section3/subsection2.xml new file mode 100644 index 000000000..c3c165aca --- /dev/null +++ b/xml/cn/chapter5/section3/subsection2.xml @@ -0,0 +1,1909 @@ + + + Maintaining the Illusion of Infinite Memory + + + + + garbage collection + + + The representation method outlined in + section solves the problem of + implementing list structure, provided that we have an infinite amount of + memory. With a real computer we will eventually run out of free space in + which to construct new pairs.This may not be true eventually, + because memories may get large enough so that it would be impossible + to run out of free memory in the lifetime of the computer. For + example, there are about + + + $3\times 10^{13}$ microseconds + + + $3\times 10^{16}$ nanoseconds + + + in a year, so if we were to + + + cons + pair + + once per + + + microsecond + + + nanosecond + + + we would need about + + + $10^{15}$ + + + $10^{18}$ + + + cells of memory to build a machine + that could operate for 30 years without running out of memory. That much + memory seems absurdly large by todays standards, but it is not + physically impossible. On the other hand, processors are getting faster and + + + a future computer may have + + + modern computers have increasingly + + + large numbers of processors operating in + parallel on a single memory, so it may be possible to use up memory much + faster than we have postulated. However, most of the pairs + generated in a typical computation are used only to hold intermediate + results. After these + results are accessed, the pairs are no longer neededthey are + garbage. For instance, the computation + + +(accumulate + 0 (filter odd? (enumerate-interval 0 n))) + + +accumulate((x, y) => x + y, + 0, + filter(is_odd, enumerate_interval(0, n))) + + + constructs two lists: the enumeration and the result of filtering + the enumeration. When the accumulation is complete, these lists are + no longer needed, and the allocated memory can be reclaimed. If we + can arrange to collect all the garbage periodically, and if this turns + out to recycle memory at about the same rate at which we construct new + pairs, we will have preserved the illusion that there is an infinite + amount of memory. + + + + In order to recycle pairs, we must have a way to determine which + allocated pairs are not needed (in the sense that their contents can + no longer influence the future of the computation). The method we + shall examine for accomplishing this is known as garbage + collection. Garbage collection is based on the observation that, at + any moment in + + a Lisp interpretation, + an interpretation based on list-structured memory, + + the only objects that can + affect the future of the computation are those that can be reached by + some succession of + + car + head + + and + + cdr + tail + + operations starting from the pointers that are currently in the machine + registers.We assume here that the stack is represented as a list + as described in section, so that + items on the stack are accessible via the pointer in the stack + register. Any memory cell that is not so accessible may be + recycled. + + + + + There are many ways to perform garbage collection. The method we + shall examine here is called + stop-and-copy garbage collector + garbage collectorstop-and-copy + stop-and-copy. The basic idea is to divide memory into two + halves: working memory and free memory. When + + cons + pair + + constructs pairs, it allocates these in working memory. When working memory + is full, we perform garbage collection by locating all the useful pairs in + working memory and copying these into consecutive locations in free memory. + (The useful pairs are located by tracing all the + + car + head + and + + cdr + tail + + pointers, starting with the machine registers.) Since we do not copy the + garbage, there will presumably be additional free memory that we can + use to allocate new pairs. In addition, nothing in the working memory + is needed, since all the useful pairs in it have been copied. Thus, + if we interchange the roles of working memory and free memory, we can + continue processing; new pairs will be allocated in the new working + memory (which was the old free memory). When this is full, we can + copy the useful pairs into the new free memory (which was the old + working memory).This idea was invented and first implemented + by + Minsky, Marvin Lee + Minsky, as part of the implementation of + LispDECon DEC PDP-1 + Lisp for the PDP-1 at the + MITResearch Laboratory of Electronics + MIT Research Laboratory of Electronics. It was further developed by + Fenichel, Robert + Yochelson, Jerome C. + Fenichel and Yochelson (1969) for use in the Lisp implementation for the + Multics time-sharing system + Multics time-sharing system. Later, + Baker, Henry G., Jr. + Baker (1978) developed a real-time version of the method, + which does not require the computation to stop during garbage collection. + Bakers idea was extended by + Hewitt, Carl Eddie + Hewitt, + Lieberman, Henry + Lieberman, and + Moon, David A. + Moon (see Lieberman and Hewitt 1983) to take + advantage of the fact that some structure is more volatile + and other structure is more permanent. +

+ An alternative commonly used garbage-collection technique is the + mark-sweep garbage collector + garbage collectormark-sweep + mark-sweep method. This consists of tracing all the structure + accessible from the machine registers and marking each pair we reach. + We then scan all of memory, and any location that is unmarked is + swept up as garbage and made available for reuse. A full + discussion of the mark-sweep method can be found in + Allen, John + Allen 1978. +

+ The Minsky-Fenichel-Yochelson algorithm is the dominant algorithm in + use for large-memory systems because it examines only the useful part + of memory. This is in contrast to mark-sweep, in which the sweep + phase must check all of memory. A second advantage of stop-and-copy + is that it is a + compacting garbage collector + garbage collectorcompacting + compacting garbage collector. That is, at the + end of the garbage-collection phase the useful data will have been + moved to consecutive memory locations, with all garbage pairs + compressed out. This can be an extremely important performance + consideration in machines with virtual memory, in which accesses to + widely separated memory addresses may require extra paging + operations.
+
+ + + Implementation of a stop-and-copy garbage collector + + + + We now use our register-machine language to describe the stop-and-copy + algorithm in more detail. We will assume that there is a register + called + root register + root that contains a pointer to a structure + that eventually points at all accessible data. This can be arranged by + storing the contents of all the machine registers in a preallocated list + pointed at by root just before starting + garbage collection.This list of + registers does not + include + the registers used by the storage-allocation + + + systemroot, + + + system: root, + + + + the-cars, + the_heads, + + + the-cdrs, + the_tails, + + and the other registers that will be introduced in this section. + We also assume that, in addition to the current working memory, there is + free memory available into which we can copy the useful data. The current + working memory consists of vectors whose base addresses are in + registers called + the_headsregister + + the-cars + the_heads + + and + the_tailsregister + + the-cdrs, + the_tails, + + and the free memory is in registers called + new_heads register + + new-cars + new_heads + + and + new_tails register + + new-cdrs. + new_tails. + + + + + + Garbage collection is triggered when we exhaust the free cells in the + current working memory, that is, when a + + cons + pair + + operation attempts to increment the free + pointer beyond the end of the memory vector. When the garbage-collection + process is complete, the root pointer will + point into the new memory, all objects accessible from the + root will have been moved to the new memory, + and the free pointer will indicate the next + place in the new memory where a new pair can be allocated. In addition, + the roles of working memory and new memory will have been + interchangednew pairs will be constructed in the new memory, + beginning at the place indicated by free, and + the (previous) working memory will be available as the new memory for the + next garbage collection. + + Figure + Figure + + shows the arrangement of memory just before and just after garbage + collection. + + +
+ + Reconfiguration of memory by the garbage-collection process. + + +
+
+ +
+
+ + Reconfiguration of memory by the garbage-collection process. + + +
+
+
+
+ + + The state of the garbage-collection process is controlled by + maintaining two pointers: + free register + free and + scan register + scan. These are initialized to point to the + beginning of the new memory. The algorithm begins by relocating the pair + pointed at by root to the beginning of the new + memory. The pair is copied, the root pointer + is adjusted to point to the new location, and the + free pointer is incremented. In addition, the + old location of the pair is marked to show that its contents have been + moved. This marking is done as follows: In the + + car + head + + position, we place a special tag that signals that this is an already-moved + object. (Such an object is traditionally called a + broken heart + broken heart.)The term + broken heart was coined by + Cressey, David + David Cressey, who wrote a garbage collector + for + MDL + LispMDL dialect of + MDL, a dialect of Lisp developed at MIT during the early 1970s. + In the + + cdr + tail + + position we place a + forwarding address + forwarding address that points at the location to which the object + has been moved. + + + + + After relocating the root, the garbage collector enters its basic + cycle. At each step in the algorithm, the + scan pointer + (initially pointing at the relocated root) points at a pair that has + been moved to the new memory but whose + + car + head + + and + + cdr + tail + + pointers still refer to objects in the old memory. These objects are each + relocated, and the scan pointer is incremented. + To relocate an object (for example, the object indicated by the + + car + head + + pointer of the pair we are scanning) we check to see if the object has + already been moved (as indicated by the presence of a broken-heart tag + in the + + car + head + + position of the object). If the object has not + already been moved, we copy it to the place indicated by + free, + update free, set up a broken heart at the + objects old location, and update the pointer to the object (in this + example, the + + car + head + + pointer of the pair we are scanning) to point + to the new location. If the object has already been moved, its + forwarding address (found in the + + cdr + tail + + position of the broken heart) is substituted for the pointer in the pair + being scanned. Eventually, all accessible objects will have been moved and + scanned, at which point the scan pointer will + overtake the free pointer and the process will + terminate. + + + + We can specify the stop-and-copy algorithm as a sequence of instructions for + a register machine. The basic step of relocating an object is accomplished + by a subroutine called + + relocate-old-result-in-new. + + relocate_old_result_in_new. + + + This subroutine gets its argument, a pointer to the object to be relocated, + from a register named + old register + old. It relocates the designated object + (incrementing free in the process), + puts a pointer to the relocated object into a register called + new register + new, and returns by branching to the entry + point stored in the register + + relocate-continue. + relocate_continue. + + + To begin garbage collection, we invoke this subroutine to relocate the + root pointer, after initializing + free and scan. + When the relocation of root has been + accomplished, we install the new pointer as the new + root and enter the main loop of the garbage + collector. + + begin_garbage_collection + +begin-garbage-collection + (assign free (const 0)) + (assign scan (const 0)) + (assign old (reg root)) + (assign relocate-continue (label reassign-root)) + (goto (label relocate-old-result-in-new)) +reassign-root + (assign root (reg new)) + (goto (label gc-loop)) + + +"begin_garbage_collection", + assign("free", constant(0)), + assign("scan", constant(0)), + assign("old", reg("root")), + assign("relocate_continue", label("reassign_root")), + go_to(label("relocate_old_result_in_new")), +"reassign_root", + assign("root", reg("new")), + go_to(label("gc_loop")), + + + + + + + In the main loop of the garbage collector we must determine whether + there are any more objects to be scanned. We do this by testing + whether the scan pointer is coincident with + the free pointer. If the pointers are equal, + then all accessible objects have been relocated, and we branch to + + gc-flip, + gc_flip, + + which cleans things up so that we can continue the interrupted computation. + If there are still pairs to be scanned, we call the relocate subroutine to + relocate the + + car + head + + of the next pair (by placing the + + car + head + + pointer in old). The + + relocate-continue + relocate_continue + + + register is set up so that the subroutine will return to update the + + car + head + + pointer. + + gc_loop + +gc-loop + (test (op =) (reg scan) (reg free)) + (branch (label gc-flip)) + (assign old (op vector-ref) (reg new-cars) (reg scan)) + (assign relocate-continue (label update-car)) + (goto (label relocate-old-result-in-new)) + + +"gc_loop", + test(list(op("==="), reg("scan"), reg("free"))), + branch(label("gc_flip")), + assign("old", list(op("vector_ref"), reg("new_heads"), reg("scan"))), + assign("relocate_continue", label("update_head")), + go_to(label("relocate_old_result_in_new")), + + + + + + At + + update-car, + update_head, + + we modify the + + car + head + + pointer of the pair being scanned, then proceed to relocate the + + cdr + tail + + of the pair. We return to + + update-cdr + update_tail + + when that relocation has been accomplished. After relocating and updating + the + + cdr, + tail, + + we are finished scanning that pair, so we continue with the main loop. + + update_head + +update-car + (perform + (op vector-set!) (reg new-cars) (reg scan) (reg new)) + (assign old (op vector-ref) (reg new-cdrs) (reg scan)) + (assign relocate-continue (label update-cdr)) + (goto (label relocate-old-result-in-new)) + +update-cdr + (perform + (op vector-set!) (reg new-cdrs) (reg scan) (reg new)) + (assign scan (op +) (reg scan) (const 1)) + (goto (label gc-loop)) + + +"update_head", + perform(list(op("vector_set"), + reg("new_heads"), reg("scan"), reg("new"))), + assign("old", list(op("vector_ref"), + reg("new_tails"), reg("scan"))), + assign("relocate_continue", label("update_tail")), + go_to(label("relocate_old_result_in_new")), + +"update_tail", + perform(list(op("vector_set"), + reg("new_tails"), reg("scan"), reg("new"))), + assign("scan", list(op("+"), reg("scan"), constant(1))), + go_to(label("gc_loop")), + + + + + + The subroutine + + relocate-old-result-in-new + + + relocate_old_result_in_new + + + relocates objects as follows: If the object to be relocated (pointed at by + old) is not a pair, then we return the same + pointer to the object unchanged (in new). + (For example, we may be scanning a pair whose + + car + head + + is the number 4. If we represent the + + car + head + + by n4, as described in + section, then we want the + relocated + + car + head + + pointer to still be n4.) Otherwise, we + must perform the relocation. If the + + car + head + + position of the pair to be relocated contains a broken-heart tag, then the + pair has in fact already been moved, so we retrieve the forwarding address + (from the + + cdr + tail + + position of the broken heart) and return this in + new. If the pointer in + old points at a yet-unmoved pair, then we move + the pair to the first free cell in new memory (pointed at by + free) and set up the broken heart by storing a + broken-heart tag and forwarding address at the old location. + + Relocate-old-result-in-new + The subroutine + relocate_old_result_in_new + + + uses a register + + + oldcr register + oldcr + + + oldht register + oldht + + + to hold the + + car + head + + or the + + cdr + tail + + of the object pointed at by old.The + garbage collector uses the low-level predicate + + pointer-to-pair? + is_pointer_to_pair + + + instead of the list-structure + + pair? + is_pair + + operation because in a real system there might be various things + that are treated as pairs for garbage-collection purposes. + For example, + + + in a Scheme system that conforms to the IEEE standard + + + a + + + procedure + + + function + + + object may be implemented as a special kind of + pair that doesnt satisfy the + + pair? + is_pair + + predicate. + For simulation purposes, + + pointer-to-pair? + is_pointer_to_pair + + + can be implemented as + + pair?. + is_pair. + + + + + relocate_old_result_in_new + +relocate-old-result-in-new + (test (op pointer-to-pair?) (reg old)) + (branch (label pair)) + (assign new (reg old)) + (goto (reg relocate-continue)) +pair + (assign oldcr (op vector-ref) (reg the-cars) (reg old)) + (test (op broken-heart?) (reg oldcr)) + (branch (label already-moved)) + (assign new (reg free)) ; new location for pair + ;; Update free pointer. + (assign free (op +) (reg free) (const 1)) + ;; Copy the car and cdr to new memory. + (perform (op vector-set!) + (reg new-cars) (reg new) (reg oldcr)) + (assign oldcr (op vector-ref) (reg the-cdrs) (reg old)) + (perform (op vector-set!) + (reg new-cdrs) (reg new) (reg oldcr)) + ;; Construct the broken heart. + (perform (op vector-set!) + (reg the-cars) (reg old) (const broken-heart)) + (perform + (op vector-set!) (reg the-cdrs) (reg old) (reg new)) + (goto (reg relocate-continue)) +already-moved + (assign new (op vector-ref) (reg the-cdrs) (reg old)) + (goto (reg relocate-continue)) + + +"relocate_old_result_in_new", + test(list(op("is_pointer_to_pair"), reg("old"))), + branch(label("pair")), + assign("new", reg("old")), + go_to(reg("relocate_continue")), +"pair", + assign("oldht", list(op("vector_ref"), + reg("the_heads"), reg("old"))), + test(list(op("is_broken_heart"), reg("oldht"))), + branch(label("already_moved")), + assign("new", reg("free")), // new location for pair + // Update $\texttt{free}$ pointer + assign("free", list(op("+"), reg("free"), constant(1))), + // Copy the head and tail to new memory + perform(list(op("vector_set"), + reg("new_heads"), reg("new"), + reg("oldht"))), + assign("oldht", list(op("vector_ref"), + reg("the_tails"), reg("old"))), + perform(list(op("vector_set"), + reg("new_tails"), reg("new"), + reg("oldht"))), + // Construct the broken heart + perform(list(op("vector_set"), + reg("the_heads"), reg("old"), + constant("broken_heart"))), + perform(list(op("vector_set"), + reg("the_tails"), reg("old"), + reg("new"))), + go_to(reg("relocate_continue")), +"already_moved", + assign("new", list(op("vector_ref"), + reg("the_tails"), reg("old"))), + go_to(reg("relocate_continue")), + + + + + + At the very end of the garbage collection process, we interchange the + role of old and new memories by interchanging pointers: interchanging + + the-cars + the_heads + + with + + new-cars, + new_heads, + + and + + the-cdrs + the_tails + + with + + new-cdrs. + new_tails. + + We will then be ready to perform another garbage + collection the next time memory runs out. + + testing_5_3_2 + tagged_list + make_go_to + pop + lookup1 + go_to_go_to_dest + make_register + + gc-flip + (assign temp (reg the-cdrs)) + (assign the-cdrs (reg new-cdrs)) + (assign new-cdrs (reg temp)) + (assign temp (reg the-cars)) + (assign the-cars (reg new-cars)) + (assign new-cars (reg temp)) + + +// TYPED POINTERS + +const NUMBER_TYPE = "number"; +const BOOL_TYPE = "bool"; +const STRING_TYPE = "string"; +const PTR_TYPE = "ptr"; +const PROG_TYPE = "prog"; +const NULL_TYPE = "null"; +const UNDEFINED_TYPE = "undefined"; +const NO_VALUE_YET_TYPE = "*unassigned*"; +const BROKEN_HEART_TYPE = "broken_heart"; + +function make_ptr_ptr(idx) { + return pair(PTR_TYPE, idx); +} + +function make_null_ptr() { + return pair(NULL_TYPE, null); +} + +function make_no_value_yet_ptr() { + return pair(NO_VALUE_YET_TYPE, null); +} + +function make_prog_ptr(idx) { + return pair(PROG_TYPE, idx); +} + +function make_broken_heart_ptr() { + return pair(BROKEN_HEART_TYPE, null); +} + +function get_elem_type(elem) { + return is_number(elem) ? NUMBER_TYPE : + is_boolean(elem) ? BOOL_TYPE : + is_string(elem) ? STRING_TYPE : + is_null(elem) ? NULL_TYPE : + is_undefined(elem) ? UNDEFINED_TYPE : + error(elem, "invalid typed elem"); +} + +function wrap_ptr(elem) { + return pair(get_elem_type(elem), elem); +} + +function unwrap_ptr(ptr) { + return tail(ptr); +} + +function is_ptr(ptr) { + return is_pair(ptr) && + !is_pair(head(ptr)) && + !is_pair(tail(ptr)) && + (head(ptr) === NUMBER_TYPE || + head(ptr) === BOOL_TYPE || + head(ptr) === STRING_TYPE || + head(ptr) === PTR_TYPE || + head(ptr) === NULL_TYPE || + head(ptr) === UNDEFINED_TYPE || + head(ptr) === PROG_TYPE || + head(ptr) === NO_VALUE_YET_TYPE || + head(ptr) === BROKEN_HEART_TYPE); +} + +function is_number_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === NUMBER_TYPE; +} + +function is_bool_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === BOOL_TYPE; +} + +function is_string_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === STRING_TYPE; +} + +function is_ptr_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === PTR_TYPE; +} + +function is_null_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === NULL_TYPE; +} + +function is_undefined_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === UNDEFINED_TYPE; +} + +function is_prog_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === PROG_TYPE; +} + +function is_no_value_yet_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === NO_VALUE_YET_TYPE; +} + +function is_broken_heart_ptr(ptr) { + return is_ptr(ptr) && head(ptr) === BROKEN_HEART_TYPE; +} + +// Primitive functions and constants + +const primitive_function_names_arities = list( + pair("display", 1), + pair("error", 1), + pair("+", 2), + pair("-", 2), + pair("*", 2), + pair("/", 2), + pair("%", 2), + pair("===", 2), + pair("!==", 2), + pair("<", 2), + pair("<=", 2), + pair(">", 2), + pair(">=", 2), + pair("!", 1), + pair("||", 2), + pair("&&", 2) +); + +const primitive_constants = list( + list("undefined", undefined), + list("math_PI" , math_PI) + ); + +function make_primitive_function(impl) { + return list("primitive", impl); +} + +function setup_environment() { + const primitive_function_names = + map(head, primitive_function_names_arities); + const primitive_function_values = + map(name => pair(make_primitive_function(name), false), + primitive_function_names); + const primitive_constant_names = + map(head, primitive_constants); + const primitive_constant_values = + map(f => pair(head(tail(f)), false), + primitive_constants); + return pair(pair( + append(primitive_function_names, + primitive_constant_names), + append(primitive_function_values, + primitive_constant_values)), + null); +} + +function flatten_list_to_vectors(the_heads, the_tails, lst, + make_ptr_fn, starting_index) { + let free = starting_index; + function helper(lst) { + if (!is_pair(lst)) { + return wrap_ptr(lst); + } else { + const index = free; + free = free + 1; + const elem = head(lst); + the_heads[index] = helper(elem); + the_tails[index] = helper(tail(lst)); + return make_ptr_fn(index); + } + } + helper(lst); + return free; +} + +// MACHINE +function get_contents(register) { + return register("get"); +} + +function set_contents(register, value) { + return register("set")(value); +} + +function make_stack() { + let stack = null; + + function push(x) { + stack = pair(x, stack); + return "done"; + } + + function pop() { + if (is_null(stack)) { + error("empty stack -- pop"); + + } else { + const top = head(stack); + stack = tail(stack); + return top; + } + } + + function initialize() { + stack = null; + return "done"; + } + + function dispatch(message) { + return message === "push" + ? push + : message === "pop" + ? pop() + : message === "initialize" + ? initialize() + : message === "stack" + ? stack + : error(message, "unknown request -- stack"); + } + + return dispatch; +} + +function make_new_machine() { + const SIZE = make_register("SIZE"); + const pc = make_register("pc"); + const flag = make_register("flag"); + const stack = make_stack(); + const stack_reassign_proc = make_register("stack_reassign_proc"); + const free = make_register("free"); + const root = make_register("root"); + const root_populate_proc = make_register("root_populate_proc"); + const root_restore_proc = make_register("root_restore_proc"); + const gc_registers = list( + list("free", free), + list("scan", make_register("scan")), + list("old", make_register("old")), + list("new", make_register("new")), + list("relocate_continue", make_register("relocate_continue")), + list("temp", make_register("temp")), + list("oldht", make_register("oldht")) + ); + const exp = make_register("exp"); + const env = make_register("env"); + const evaluator_registers = list( + list("exp", exp), + list("env", env), + list("val", make_register("val")), + list("continue", make_register("continue")), + list("fun", make_register("fun")), + list("argl", make_register("argl")), + list("unev", make_register("unev")), + list("fun", make_register("fun")) + ); + const aux_registers = list( + list("res", make_register("val")), + list("err", make_register("err")), + list("a", make_register("a")), + list("b", make_register("b")), + list("c", make_register("c")), + list("d", make_register("d")), + list("e", make_register("e")), + list("f", make_register("f")) + ); + const the_heads = make_register("the_heads"); + const the_tails = make_register("the_tails"); + set_contents(the_heads, make_vector()); + set_contents(the_tails, make_vector()); + const new_heads = make_register("new_heads"); + const new_tails = make_register("new_tails"); + set_contents(new_heads, make_vector()); + set_contents(new_tails, make_vector()); + const prog_heads = make_register("prog_heads"); + const prog_tails = make_register("prog_tails"); + let the_instruction_sequence = null; + let the_ops = list(list("initialize_stack", + () => stack("initialize"))); + the_ops = append(the_ops, vector_ops); + let register_table = + list(list("SIZE", SIZE), + list("pc", pc), + list("flag", flag), + list("root", root), + list("root_populate_proc", root_populate_proc), + list("root_restore_proc", root_restore_proc), + list("stack_reassign_proc", stack_reassign_proc), + list("the_heads", the_heads), + list("the_tails", the_tails), + list("new_heads", new_heads), + list("new_tails", new_tails), + list("prog_heads", prog_heads), + list("prog_tails", prog_tails)); + register_table = append(register_table, gc_registers); + register_table = append(register_table, evaluator_registers); + register_table = append(register_table, aux_registers); + + function start() { + const root_registers = + append(aux_registers, evaluator_registers); + set_contents(pc, the_instruction_sequence); + set_contents(free, + make_ptr_ptr(flatten_list_to_vectors( + the_heads("get"), + the_tails("get"), + setup_environment(), + make_ptr_ptr, + length(root_registers)))); + set_contents(env, make_ptr_ptr(length(root_registers))); + function root_populate_proc_fn() { + const root_ptr = free("get"); + root("set")(root_ptr); + let register_list = root_registers; + while (!is_null(register_list)) { + const content = head(tail(head(register_list)))("get"); + const index = unwrap_ptr(free("get")); + the_heads("get")[index] = + content === "*unassigned*" + ? make_null_ptr() : content; + free("set")(make_ptr_ptr(index + 1)); + the_tails("get")[index] = free("get"); + register_list = tail(register_list); + } + the_tails("get")[unwrap_ptr(free("get")) - 1] = + make_null_ptr(); + } + function root_restore_proc_fn() { + let root_ptr = root("get"); + let register_list = root_registers; + while (!is_null(register_list)) { + const index = unwrap_ptr(root_ptr); + const value = the_heads("get")[index]; + head(tail(head(register_list)))("set")(value); + root_ptr = the_tails("get")[index]; + register_list = tail(register_list); + } + } + function stack_reassign_proc_fn() { + let local_stack = stack("stack"); + while (!is_null(local_stack)) { + const value = head(local_stack); + if (is_ptr_ptr(value)) { + const index = unwrap_ptr(value); + const new_ptr = the_tails("get")[index]; + set_head(local_stack, new_ptr); + } else {} + local_stack = tail(local_stack); + } + } + set_contents(root_populate_proc, root_populate_proc_fn); + set_contents(root_restore_proc, root_restore_proc_fn); + set_contents(stack_reassign_proc, stack_reassign_proc_fn); + return execute(); + } + function allocate_register(name) { + if (is_undefined(assoc(name, register_table))) { + register_table = pair(list(name, make_register(name)), + register_table); + } else { + error(name, "multiply defined register"); + } + return "register allocated"; + } + function lookup_register(name) { + const val = assoc(name, register_table); + return is_undefined(val) + ? error(name, "unknown register") + : head(tail(val)); + } + function execute() { + const insts = get_contents(pc); + if (is_null(insts)) { + return "done"; + } else { + const proc = inst_execution_fun(head(insts)); + proc(); + return execute(); + } + } + function dispatch(message) { + return message === "start" + ? start + : message === "install_instruction_sequence" + ? seq => { the_instruction_sequence = seq; } + : message === "allocate_register" + ? allocate_register + : message === "get_register" + ? lookup_register + : message === "install_operations" + ? ops => { the_ops = append(the_ops, ops); } + : message === "stack" + ? stack + : message === "operations" + ? the_ops + : error(message, "unknown request -- machine"); + } + return dispatch; +} + +function make_machine(register_names, ops, controller) { + const machine = make_new_machine(); + + map(reg_name => machine("allocate_register")(reg_name), register_names); + machine("install_operations")(ops); + machine("install_instruction_sequence")(assemble(controller, machine)); + + return machine; +} + +function start(machine) { + return machine("start")(); +} + +function get_register_contents(machine, register_name) { + return get_contents(get_register(machine, register_name)); +} + +function set_register_contents(machine, register_name, value) { + set_contents(get_register(machine, register_name), value); + return "done"; +} + +function get_register(machine, reg_name) { + return machine("get_register")(reg_name); +} + + +// ASSEMBLER + +function assemble(controller, machine) { + function receive(insts, labels) { + update_insts(insts, labels, machine); + return insts; + } + + return extract_labels(controller, receive); +} + +function extract_labels(text, receive) { + function helper(insts, labels) { + const next_inst = head(text); + + return is_string(next_inst) + ? receive(insts, pair(make_label_entry(next_inst, insts), labels)) + : receive(pair(make_inst(next_inst), insts), labels); + } + + return is_undefined(text) || is_null(text) + ? receive(null, null) + : extract_labels(tail(text), helper); +} + +function update_insts(insts, labels, machine) { + const pc = get_register(machine, "pc"); + const flag = get_register(machine, "flag"); + const stack = machine("stack"); + const ops = machine("operations"); + + const set_iep = set_inst_execution_fun; + const make_ep = make_execution_function; + return map(i => set_iep(i, + make_ep(inst_controller_instruction(i), + labels, + machine, + pc, + flag, + stack, + ops)), + insts); +} + +function make_inst(inst_controller_instruction) { + return pair(inst_controller_instruction, null); +} + +function inst_controller_instruction(inst) { + return head(inst); +} + +function inst_execution_fun(inst) { + return tail(inst); +} + +function set_inst_execution_fun(inst, fun) { + set_tail(inst, fun); +} + +function make_label_entry(label_name, insts) { + return pair(label_name, insts); +} + +function lookup_label(labels, label_name) { + const val = assoc(label_name, labels); + + return is_undefined(val) + ? error(label_name, "undefined label -- assemble") + : tail(val); +} + +function make_execution_function(inst, labels, machine, pc, flag, stack, ops) { + const x = head(inst); + + return x === "assign" + ? make_assign_ef(inst, machine, labels, ops, pc) + : x === "test" + ? make_test_ef(inst, machine, labels, ops, flag, pc) + : x === "branch" + ? make_branch_ef(inst, machine, labels, flag, pc) + : x === "go_to" + ? make_go_to_ef(inst, machine, labels, pc) + : x === "save" + ? make_save_ef(inst, machine, stack, pc) + : x === "restore" + ? make_restore_ef(inst, machine, stack, pc) + : x === "perform" + ? make_perform_ef(inst, machine, labels, ops, pc) + : x === "dump_memory" // Added to allow printing the memory vectors + ? () => { + display(stringify(get_register_contents(machine, "the_heads"))); + display(stringify(get_register_contents(machine, "the_tails"))); + advance_pc(pc); + } + : error(inst, "unknown instruction type -- assemble"); +} + +function make_assign_ef(inst, machine, labels, operations, pc) { + const target = get_register(machine, assign_reg_name(inst)); + const value_exp = assign_value_exp(inst); + const value_fun = is_operation_exp(value_exp) + ? make_operation_exp_ef(value_exp, machine, labels, operations) + : make_primitive_exp_ef(value_exp, machine, labels); + + function perform_assign() { + set_contents(target, value_fun()); + advance_pc(pc); + } + + return perform_assign; +} + +function assign_reg_name(assign_instruction) { + return head(tail(assign_instruction)); +} + +function assign_value_exp(assign_instruction) { + return head(tail(tail(assign_instruction))); +} + +function assign(reg_name, value_exp) { + return list("assign", reg_name, value_exp); +} + +function dump_memory() { + return list("dump_memory", "the_heads", "the_tails"); +} + +function advance_pc(pc) { + set_contents(pc, tail(get_contents(pc))); + +} + +function make_test_ef(inst, machine, labels, operations, flag, pc) { + const condition = test_condition(inst); + + if (is_operation_exp(condition)) { + const condition_fun = make_operation_exp_ef(condition, machine, + labels, operations); + + function perform_test() { + set_contents(flag, unwrap_ptr(condition_fun())); + advance_pc(pc); + } + + return perform_test; + } else { + error(inst, "bad test instruction -- assemble"); + } +} + +function test_condition(test_instruction) { + return head(tail(test_instruction)); +} + +function test(condition) { + return list("test", condition); +} + +function make_branch_ef(inst, machine, labels, flag, pc) { + const dest = branch_dest(inst); + + if (is_label_exp(dest)) { + const insts = lookup_label(labels, label_exp_label(dest)); + + function perform_branch() { + if (get_contents(flag)) { + set_contents(pc, insts); + + } else { + advance_pc(pc); + } + } + + return perform_branch; + + } else { + error(inst, "bad branch instruction -- assemble"); + } +} + +function branch_dest(branch_instruction) { + return head(tail(branch_instruction)); +} + +function branch(dest) { + return list("branch", dest); +} + +function make_goto(inst, machine, labels, pc) { + const dest = goto_dest(inst); + + if (is_label_exp(dest)) { + const insts = lookup_label(labels, label_exp_label(dest)); + return () => set_contents(pc, insts); + + } else if (is_register_exp(dest)) { + const reg = get_register(machine, register_exp_reg(dest)); + return () => set_contents(pc, get_contents(reg)); + + } else { + error(inst, "bad go_to instruction -- assemble"); + } +} + +function goto_dest(goto_instruction) { + return head(tail(goto_instruction)); +} +/* +function go_to(dest) { + return list("go_to", dest); +} +*/ +function make_save_ef(inst, machine, stack, pc) { + const reg = get_register(machine, stack_inst_reg_name(inst)); + + function perform_save() { + push(stack, get_contents(reg)); + advance_pc(pc); + } + + return perform_save; +} + +function make_restore_ef(inst, machine, stack, pc) { + const reg = get_register(machine, stack_inst_reg_name(inst)); + + function perform_restore() { + set_contents(reg, pop(stack)); + advance_pc(pc); + } + + return perform_restore; +} + +function stack_inst_reg_name(stack_instruction) { + return head(tail(stack_instruction)); +} + +function save(register_name) { + return list("save", register_name); +} + +function restore(register_name) { + return list("restore", register_name); +} + +function make_perform_ef(inst, machine, labels, operations, pc) { + const action = perform_action(inst); + + if (is_operation_exp(action)) { + const action_fun = make_operation_exp_ef(action, machine, + labels, operations); + return () => { action_fun(); advance_pc(pc); }; + + } else { + error(inst, "bad perform instruction -- assemble"); + } +} + +function perform_action(inst) { + return head(tail(inst)); +} + +function perform(op) { + return list("perform", op); +} + + +function make_primitive_exp_ef(exp, machine, labels) { + if (is_constant_exp(exp)) { + const c = constant_exp_value(exp); + return () => c; + + } else if (is_label_exp(exp)) { + const insts = lookup_label(labels, label_exp_label(exp)); + return () => insts; + + } else if (is_register_exp(exp)) { + const r = get_register(machine, register_exp_reg(exp)); + return () => get_contents(r); + + } else { + error(exp, "unknown expression type -- assemble"); + } +} + +/* TODO: probably remove these -- suddenly available through new import chains +function is_register_exp(exp) { + return is_tagged_list(exp, "reg"); +} + +function register_exp_reg(exp) { + return head(tail(exp)); +} + +function reg(name) { + return list("reg", name); +} + +function is_constant_exp(exp) { + return is_tagged_list(exp, "constant"); +} + +function constant_exp_value(exp) { + return head(tail(exp)); +} + +function constant(value) { + return list("constant", wrap_ptr(value)); +} + +function is_label_exp(exp) { + return is_tagged_list(exp, "label"); +} + +function label_exp_label(exp) { + return head(tail(exp)); +} + +function label(string) { + return list("label", string); +} +*/ + +function make_operation_exp_ef(exp, machine, labels, operations) { + const op = lookup_prim(op_exp_op(exp), operations); + const aprocs = map(e => make_primitive_exp_ef(e, machine, labels), + operation_exp_operands(exp)); + + function perform_operation_exp() { + return op(map(p => p(), aprocs)); + } + + return perform_operation_exp; +} + +/* TODO: probably remove these -- suddenly available through new import chains +function is_operation_exp(exp) { + return is_tagged_list(head(exp), "op"); +} + +function operation_exp_operands(operation_exp) { + return tail(operation_exp); +} + +function op(name) { + return list("op", name); +} +*/ + +function op_exp_op(operation_exp) { + return head(tail(head(operation_exp))); +} + +function lookup_prim(symbol, operations) { + const val = assoc(symbol, operations); + + return is_undefined(val) + ? error(symbol, "unknown operation -- assemble") + : head(tail(val)); +} + +// PAIR OPERATIONS + +// head in "a", tail in "b" +const pair_controller = list( + "pair", + save("continue"), + assign("continue", label("pair_after_gc")), + test(list(op("==="), reg("free"), reg("SIZE"))), + branch(label("begin_garbage_collection")), + "pair_after_gc", + restore("continue"), + perform(list(op("vector_set"), reg("the_heads"), reg("free"), reg("a"))), + perform(list(op("vector_set"), reg("the_tails"), reg("free"), reg("b"))), + assign("res", reg("free")), + assign("free", list(op("inc_ptr"), reg("free"))), + go_to(reg("continue")) +); + + +function underlying_javascript_closure(fn) { + return args => apply_in_underlying_javascript(fn, args); +} + +function unwrap_args(fn) { + return args => fn(map(unwrap_ptr, args)); +} + +function wrap_return_value(fn) { + return args => wrap_ptr(fn(args)); +} + +function primitive_function(fn) { + return wrap_return_value(unwrap_args(underlying_javascript_closure(fn))); +} + +// 5.3 MEMORY MANAGEMENT + +function vector_ref(vector, idx) { + return vector[unwrap_ptr(idx)]; +} + +function vector_set(vector, idx, val) { + vector[unwrap_ptr(idx)] = val; +} + +function make_vector() { + return []; +} + +function inc_ptr(ptr) { + return make_ptr_ptr(unwrap_ptr(ptr) + 1); +} + +const vector_ops = list( + list("vector_ref", underlying_javascript_closure(vector_ref)), + list("vector_set", underlying_javascript_closure(vector_set)), + list("inc_ptr", underlying_javascript_closure(inc_ptr)) +); + +// MACHINE SETUP +const ptr_ops = + list( + list("make_ptr_ptr", + underlying_javascript_closure(make_ptr_ptr)), + list("make_null_ptr", + underlying_javascript_closure(make_null_ptr)), + list("make_no_value_yet_ptr", + underlying_javascript_closure(make_no_value_yet_ptr)), + list("make_prog_ptr", underlying_javascript_closure(make_prog_ptr)), + list("make_broken_heart_ptr", + underlying_javascript_closure(make_broken_heart_ptr)), + list("is_number_ptr", + wrap_return_value(underlying_javascript_closure(is_number_ptr))), + list("is_bool_ptr", + wrap_return_value(underlying_javascript_closure(is_bool_ptr))), + list("is_string_ptr", + wrap_return_value(underlying_javascript_closure(is_string_ptr))), + list("is_ptr_ptr", + wrap_return_value(underlying_javascript_closure(is_ptr_ptr))), + list("is_null_ptr", + wrap_return_value(underlying_javascript_closure(is_null_ptr))), + list("is_undefined_ptr", + wrap_return_value(underlying_javascript_closure(is_undefined_ptr))), + list("is_prog_ptr", + wrap_return_value(underlying_javascript_closure(is_prog_ptr))), + list("is_no_value_yet_ptr", + wrap_return_value(underlying_javascript_closure(is_no_value_yet_ptr))), + list("is_broken_heart_ptr", + wrap_return_value(underlying_javascript_closure(is_broken_heart_ptr))) +); + +const primitive_ops = list( + list("display", primitive_function(display)), + list("error", primitive_function(error)), + list("+", primitive_function((x, y) => x + y)), + list("-", primitive_function((x, y) => x - y)), + list("*", primitive_function((x, y) => x * y)), + list("/", primitive_function((x, y) => x / y)), + list("%", primitive_function((x, y) => x % y)), + list("===", primitive_function((x, y) => x === y)), + list("!==", primitive_function((x, y) => x !== y)), + list("<", primitive_function((x, y) => x < y)), + list("<=", primitive_function((x, y) => x <= y)), + list(">", primitive_function((x, y) => x > y)), + list(">=", primitive_function((x, y) => x >= y)), + list("!", primitive_function(x => !x)), + list("||", primitive_function((x, y) => x || y)), + list("&&", primitive_function((x, y) => x && y)) +); + +const gc_ops = list( + list("call_proc", underlying_javascript_closure(proc => proc())) +); + +const gc_controller = list( + "begin_garbage_collection", + perform(list(op("call_proc"), reg("root_populate_proc"))), + assign("free", list(op("make_ptr_ptr"), constant(0))), + assign("scan", list(op("make_ptr_ptr"), constant(0))), + assign("old", reg("root")), + assign("relocate_continue", label("reassign_root")), + go_to(label("relocate_old_result_in_new")), + "reassign_root", + assign("root", reg("new")), + go_to(label("gc_loop")), + "gc_loop", + test(list(op("==="), reg("scan"), reg("free"))), + branch(label("gc_flip")), + assign("old", list(op("vector_ref"), reg("new_heads"), reg("scan"))), + assign("relocate_continue", label("update_head")), + go_to(label("relocate_old_result_in_new")), + "update_head", + perform(list(op("vector_set"), reg("new_heads"), reg("scan"), reg("new"))), + assign("old", list(op("vector_ref"), reg("new_tails"), reg("scan"))), + assign("relocate_continue", label("update_tail")), + go_to(label("relocate_old_result_in_new")), + "update_tail", + perform(list(op("vector_set"), reg("new_tails"), reg("scan"), reg("new"))), + assign("scan", list(op("inc_ptr"), reg("scan"))), + go_to(label("gc_loop")), + "relocate_old_result_in_new", + test(list(op("is_ptr_ptr"), reg("old"))), + branch(label("gc_pair")), + assign("new", reg("old")), + go_to(reg("relocate_continue")), + "gc_pair", + assign("oldht", list(op("vector_ref"), reg("the_heads"), reg("old"))), + test(list(op("is_broken_heart_ptr"), reg("oldht"))), + branch(label("already_moved")), + assign("new", reg("free")), + // new location for pair + // Update free pointer + assign("free", list(op("inc_ptr"), reg("free"))), + // Copy the head and tail to new memory + perform(list(op("vector_set"), + reg("new_heads"), reg("new"), reg("oldht"))), + assign("oldht", list(op("vector_ref"), reg("the_tails"), reg("old"))), + perform(list(op("vector_set"), + reg("new_tails"), reg("new"), reg("oldht"))), + // Construct the broken heart + assign("oldht", list(op("make_broken_heart_ptr"))), + perform(list(op("vector_set"), + reg("the_heads"), reg("old"), reg("oldht"))), + perform(list(op("vector_set"), + reg("the_tails"), reg("old"), reg("new"))), + go_to(reg("relocate_continue")), + "already_moved", + assign("new", list(op("vector_ref"), reg("the_tails"), reg("old"))), + go_to(reg("relocate_continue")), + "gc_flip", + perform(list(op("call_proc"), reg("stack_reassign_proc"))), + assign("temp", reg("the_tails")), + assign("the_tails", reg("new_tails")), + assign("new_tails", reg("temp")), + assign("temp", reg("the_heads")), + assign("the_heads", reg("new_heads")), + assign("new_heads", reg("temp")), + perform(list(op("call_proc"), reg("root_restore_proc"))), + go_to(reg("continue")) +); + +const error_controller = list( + "error", + perform(list(op("error"), reg("res"), reg("err"))), + go_to(label("end_evaluation")) +); + +const begin_controller = list( + "fig_5_14", + "pair4", + assign("a", constant(4)), + assign("b", list(op("make_null_ptr"))), + assign("continue", label("garbage1")), + go_to(label("pair")), + /// The following creates a garbage + /// pair (9999, 9999) which will + /// not affect live object count at + /// the end of the program. You can + /// verify by adding more garbage + /// or remove this line. Or + /// uncomment the use of + /// dump_memory() below, before and + /// after GC. + "garbage1", + assign("a", constant(9999)), + assign("b", constant(9999)), + assign("continue", label("pair2")), + go_to(label("pair")), + "pair2", + assign("a", constant(3)), + assign("b", reg("res")), + assign("continue", label("garbage2")), + go_to(label("pair")), + /// The following creates a garbage + /// pair (9999, 9999) which will + /// not affect live object count at + /// the end of the program. You can + /// verify by adding more garbage + /// or remove this line. Or + /// uncomment the use of + /// dump_memory() below, before and + /// after GC. + "garbage2", + assign("a", constant(9999)), + assign("b", constant(9999)), + assign("continue", label("pair7")), + go_to(label("pair")), + "pair7", + assign("temp", reg("res")), + assign("a", constant(2)), + assign("b", list(op("make_null_ptr"))), + assign("continue", label("pair5")), + go_to(label("pair")), + "pair5", + assign("a", constant(1)), + assign("b", reg("res")), + assign("continue", label("pair1")), + go_to(label("pair")), + "pair1", + assign("a", reg("res")), + assign("b", reg("temp")), + assign("continue", label("done")), + go_to(label("pair")), + "done", + // dump_memory(), // uncomment to get a dump of heads and tails vectors + assign("continue", label("after_gc")), + go_to(label("begin_garbage_collection")), + "after_gc", + // dump_memory(), // uncomment to get a dump of heads and tails vectors + go_to(label("end_evaluation"))); + +const end_controller = list( + "end_evaluation" +); + +const ops = accumulate(append, null, list( + vector_ops, + ptr_ops, + gc_ops, + primitive_ops +)); + +const controller = accumulate(append, null, list( + begin_controller, + pair_controller, + gc_controller, + error_controller, + end_controller +)); + +function make_evaluator_machine(size) { + const evaluator_machine = make_machine(null, ops, controller); + set_register_contents(evaluator_machine, "SIZE", wrap_ptr(size)); + return evaluator_machine; +} + +const evaluator_machine = make_evaluator_machine(10000); + +set_register_contents(evaluator_machine, "a", wrap_ptr(206)); +set_register_contents(evaluator_machine, "b", wrap_ptr(40)); + +start(evaluator_machine); +get_register_contents(evaluator_machine, "free"); +// [ 'ptr', 108 ] The number of live objects in the program at termination + + [ 'ptr', 108 ] + +"gc_flip", + assign("temp", reg("the_tails")), + assign("the_tails", reg("new_tails")), + assign("new_tails", reg("temp")), + assign("temp", reg("the_heads")), + assign("the_heads", reg("new_heads")), + assign("new_heads", reg("temp")) + + + + + list-structured memory + memorylist-structured + garbage collection + stop-and-copy garbage collector + garbage collectorstop-and-copy + + +
diff --git a/xml/cn/chapter5/section4/section4.xml b/xml/cn/chapter5/section4/section4.xml new file mode 100644 index 000000000..f2eb28b2c --- /dev/null +++ b/xml/cn/chapter5/section4/section4.xml @@ -0,0 +1,265 @@ +
+ The Explicit-Control Evaluator + + + + +
+ A silicon-chip implementation of an evaluator for Scheme. + + +
+
+ + + + + + explicit-control evaluator for JavaScript + + + In section we saw how to + transform simple + + Scheme + JavaScript + + programs into descriptions of register + machines. We will now perform this transformation on a more complex + program, the metacircular evaluator of + sections, + which shows how the behavior of a + + Scheme + JavaScript + + interpreter can be described in terms of the + + + procedures + eval + + + functions + evaluate + + + and apply. + The explicit-control + evaluator that we develop in this section shows how the underlying + + procedure-calling + function-calling + + and argument-passing mechanisms used in the + evaluation process can be described in terms of operations on + registers and stacks. In addition, the explicit-control evaluator can + serve as an implementation of a + + Scheme + JavaScript + + interpreter, written in a language that is very similar to the native machine + language of conventional computers. The evaluator can be executed by the + register-machine simulator of section. + Alternatively, it can be used as a starting point for building a + machine-language implementation of a + + Scheme + JavaScript + + evaluator, or even a + Scheme chip + integrated-circuit implementation of Scheme + chip implementation of Scheme + Schemeintegrated-circuit implementation of + special-purpose machine for evaluating + + Scheme expressions. + JavaScript programs. + + Figure shows such a hardware + implementation: a silicon chip that acts as an evaluator for + + Scheme. + + Scheme, the language used in place of JavaScript in the original edition of this book. + + + The chip designers started with the data-path and controller specifications + for a register machine similar to the evaluator described in this section + and used design automation programs to construct the + integrated-circuit layout.See + Batali, John Dean + Batali et al.1982 for more + information on the chip and the method by which it was designed. + + + + + +
+ A silicon-chip implementation of an evaluator for Scheme. + Scheme chip + integrated-circuit implementation of Scheme + Schemeintegrated-circuit implementation of + chip implementation of Scheme + + +
+
+ + + + Registers and operations + + + explicit-control evaluator for JavaScriptdata paths + explicit-control evaluator for JavaScriptoperations + + + In designing the explicit-control evaluator, we must specify the + operations to be used in our register machine. We described the + metacircular evaluator in terms of abstract syntax, using + + procedures + functions + + such as + + quoted? + is_literal + + + and + make-procedure. + + make_function. + + + In implementing the + register machine, we could expand these + + procedures + functions + + into sequences of + elementary list-structure memory operations, and implement these + operations on our register machine. However, this would make our + evaluator very long, obscuring the basic structure with + details. To clarify the presentation, we will include as primitive + operations of the register machine the syntax + + procedures + functions + + given in + section and the + + procedures + functions + + for representing environments and other runtime data given in + sections + and. + In order to completely specify an evaluator that could be programmed + in a low-level machine language or implemented in hardware, we would + replace these operations by more elementary operations, using the + list-structure implementation we described in + section. + + + + Our + + Scheme + JavaScript + + evaluator register machine includes a stack and seven + registers: + explicit-control evaluator for JavaScriptregisters + comp register + + exp, + comp, + + env register + env, + val register + val, + continue registerexplicitin explicit-control evaluator + continue, + fun register + + + proc, + + + fun, + + + argl register + argl, and + unev register + unev. + + + Exp + + + The comp register + + + is used to hold the + + + expression + + + component + + + to be evaluated, and env contains the environment in + which the evaluation is to be performed. At the end of an evaluation, + val contains the value obtained by evaluating the + expressioncomponent + in the designated environment. The continue register is + used to implement recursion, as explained in section. (The evaluator needs to call itself recursively, since + evaluating an expressiona + component requires evaluating its + subexpressions.subcomponents.) The registers + + + proc, + + + fun, + + + argl, and unev are + used in evaluating function applications. + + + + We will not provide a data-path diagram to show how the registers and + operations of the evaluator are connected, nor will we give the + complete list of machine operations. These are implicit in the + evaluators controller, which will be presented in detail. + + + explicit-control evaluator for JavaScriptdata paths + + + &subsection5.4.1; + + + &subsection5.4.2; + + + &subsection5.4.3; + + + &subsection5.4.4; + +
+ diff --git a/xml/cn/chapter5/section4/subsection1.xml b/xml/cn/chapter5/section4/subsection1.xml new file mode 100644 index 000000000..65b6795d5 --- /dev/null +++ b/xml/cn/chapter5/section4/subsection1.xml @@ -0,0 +1,824 @@ + + + + The Core of the Explicit-Control Evaluator + The Dispatcher and Basic Evaluation + + + + + + + + explicit-control evaluator for Schemecontroller + + + explicit-control evaluator for JavaScriptcontroller + + + + + The central element in the evaluator is the sequence of instructions beginning at + + eval-dispatch. + eval_dispatch. + + This corresponds to the + + eval + evaluate + + + procedure + function + + of the metacircular evaluator described in section. When the controller starts at + + eval-dispatch, + eval_dispatch, + + + it evaluates the + expressioncomponent + specified by + + exp + comp + + in the environment specified by env. When evaluation is + complete, the controller will go to the entry point stored in + continue, and the val + register will hold the value of the + expression.component. + As with the metacircular + + eval, + evaluate, + + the structure of + + eval-dispatch + eval_dispatch + + + is a case analysis on the syntactic type of the + expressioncomponent + to be evaluated.In our controller, the dispatch is written as a sequence of + test and branch + instructions. Alternatively, it could have been written in a data-directed + + style (and in a real system it probably would have been) to avoid + style, which avoids + + the need to perform sequential tests and + + to facilitate + facilitates + + the definition of new + expressioncomponent + types. + + + A machine designed to run Lisp would probably include a + dispatch-on-type instruction + that would efficiently execute such data-directed + dispatches. + + + + + + + + + + + + + + + + + eval_dispatch + +eval-dispatch +(test (op self-evaluating?) (reg exp)) +(branch (label ev-self-eval)) +(test (op variable?) (reg exp)) +(branch (label ev-variable)) +(test (op quoted?) (reg exp)) +(branch (label ev-quoted)) +(test (op assignment?) (reg exp)) +(branch (label ev-assignment)) +(test (op definition?) (reg exp)) +(branch (label ev-definition)) +(test (op if?) (reg exp)) +(branch (label ev-if)) +(test (op lambda?) (reg exp)) +(branch (label ev-lambda)) +(test (op begin?) (reg exp)) +(branch (label ev-begin)) +(test (op application?) (reg exp)) +(branch (label ev-application)) +(goto (label unknown-expression-type)) + + +"eval_dispatch", + test(list(op("is_literal"), reg("comp"))), + branch(label("ev_literal")), + test(list(op("is_name"), reg("comp"))), + branch(label("ev_name")), + test(list(op("is_application"), reg("comp"))), + branch(label("ev_application")), + test(list(op("is_operator_combination"), reg("comp"))), + branch(label("ev_operator_combination")), + test(list(op("is_conditional"), reg("comp"))), + branch(label("ev_conditional")), + test(list(op("is_lambda_expression"), reg("comp"))), + branch(label("ev_lambda")), + test(list(op("is_sequence"), reg("comp"))), + branch(label("ev_sequence")), + test(list(op("is_block"), reg("comp"))), + branch(label("ev_block")), + test(list(op("is_return_statement"), reg("comp"))), + branch(label("ev_return")), + test(list(op("is_function_declaration"), reg("comp"))), + branch(label("ev_function_declaration")), + test(list(op("is_declaration"), reg("comp"))), + branch(label("ev_declaration")), + test(list(op("is_assignment"), reg("comp"))), + branch(label("ev_assignment")), + go_to(label("unknown_component_type")), + + + + + + Evaluating simple expressions + + + + + explicit-control evaluator for Schemeexpressions with no subexpressions to evaluate + + + explicit-control evaluator for JavaScriptexpressions with no subexpressions to evaluate + + + + + Numbers and strings (which are self-evaluating), + + variables, quotations, + names, + + and + + lambda + lambda + + expressions have no subexpressions to be evaluated. For these, the evaluator simply + places the correct value in the val register and + continues execution at the entry point specified by + continue. Evaluation of simple expressions is performed + by the following controller code: + + ev_literal + ev_name + ev_lambda + +ev-self-eval +(assign val (reg exp)) +(goto (reg continue)) +ev-variable +(assign val (op lookup-variable-value) (reg exp) (reg env)) +(goto (reg continue)) +ev-quoted +(assign val (op text-of-quotation) (reg exp)) +(goto (reg continue)) +ev-lambda +(assign unev (op lambda-parameters) (reg exp)) +(assign exp (op lambda-body) (reg exp)) +(assign val (op make-procedure) +(reg unev) (reg exp) (reg env)) +(goto (reg continue)) + + +"ev_literal", + assign("val", list(op("literal_value"), reg("comp"))), + go_to(reg("continue")), + +"ev_name", + assign("val", list(op("symbol_of_name"), reg("comp"), reg("env"))), + assign("val", list(op("lookup_symbol_value"), + reg("val"), reg("env"))), + go_to(reg("continue")), + +"ev_lambda", + assign("unev", list(op("lambda_parameter_symbols"), reg("comp"))), + assign("comp", list(op("lambda_body"), reg("comp"))), + assign("val", list(op("make_function"), + reg("unev"), reg("comp"), reg("env"))), + go_to(reg("continue")), + + + Observe how + + ev-lambda + ev_lambda + + uses the + + unev + unev + + and + + exp + comp + + registers to hold the parameters and body of the lambda expression so + that they can be passed to the + + make-procedure + make_function + + operation, along with the environment in env. + + + + + explicit-control evaluator for Schemeexpressions with no subexpressions to evaluate + + + explicit-control evaluator for JavaScriptexpressions with no subexpressions to evaluate + + + + + + + + + Evaluating procedure applications + + + + + explicit-control evaluator for Schemeprocedure application + explicit-control evaluator for Schemecombinations + A procedure application is specified by a combination containing an + operator and operands. The operator is a subexpression whose value is a + procedure, and the operands are subexpressions whose values are the + arguments to which the procedure should be applied. The metacircular + eval + handles applications by calling itself recursively to + evaluate each element of the combination, and then passing the results + to apply, which performs the actual + procedure + application. The + explicit-control evaluator does the same thing; these recursive calls + are implemented by + goto + instructions, together with + explicit-control evaluator for JavaScriptstack usage + use of the stack to save registers that will be restored after the recursive + call returns. Before each call we will be careful to identify which + registers must be saved (because their values will be needed + later).This is an important but subtle point in translating + algorithms from a procedural language, such as Lisp, + to a register-machine language. As an alternative to saving only what is + needed, we could save all the registers (except + val) before each recursive call. + This is called a + framed-stack discipline + stackframed + framed-stack discipline. This + would work but might save more registers than necessary; this could be + an important consideration in a system where stack operations are + expensive. Saving registers whose contents will not be needed later + may also hold on to useless data that could otherwise be + garbage-collected, freeing space to be reused. + + + We begin the evaluation of an application by evaluating the + operator to produce a procedure, which will later be applied to the evaluated + operands. To evaluate the operator, we move it to the + exp + register and go to eval-dispatch. + The environment in the env register is already + the correct one in which to evaluate the operator. + However, we save env because we will need it + later to evaluate the operands. We also extract the operands + into unev and save this on the stack. We set + up continue so that + eval-dispatch will resume at + ev-appl-did-operator + after the operator has been evaluated. First, however, we save the old value of + continue, which tells the controller where to + continue after the application. + + ev_operator_combination + ev_application + +ev-application + (save continue) + (save env) + (assign unev (op operands) (reg exp)) + (save unev) + (assign exp (op operator) (reg exp)) + (assign continue (label ev-appl-did-operator)) + (goto (label eval-dispatch)) + + + + explicit-control evaluator for Schemeoperand evaluation + + Upon returning from evaluating the operator subexpression, + we proceed to evaluate the operands of the combination + and to accumulate the resulting arguments in a list, held in + argl. + First we restore the unevaluated operands and the environment. We + initialize argl to an empty list. Then we + assign to the + proc + register the procedure that was produced by evaluating the operator. + If there are no operands, we go directly to + apply-dispatch. Otherwise we save + proc + on the stack and start the argument-evaluation + loop:We add to the evaluator data-structure + procedures + in section the following two + procedures for manipulating argument lists: + + empty_arglist + adjoin_arg + empty_arglist_duplicate + +(define (empty-arglist) '()) + +(define (adjoin-arg arg arglist) + (append arglist (list arg))) + + + We also use an additional syntax procedure + to test for the last + operand in a combination: + + is_last_argument_expression + is_last_argument_expression_duplicate + +(define (last-operand? ops) +(null? (cdr ops))) + + + + +ev-appl-did-operator + (restore unev) ; the operands + (restore env) + (assign argl (op empty-arglist)) + (assign proc (reg val)) ; the operator + (test (op no-operands?) (reg unev)) + (branch (label apply-dispatch)) + (save proc) + + + + + + Each cycle of the argument-evaluation loop evaluates an + operand + from the list in unev and accumulates the + result into argl. To evaluate an + operand, we place it in the + exp + register and go to + eval-dispatch, + after setting continue so that execution will + resume with the argument-accumulation phase. But first we save the + arguments accumulated so far (held in argl), the + environment (held in env), and the remaining + operands + to be evaluated (held in unev). A special case + is made for the evaluation of the last + operand which is handled at + ev-appl-last-arg. + + +ev-appl-operand-loop + (save argl) + (assign exp (op first-operand) (reg unev)) + (test (op last-operand?) (reg unev)) + (branch (label ev-appl-last-arg)) + (save env) + (save unev) + (assign continue (label ev-appl-accumulate-arg)) + (goto (label eval-dispatch)) + + + + + + When an operand has been evaluated, the value is accumulated into the list + held in argl. The operand + is then removed from the list of unevaluated operands + in unev, and + the argument-evaluation loop continues. + + +ev-appl-accumulate-arg + (restore unev) + (restore env) + (restore argl) + (assign argl (op adjoin-arg) (reg val) (reg argl)) + (assign unev (op rest-operands) (reg unev)) + (goto (label ev-appl-operand-loop)) + + + + + + Evaluation of the last argument is handled differently. There is no + need to save the environment or the list of unevaluated + operands before going to + eval-dispatch, + since they will not be required after the last + operand is evaluated. + Thus, we return from the evaluation to a special entry point + ev-appl-accum-last-arg, + which restores the argument list, accumulates the new argument, restores the + saved procedure, and goes off to perform the application.The + optimization of treating the last operand specially is known as + evlis tail recursion + evlis tail recursion (see + Wand, Mitchell + Wand 1980). We could be somewhat more efficient + in the argument evaluation loop if we made evaluation of the first + operand a special case too. This would permit us to postpone + initializing argl until after evaluating the + first operand, so + as to avoid saving argl in this case. The + compiler in section performs this + optimization. (Compare the + construct-arglist procedure + of section.) + + +ev-appl-last-arg + (assign continue (label ev-appl-accum-last-arg)) + (goto (label eval-dispatch)) +ev-appl-accum-last-arg + (restore argl) + (assign argl (op adjoin-arg) (reg val) (reg argl)) + (restore proc) + (goto (label apply-dispatch)) + + + + + + The details of the argument-evaluation loop determine the + order of evaluationin explicit-control evaluator + order in which the interpreter evaluates the + operands of a combination (e.g., + left to right or right to leftsee + exercise). This order is not + determined by the metacircular evaluator, which inherits its control + structure from the underlying Scheme in which it is implemented. + The order of operand evaluation in the metacircular evaluator is + determined by the order of evaluation of the arguments to + cons in the procedure + list-of-values of section (see exercise). + Because the first-operand selector (used in + ev-appl-operand-loop to extract successive + operands from unev) is implemented as + car and the + rest-operands selector is implemented as + cdr, the explicit-control evaluator will + evaluate the operands of a combination in left-to-right order. + + + explicit-control evaluator for Schemeoperand evaluation + + + + Procedure application + + + + + + The entry point + apply-dispatch + corresponds to the apply + procedure of the metacircular evaluator. By the time we get to + apply-dispatch, + the proc register contains the + procedure + to apply and argl contains the list of + evaluated arguments to which it must be applied. The saved value of + continue (originally passed to + eval-dispatch + and saved at + ev-application), + which tells where to return with the result of the + procedure application, is on the stack. When the application is complete, the + controller transfers to the entry point specified by the saved + continue, with the result of the application in + val. As with the metacircular + apply, there are two cases to consider. Either + the procedure to be applied is a primitive or it is a compound + procedure. + + apply_dispatch + +apply-dispatch + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-apply)) + (test (op compound-procedure?) (reg proc)) + (branch (label compound-apply)) + (goto (label unknown-procedure-type)) + + + + + + We assume that each + explicit-control evaluator for JavaScriptprimitive procedures + primitive is implemented so as to obtain its + arguments from argl and place its result in + val. To specify how the machine handles + primitives, we would have to provide a sequence of controller instructions + to implement each primitive and arrange for + primitive-apply + to dispatch to the + instructions for the primitive identified by the + contents of + proc. + Since we are interested in the structure of the evaluation process rather + than the details of the primitives, we will instead just use an + apply-primitive-procedure operation + that applies the + procedure in proc + to the arguments in argl. For the purpose of + simulating the evaluator with the simulator + of section we use the + procedure apply-primitive-procedure, + which calls on the underlying Scheme system to perform the application, + just as we did for the metacircular + evaluator in section. After computing + the value of the primitive application, we restore + continue and go + to the designated entry point. + + primitive_apply + +primitive-apply + (assign val (op apply-primitive-procedure) + (reg proc) + (reg argl)) + (restore continue) + (goto (reg continue)) + + + + + + explicit-control evaluator for JavaScriptcompound proceduresfunctions + To apply a compound procedure, we proceed just as with + the metacircular evaluator. We construct a frame + that binds the + procedures + parameters to the arguments, use this frame to extend the environment + carried by the procedure, + and evaluate in this extended environment + the sequence of expressions that forms the body of the + procedure. + Ev-sequence, described below + in section, + handles the evaluation of the sequence. + + compound_apply + +compound-apply + (assign unev (op procedure-parameters) (reg proc)) + (assign env (op procedure-environment) (reg proc)) + (assign env (op extend-environment) + (reg unev) (reg argl) (reg env)) + (assign unev (op procedure-body) (reg proc)) + (goto (label ev-sequence)) + + + + + Compound-apply is the only + place in the interpreter where the + env register is ever assigned a + new value. Just as in the metacircular evaluator, the new environment + is constructed from the environment carried by the + procedure, together with the argument list and the corresponding list of + variables to be bound. + + + explicit-control evaluator for Schemeprocedurefunction application + explicit-control evaluator for Schemecombinations + + + + + + + Conditionals + + + + + As with the metacircular evaluator, syntactic forms are handled by selectively + evaluating fragments of the component. For a + explicit-control evaluator for JavaScriptconditionals + conditional, we must evaluate the + predicate and decide, based on the value of predicate, whether to evaluate the + consequent or the alternative. + + + + Before evaluating the predicate, we save the conditional itself, which is in + comp, so that we can later extract the + consequent or alternative. To evaluate the predicate expression, we move it to + the comp register and go to + eval_dispatch. The environment in the + env register is already the correct one in + which to evaluate the predicate. However, we save + env because we will need it later to + evaluate the consequent or the alternative. We set up + continue so that evaluation will resume at + ev_conditional_decide after the predicate + has been evaluated. First, however, we save the old value of + continue, which we will need later in order + to return to the evaluation of the statement that is waiting for the value of + the conditional. + + ev_conditional + +"ev_conditional", + save("comp"), // save conditional for later + save("env"), + save("continue"), + assign("continue", label("ev_conditional_decide")), + assign("comp", list(op("conditional_predicate"), reg("comp"))), + go_to(label("eval_dispatch")), // evaluate the predicate + + + + + + + When we resume at ev_conditional_decide after + evaluating the predicate, we test whether it was true or false + and, depending on the result, place either the consequent or the alternative in + comp before going to + eval_dispatch. + In this chapter, we will use the function + is_falsywhy used in explicit-control evaluator + is_falsy to test the value of the predicate. + This allows us to write the consequent and alternative branches in the same + order as in a conditional, and simply fall through to the consequent + branch when the predicate holds. The function is_falsy + is declared as the opposite of the + is_truthy function used to test predicates of + conditionals in section. + Notice that restoring + env and + continue here sets up + eval_dispatch to have the correct environment + and to continue at the right place to receive the value of the conditional. + + +"ev_conditional_decide", + restore("continue"), + restore("env"), + restore("comp"), + test(list(op("is_falsy"), reg("val"))), + branch(label("ev_conditional_alternative")), +"ev_conditional_consequent", + assign("comp", list(op("conditional_consequent"), reg("comp"))), + go_to(label("eval_dispatch")), +"ev_conditional_alternative", + assign("comp", list(op("conditional_alternative"), reg("comp"))), + go_to(label("eval_dispatch")), + + + + + + + Sequence Evaluation + + + + explicit-control evaluator for JavaScriptsequences of statements + + + The 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. + + + The entries at ev_sequence_next and + ev_sequence_continue form a loop that + successively evaluates each statement in a sequence. + The list of unevaluated + statements is kept in unev. + At ev_sequence we place the sequence of + statements to be evaluated in unev. If the + sequence is empty, we set val to undefined and jump to + continue via + ev_sequence_empty. Otherwise we start the + sequence-evaluation loop, first saving the value of continue on the stack, because + the continue register will be used for local flow of control in the loop, and the original + value is needed for continuing after the statement sequence. Before evaluating + each statement, we check to see if there are additional statements to be evaluated + in the sequence. If so, we save the rest of the unevaluated statements (held in + unev) and the environment in which these must + be evaluated (held in env) and call + eval_dispatch to evaluate the statement, + which has been placed in comp. The + two saved registers are restored after this evaluation, at + ev_sequence_continue. + + + + The final statement in the sequence is handled differently, at the entry point + ev_sequence_last_statement. Since there are + no more statements to be evaluated after this one, we need not save + unev or + env before going to + eval_dispatch. The value of the whole + sequence is the value of the last statement, so after the evaluation of the last + statement there is nothing left to do except continue at the entry point that was + saved at ev_sequence. + Rather than setting up continue to arrange + for eval_dispatch to return here and then + restoring continue from the stack and + continuing at that entry point, we restore + continue from the stack before going to + eval_dispatch, so that + eval_dispatch will continue at that entry + point after evaluating the statement. + + + + ev_sequence + +"ev_sequence", + assign("unev", list(op("sequence_statements"), reg("comp"))), + test(list(op("is_empty_sequence"), reg("unev"))), + branch(label("ev_sequence_empty")), + save("continue"), +"ev_sequence_next", + assign("comp", list(op("first_statement"), reg("unev"))), + test(list(op("is_last_statement"), reg("unev"))), + branch(label("ev_sequence_last_statement")), + save("unev"), + save("env"), + assign("continue", label("ev_sequence_continue")), + go_to(label("eval_dispatch")), +"ev_sequence_continue", + restore("env"), + restore("unev"), + assign("unev", list(op("rest_statements"), reg("unev"))), + go_to(label("ev_sequence_next")), +"ev_sequence_last_statement", + restore("continue"), + go_to(label("eval_dispatch")), + +"ev_sequence_empty", + assign("val", constant(undefined)), + go_to(reg("continue")), + + + + + Unlike eval_sequence in the metacircular + evaluator, ev_sequence does not need to check whether a return statement was + evaluated so as to terminate the sequence evaluation. The explicit + control in this evaluator allows a return statement to jump directly to + the continuation of the current function application without resuming the + sequence evaluation. Thus sequence evaluation does not need to be concerned + with returns, or even be aware of the existence of return statements in the + language. Because a return statement jumps out of the sequence-evaluation code, + the restores of saved registers at ev_sequence_continue + wont be executed. We will see later how the return statement removes these values from the stack. + + + explicit-control evaluator for JavaScriptsequences of statements + + + + + diff --git a/xml/cn/chapter5/section4/subsection2.xml b/xml/cn/chapter5/section4/subsection2.xml new file mode 100644 index 000000000..e6e991548 --- /dev/null +++ b/xml/cn/chapter5/section4/subsection2.xml @@ -0,0 +1,958 @@ + + + + + Sequence Evaluation and Tail Recursion + + + Evaluating Function Applications + + + + + + + + + The portion of the explicit-control evaluator at + ev-sequence is analogous to the metacircular + evaluators eval-sequence procedure. It + handles sequences of expressions in procedure bodies or in explicit + begin expressions. + + + + Explicitbegin expressions are evaluated by + placing the sequence of expressions to be evaluated in + unev, saving + continue on the stack, and jumping to + ev-sequence. + + ev_sequence_start + +ev-begin + (assign unev (op begin-actions) (reg exp)) + (save continue) + (goto (label ev-sequence)) + + + + The implicit sequences in procedure bodies are handled by jumping to + ev-sequence from + compound-apply, at which point + continue is already on the stack, having + been saved at ev-application. + + + + The entries at ev-sequence and + ev-sequence-continue form a loop that successively + evaluates each expression in a sequence. The list of unevaluated expressions is + kept in unev. Before evaluating each expression, we + check to see if there are additional expressions to be evaluated in the + sequence. If so, we save the rest of the unevaluated expressions (held in + unev) and the environment in which these must be + evaluated (held in env) and call + eval-dispatch to evaluate the expression. The two + saved registers are restored upon the return from this evaluation, at + ev-sequence-continue. + + + + The final expression in the sequence is handled differently, at the entry point + ev-sequence-last-exp. Since there are no more + expressions to be evaluated after this one, we need not save + unev or env before + going to eval-dispatch. expression, so after the + evaluation of the last expression there is nothing left to do except continue at + the entry point currently held on the stack (which was saved by + ev-application or + ev-begin.) Rather than setting up + continue to arrange for + eval-dispatch to return here and then restoring + continue from the stack and continuing at that + entry point, we restore continue from the stack + before going to eval-dispatch, so that + eval-dispatch will continue at that entry point + after evaluating the expression. + + ev_sequence + +ev-sequence + (assign exp (op first-exp) (reg unev)) + (test (op last-exp?) (reg unev)) + (branch (label ev-sequence-last-exp)) + (save unev) + (save env) + (assign continue (label ev-sequence-continue)) + (goto (label eval-dispatch)) +ev-sequence-continue + (restore env) + (restore unev) + (assign unev (op rest-exps) (reg unev)) + (goto (label ev-sequence)) +ev-sequence-last-exp + (restore continue) + (goto (label eval-dispatch)) + + + + + + + + explicit-control evaluator for JavaScriptfunction application + explicit-control evaluator for JavaScriptcombinations + + + A function application is specified by a combination containing a function + expression and argument expressions. The function expression is a subexpression + whose value is a function, and the argument expressions are subexpressions whose + values are the arguments to which the function should be applied. The metacircular + evaluate handles applications by calling + itself recursively to evaluate each element of the combination, and then passing + the results to apply, which performs the + actual function application. The explicit-control evaluator does the same thing; + these recursive calls are implemented by + go_to instructions, together with use of the + stack to save registers that will be restored after the recursive call returns. + Before each call we will be + explicit-control evaluator for JavaScriptstack usage + careful to identify which registers must be saved + (because their values will be needed later).This is an important but + subtle point in translating algorithms from a procedural language, such as + JavaScript, to a register-machine language. As an alternative to saving only what + is needed, we could save all the registers (except + val) before each recursive call. This is + called a + framed-stack discipline + stackframed + framed-stack discipline. + This would work but might save more registers than necessary; this could be an + important consideration in a system where stack operations are expensive. Saving + registers whose contents will not be needed later may also hold on to useless data + that could otherwise be garbage-collected, freeing space to be reused. + + + + As in the metacircular evaluator, operator combinations are transformed into + applications of primitive functions corresponding to the operators. This takes + place at ev_operator_combination, which + performs this transformation in place in comp + and falls through to + ev_application. + We assume that the syntax transformer + operator_combination_to_application is + available as a machine operation. In an actual implementation built from + scratch, we would use our explicit-control evaluator to interpret a JavaScript + program that performs source-level transformations like this one and + function_decl_to_constant_decl + in a syntax phase that runs before execution. + + + + + + We begin the evaluation of an application by evaluating the function expression to + produce a function, which will later be applied to the evaluated argument + expressions. To evaluate the function expression, we move it to the + comp register and go to + eval_dispatch. The environment in the + env register is already the correct one in + which to evaluate the function expression. However, we save + env because we will need it later to evaluate + the argument expressions. We also extract the argument expressions into + unev and save this on the stack. We set up + continue so that + eval_dispatch will resume at + ev_appl_did_function_expression after the + function expression has been evaluated. First, however, we save the old value of + continue, which tells the controller where to + continue after the application. + + + ev_operator_combination + ev_application + +"ev_operator_combination", + assign("comp", list(op("operator_combination_to_application"), + reg("comp"), reg("env"))), +"ev_application", + save("continue"), + save("env"), + assign("unev", list(op("arg_expressions"), reg("comp"))), + save("unev"), + assign("comp", list(op("function_expression"), reg("comp"))), + assign("continue", label("ev_appl_did_function_expression")), + go_to(label("eval_dispatch")), + + + + + explicit-control evaluator for JavaScriptargument evaluation + + + Upon returning from evaluating the function expression, we proceed to evaluate the + argument expressions of the application and to accumulate the resulting arguments + in a list, held in argl. (This is like the + evaluation of a sequence of statements, except that we collect the values.) First + we restore the unevaluated argument expressions and the environment. We initialize + argl to an empty list. Then we assign to the + fun register the function that was produced + by evaluating the function expression. If there are no argument expressions, we go + directly to apply_dispatch. Otherwise we save + fun on the stack and start the + argument-evaluation loop:We add to the evaluator data-structure + functions in section the following + two functions for manipulating argument lists: + + empty_arglist + adjoin_arg + empty_arglist + +function empty_arglist() { return null; } + +function adjoin_arg(arg, arglist) { + return append(arglist, list(arg)); +} + + + We also make use of an additional syntax function to test for the last argument expression + in an application: + + is_last_argument_expression + is_last_argument_expression + +function is_last_argument_expression(arg_expression) { + return is_null(tail(arg_expression)); +} + + + + +"ev_appl_did_function_expression", + restore("unev"), // the argument expressions + restore("env"), + assign("argl", list(op("empty_arglist"))), + assign("fun", reg("val")), // the function + test(list(op("is_null"), reg("unev"))), + branch(label("apply_dispatch")), + save("fun"), + + + + + + Each cycle of the argument-evaluation loop evaluates an argument expression from + the list in unev and accumulates the result + into argl. To evaluate an argument + expression, we place it in the comp register + and go to eval_dispatch, after setting + continue so that execution will resume with + the argument-accumulation phase. But first we save the arguments accumulated so + far (held in argl), the environment (held in + env), and the remaining argument expressions + to be evaluated (held in unev). A special + case is made for the evaluation of the last argument expression, which is handled + at ev_appl_last_arg. + + + +"ev_appl_argument_expression_loop", + save("argl"), + assign("comp", list(op("head"), reg("unev"))), + test(list(op("is_last_argument_expression"), reg("unev"))), + branch(label("ev_appl_last_arg")), + save("env"), + save("unev"), + assign("continue", label("ev_appl_accumulate_arg")), + go_to(label("eval_dispatch")), + + + + + + When an argument expression has been evaluated, the value is accumulated into the + list held in argl. The argument expression is + then removed from the list of unevaluated argument expressions in + unev, and the argument-evaluation loop + continues. + + + +"ev_appl_accumulate_arg", + restore("unev"), + restore("env"), + restore("argl"), + assign("argl", list(op("adjoin_arg"), reg("val"), reg("argl"))), + assign("unev", list(op("tail"), reg("unev"))), + go_to(label("ev_appl_argument_expression_loop")), + + + + + + Evaluation of the last argument expression is handled differently, as is the last + statement in a sequence. There is no need to save the environment or the list of + unevaluated argument expressions before going to + eval_dispatch, since they will not be + required after the last argument expression is evaluated. Thus, we return from the + evaluation to a special entry point + ev_appl_accum_last_arg, which restores the + argument list, accumulates the new argument, restores the saved function, and goes + off to perform the application.The optimization of treating the last + argument expression specially is known as + evlis tail recursion + evlis tail recursion (see + Wand, Mitchell + Wand 1980). + We could be somewhat more efficient in the argument evaluation loop if we made + evaluation of the first argument expression a special case too. This would permit + us to postpone initializing argl until after + evaluating the first argument expression, so as to avoid saving + argl in this case. The compiler in + section performs this optimization. (Compare + the construct_arglist function of + section.) + + + +"ev_appl_last_arg", + assign("continue", label("ev_appl_accum_last_arg")), + go_to(label("eval_dispatch")), +"ev_appl_accum_last_arg", + restore("argl"), + assign("argl", list(op("adjoin_arg"), reg("val"), reg("argl"))), + restore("fun"), + go_to(label("apply_dispatch")), + + + + + + The details of the argument-evaluation loop determine the + order of evaluationin explicit-control evaluator + order in which the + interpreter evaluates the argument expressions of a combination (e.g., left to + right or right to left—see exercise). + This order is not determined by the metacircular evaluator, which inherits its + control structure from the underlying JavaScript in which it is implemented. + The order of argument-expression evaluation by the function + list_of_values in the metacircular + evaluator is determined by the order of evaluation of the arguments to + pair, which is used to construct the + argument list. + order of evaluationin metacircular evaluator + The version of + list_of_values in footnote of section + calls pair directly; the version in the + text uses map, which calls + pair. (See exercise.) + + Because we use head in ev_appl_argument_expression_loop + to extract successive argument expressions from unev + and tail at + ev_appl_@accumulate_arg to extract the rest of the argument expressions, + the explicit-control evaluator will + evaluate the argument expressions of a combination in left-to-right order, + as required by the ECMAScript specification. + + + explicit-control evaluator for JavaScriptargument evaluation + + + + Function Application + + + + + + + The entry point apply_dispatch corresponds to + the apply function of the metacircular + evaluator. By the time we get to + apply_dispatch, the + fun register contains the function to apply + and argl contains the list of evaluated + arguments to which it must be applied. The saved value of + continue (originally passed to + eval_dispatch and saved at + ev_application), which tells where to return + with the result of the function application, is on the stack. When the application + is complete, the controller transfers to the entry point specified by the saved + continue, with the result of the application + in val. As with the metacircular + apply, there are two cases to consider. + Either the function to be applied is a primitive or it is a compound function. + + + apply_dispatch + +"apply_dispatch", + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_apply")), + test(list(op("is_compound_function"), reg("fun"))), + branch(label("compound_apply")), + go_to(label("unknown_function_type")), + + + + + + We assume that each + explicit-control evaluator for JavaScriptprimitive functions + primitive is implemented so as to obtain its arguments from + argl and place its result in + val. To specify how the machine handles + primitives, we would have to provide a sequence of controller instructions to + implement each primitive and arrange for + primitive_apply to dispatch to the + instructions for the primitive identified by the contents of + fun. Since we are interested in the structure + of the evaluation process rather than the details of the primitives, we will + instead just use an apply_primitive_function + operation that applies the function in fun to the arguments in + argl. For the purpose of simulating the + evaluator with the simulator of section we use + the function apply_primitive_function, which + calls on the underlying JavaScript system to perform the application, just as we + did for the metacircular evaluator in section. After computing the value of the primitive + application, we restore continue and go to + the designated entry point. + + primitive_apply + +"primitive_apply", + assign("val", list(op("apply_primitive_function"), + reg("fun"), reg("argl"))), + restore("continue"), + go_to(reg("continue")), + + + + + + The sequence of instructions labeled + compound_apply specifies the application of + explicit-control evaluator for JavaScriptcompound functions + compound functions. To apply a compound function, we proceed in a way similar to + what we did in the metacircular evaluator. We construct a frame that binds the + function's parameters to the arguments, use this frame to extend the environment + carried by the function, and evaluate in this extended environment the body of the + function. + + + + At this point the compound function is in register + fun and its arguments are in + argl. We extract the function's parameters + into unev and its environment into + env. We then replace the environment in + env with the environment constructed by + extending it with bindings of the parameters to the given arguments. We then + extract the body of the function into comp. + The natural next step would be to restore the saved + continue and proceed to + eval_dispatch to evaluate the body and go to + the restored continuation with the result in + val, as is done for the last statement of a + sequence. But there is a complication! + + + + The complication has two aspects. One is that + at any point in the evaluation of the body, a + explicit-control evaluator for JavaScriptreturn statements + return statement may require the + function to return the value of the return expression as the value of the body. + But a return statement may be nested arbitrarily deeply in the body; so the stack + at the moment the return statement is encountered is not necessarily the stack + that is needed for a return from the function. One way to make it possible to + adjust the stack for the return is to put a \emph{marker} on the stack that can be + found by the return code. This is implemented by the + register-machine languagepush_marker_to_stack + push_marker_to_stack (in register machine) + push_marker_to_stack instruction. The return + code can then use the + register-machine languagerevert_stack_to_marker + revert_stack_to_marker (in register machine) + revert_stack_to_marker + instruction to restore the stack to the place indicated by the marker before + evaluating the return expression.The special instructions + push_marker_to_stack and + revert_stack_to_marker are not strictly + necessary and could be implemented by explicitly pushing and popping a marker + value onto and off the stack. Anything that could not be confused with a value in + the program can be used as a marker. See exercise. + + + + + The other aspect of the complication is that if the evaluation of the body + terminates without executing a return statement, the value of the body must be + undefined. To handle this, we set up the + continue register to point to the entry point + return_undefined before going off to + eval_dispatch to evaluate the body. If a + return statement is not encountered during evaluation of the body, evaluation of + the body will continue at return_undefined. + + compound_apply + +"compound_apply", + assign("unev", list(op("function_parameters"), reg("fun"))), + assign("env", list(op("function_environment"), reg("fun"))), + assign("env", list(op("extend_environment"), + reg("unev"), reg("argl"), reg("env"))), + assign("comp", list(op("function_body"), reg("fun"))), + push_marker_to_stack(), + assign("continue", label("return_undefined")), + go_to(label("eval_dispatch")), + + + + + + The only places in the interpreter where the + env register is assigned a new value are + compound_apply and + ev_block (section). Just as in the metacircular evaluator, + the new environment for evaluation of a function body is constructed from the + environment carried by the function, together with the argument list and the + corresponding list of names to be bound. + + + explicit-control evaluator for JavaScriptfunction application + explicit-control evaluator for JavaScriptcombinations + + + When a return statement is evaluated at + ev_return, we use the + revert_stack_@to_marker instruction to restore + the stack to its state at the beginning of the function call by removing all + values from the stack down to and including the marker. As a consequence, + restore("continue") will restore the + continuation of the function call, which was saved at + ev_application. We then proceed to evaluate + the return expression, whose result will be placed in + val and thus be the value returned from the + function when we continue after the evaluation of the return expression. + + + ev_return + +"ev_return", + revert_stack_to_marker(), + restore("continue"), + assign("comp", list(op("return_expression"), reg("comp"))), + go_to(label("eval_dispatch")), + + + + + + If no return statement is encountered during evaluation of the function body, + return valueundefined as + evaluation continues at return_undefined, the + continuation that was set up at + compound_apply. To return + undefined from the function, we put + undefined into + val and go to the entry point that was put + onto the stack at ev_application. Before we + can restore that continuation from the stack, however, we must remove the marker + that was saved at compound_apply. + + + return_undefined + +"return_undefined", + revert_stack_to_marker(), + restore("continue"), + assign("val", constant(undefined)), + go_to(reg("continue")), + + + + + + + + + + + Return Statements and + + + Tail Recursion + + + + + + + + explicit-control evaluator for JavaScripttail recursion + + + tail recursionexplicit-control evaluator and + return statementhandling in explicit-control evaluator + + + In chapter we said that the process described by a + + procedure + function + + such as + + + +(define (sqrt-iter guess x) + (if (good-enough? guess x) + guess + (sqrt-iter (improve guess x) + x))) + + +function sqrt_iter(guess, x) { + return is_good_enough(guess, x) + ? guess + : sqrt_iter(improve(guess, x), x); +} + + + + is an iterative process. Even though the + + procedure + function + + is syntactically recursive (defined in terms of itself), it is not logically + necessary for an evaluator to save information in passing from one call to + + sqrt-iter + sqrt_iter + + to the next.We saw in + section how to + implement such a process with a register machine that had no stack; the + state of the process was stored in a fixed set of registers. An + evaluator that can execute a + + procedure + function + + such as + + sqrt-iter + sqrt_iter + + without requiring increasing storage as the + + procedure + function + + continues to call itself is called a + tail-recursive evaluator + metacircular evaluator for JavaScripttail recursion and + tail recursionmetacircular evaluator and + return statementtail recursion and + tail-recursive evaluator. + + + + + The metacircular implementation of the evaluator in chapter + does not specify whether the evaluator is tail-recursive, because that + evaluator inherits its mechanism for saving state from the underlying + Scheme. With the explicit-control evaluator, however, we can trace + through the evaluation process to see when procedure calls cause a net + accumulation of information on the stack. + + + + The metacircular implementation of the evaluator in chapter isn't + tail-recursive. It implements a return statement as a + constructor of a return value object containing the value to be + returned and inspects the result of a function call to see whether it is + such an object. If the evaluation of a function body produces a return value + object, the return value of the function is the contents of that object; + otherwise, the return value is + undefined. Both the construction of the + return value object and the eventual inspection of the result of the + function call are deferred operations, which lead to an accumulation of + information on the stack. + + + + + + + + Our evaluator is tail-recursive, because in order to evaluate the final + expression of a sequence we transfer directly to + eval-dispatch without saving any information on the + stack. Hence, evaluating the final expression in a sequenceeven if it + is a procedure call (as in sqrt-iter, where the + if expression, which is the last expression in the + procedure body, reduces to a call to + sqrt-iter)will not cause any information + to be accumulated on the stack. + + If we did not think to take advantage of the fact that it was unnecessary to + save information in this case, we might have implemented + eval-sequence by treating all the expressions in a + sequence in the same waysaving the registers, evaluating the + expression, returning to restore the registers, and repeating this until all the + expressions have been evaluated:We can define + no-more-exps? as follows: + + + +(define (no-more-exps? seq) (null? seq)) + + + + ev-sequencewithout tail recursion + +ev-sequence + (test (op no-more-exps?) (reg unev)) + (branch (label ev-sequence-end)) + (assign exp (op first-exp) (reg unev)) + (save unev) + (save env) + (assign continue (label ev-sequence-continue)) + (goto (label eval-dispatch)) +ev-sequence-continue + (restore env) + (restore unev) + (assign unev (op rest-exps) (reg unev)) + (goto (label ev-sequence)) +ev-sequence-end + (restore continue) + (goto (reg continue)) + + + + + + + Our explicit-control evaluator is tail-recursive, because it does not need to wrap up + return values for inspection and thus avoids the buildup of stack from deferred operations. + At ev_return, in order to evaluate the expression that + computes the return value of a function, we transfer directly to + eval_dispatch with nothing more on the stack + than right before the function call. We accomplish this by undoing any saves to + the stack by the function (which are useless because we are returning) using + revert_stack_to_marker. Then, rather than arranging + for eval_dispatch to come back here and then + restoring continue from the stack and + continuing at that entry point, we restore + continue from the stack before going to + eval_dispatch so that + eval_dispatch will continue at that entry + point after evaluating the expression. Finally, we transfer to + eval_dispatch without saving any information + on the stack. Thus, when we proceed to evaluate a return expression, the stack is + the same as just before the call to the function whose return value we are about + to compute. Hence, evaluating a return expression—even if it is a function call + (as in sqrt_iter, where the conditional + expression reduces to a call to + sqrt_iter)—will not cause any information to + accumulate on the stack.This implementation of tail recursion is one + variety of a well-known optimization technique used by many compilers. In + compiling a function that ends with a function call, one can replace the call by a + jump to the called function's entry point. Building this strategy into the + interpreter, as we have done in this section, provides the optimization uniformly + throughout the language. + + + + If we did not think to take advantage of the fact that it is unnecessary to + hold on to the useless information on the stack while evaluating a return + expression, we might have taken the straightforward approach of evaluating + the return expression, coming back to restore the stack, and finally + continuing at + the entry point that is waiting for the result of the function call: + + + +"ev_return", // alternative implementation: not tail-recursive + assign("comp", list(op("return_expression"), reg("comp"))), + assign("continue", label("ev_restore_stack")), + go_to(label("eval_dispatch")), +"ev_restore_stack", + revert_stack_to_marker(), // undo saves in current function + restore("continue"), // undo save at $\texttt{ev\char`_application}$ + go_to(reg("continue")), + + + + + + + + + + This may seem like a minor change to our previous code for evaluation of + a sequencereturn statements: + + The only difference is that we go through the save-restore + cycle for the last expression in a sequence as well as for the + othersdelay undoing any register saves to the stack until + after the evaluation of the return expression. + + The interpreter will still give the same value for any expression. But this change + is fatal to the tail-recursive implementation, because we must now come back after + evaluating the final expression in a + sequencereturn expression in order + to undo the (useless) register saves. + + These extra saves will accumulate during a nest of + procedurefunction + calls. + + Consequently, processes such as + sqrt-itersqrt_iter + will require space proportional to the number of iterations rather than requiring + constant space. + + This difference can be significant. For example, + iterative processimplemented by procedurefunction call + with tail recursion, an infinite loop can be expressed using only the + + procedure-call mechanism: + function-call and return mechanisms: + + + +(define (count n) + (newline) + (display n) + (count (+ n 1))) + + +function count(n) { + display(n); + return count(n + 1); +} + + + + Without tail recursion, such a + + procedure + function + + would eventually run out of stack space, and expressing a true iteration + would require some control mechanism other than + + procedure + function + + call. + + + + + + Note that our JavaScript implementation requires the use of + tail recursionreturn statement necessary for + return in order to be tail-recursive. + Because the undoing of the register saves takes place at + ev_return, removing + return from the + count function above will cause it to + eventually run out of stack space. This explains the use of + return in the infinite driver loops in + chapter. + + + explicit-control evaluator for JavaScriptreturn statements + + + + + + + + Explain how the + explicit-control evaluator for JavaScripttail recursion + tail recursionexplicit-control evaluator and + stack builds up if return is + removed from count: + + +function count(n) { + display(n); + count(n + 1); +} + + + + + + + + Implement the equivalent of push_marker_to_stack by + using save at + compound_apply to store a special marker + value on the stack. Implement the equivalent of + revert_@stack_@to_@marker at + ev_return and + return_undefined as a loop that repeatedly + performs a restore until it hits the + marker. Note that this will require restoring a value to a register other than the one it + was saved from. (Although we are careful to avoid that in our evaluator, our stack + implementation actually allows it. See exercise .) + This is necessary because the only way to pop from the stack is by + restoring to a register. + + Hint: You will need to create a unique constant to serve as the marker, for + example with const marker = list("marker"). + Because list creates a new pair, it cannot be + === to anything else on the stack. + + + + + + + Implement + register-machine languagepush_marker_to_stack + push_marker_to_stack (in register machine) + push_marker_to_stack and + register-machine languagerevert_stack_to_marker + revert_stack_to_marker (in register machine) + revert_stack_to_marker as register-machine + instructions, following the implementation of + save and + restore in section. Add functions + push_marker and + pop_marker to access stacks, mirroring the + implementation of push and + pop in section. Note that you do not + need to actually insert a marker into the stack. Instead, you can add a local + state variable to the stack model to keep track of the position of the last save + before each push_marker_to_stack. + If you choose to put a marker on the stack, see the hint in + exercise. + + + tail recursionexplicit-control evaluator and + + + + + + + explicit-control evaluator for JavaScripttail recursion + + + + diff --git a/xml/cn/chapter5/section4/subsection3.xml b/xml/cn/chapter5/section4/subsection3.xml new file mode 100644 index 000000000..378ab50bb --- /dev/null +++ b/xml/cn/chapter5/section4/subsection3.xml @@ -0,0 +1,363 @@ + + + + + Conditionals, Assignments and Definitions + + + Blocks, Assignments, and Declarations + + + + + + + + + + + + As with the metacircular evaluator, special syntactic forms are handled by selectively + evaluating fragments of the expression. For an if + expression, we must evaluate the predicate and decide, based on the value of + predicate, whether to evaluate the consequent or the alternative. + + + + Before evaluating the predicate, we save the if + expression itself so that we can later extract the consequent or alternative. We also + save the environment, which we will need later in order to evaluate the consequent or + the alternative, and we save continue, which we will need + later in order to return to the evaluation of the expression that is waiting for the + value of the if. + + +ev-if +(save exp) ; save expression for later +(save env) +(save continue) +(assign continue (label ev-if-decide)) +(assign exp (op if-predicate) (reg exp)) +(goto (label eval-dispatch)) ; evaluate the predicate + + + + + + When we return from evaluating the predicate, we test whether it was true or false + and, depending on the result, place either the consequent or the alternative in + exp before going to + eval-dispatch. Notice that restoring + env and continue here sets + up eval-dispatch to have the correct environment and to + continue at the right place to receive the value of the + if expression. + + +ev-if-decide +(restore continue) +(restore env) +(restore exp) +(test (op true?) (reg val)) +(branch (label ev-if-consequent)) +ev-if-alternative +(assign exp (op if-alternative) (reg exp)) +(goto (label eval-dispatch)) +ev-if-consequent +(assign exp (op if-consequent) (reg exp)) +(goto (label eval-dispatch)) + + + + + + + Blocks + + + explicit-control evaluator for JavaScriptblocks + explicit-control evaluator for JavaScriptdeclarations + + + The body of a block is evaluated with respect to the current environment + extended by a frame that binds all local names to the value + "*unassigned*". We temporarily + make use of the val register to + hold the list of all variables declared in the block, which is obtained + by + scanning out declarationsin explicit-control evaluator + scan_out_declarations + from section. The functions + scan_@out_@declarations and + list_of_unassigned are assumed + to be available as machine operations. + Footnote + suggests that an actual implementation would perform + syntax transformations before program execution. + In the same vein, names declared in blocks should be scanned out + in a preprocessing step rather than each time a block is evaluated. + + + ev_block + +"ev_block", + assign("comp", list(op("block_body"), reg("comp"))), + assign("val", list(op("scan_out_declarations"), reg("comp"))), + + save("comp"), // so we can use it to temporarily hold $\texttt{*unassigned*}$ values + assign("comp", list(op("list_of_unassigned"), reg("val"))), + assign("env", list(op("extend_environment"), + reg("val"), reg("comp"), reg("env"))), + restore("comp"), // the block body + go_to(label("eval_dispatch")), + + + + + + + + + + Assignments and + + definitions + declarations + + + + + + Assignments + explicit-control evaluator for JavaScriptassignments + are handled by + + ev-assignment, which is + ev_assignment, + + + reached from + + eval-dispatch + eval_@dispatch + + + with the assignment expression in + exp. + comp. + The + code at + + + ev-assignment + ev_assignment + + + first evaluates the value part of the expression and then installs the new + value in the environment. + + Set-variable-value! + + The function + assign_symbol_value + + + is assumed to be available as a machine operation. + + ev_assignment + +ev-assignment +(assign unev (op assignment-variable) (reg exp)) +(save unev) ; save variable for later +(assign exp (op assignment-value) (reg exp)) +(save env) +(save continue) +(assign continue (label ev-assignment-1)) +(goto (label eval-dispatch)) ; evaluate the assignment value +ev-assignment-1 +(restore continue) +(restore env) +(restore unev) +(perform +(op set-variable-value!) (reg unev) (reg val) (reg env)) +(assign val (const ok)) +(goto (reg continue)) + + +"ev_assignment", + assign("unev", list(op("assignment_symbol"), reg("comp"))), + save("unev"), // save variable for later + assign("comp", list(op("assignment_value_expression"), reg("comp"))), + save("env"), + save("continue"), + assign("continue", label("ev_assignment_install")), + go_to(label("eval_dispatch")), // evaluate assignment value +"ev_assignment_install", + restore("continue"), + restore("env"), + restore("unev"), + perform(list(op("assign_symbol_value"), + reg("unev"), reg("val"), reg("env"))), + go_to(reg("continue")), + + + + + + + + Definitions are handled in a similar way: + + + Declarations + explicit-control evaluator for JavaScriptdeclarations + of variables and constants are handled in a similar way. + Note that whereas the value of an assignment is the value that was assigned, + the value of a declaration is + undefined. This is handled by + setting val to + undefined before continuing. + As in the metacircular evaluator, we transform a function declaration + into a constant declaration whose value expression is a lambda expression. This happens at + ev_function_declaration, which makes the + transformation in place in comp and + falls through to ev_declaration. + + + + ev_function_declaration + ev_declaration + +ev-definition +(assign unev (op definition-variable) (reg exp)) +(save unev) ; save variable for later +(assign exp (op definition-value) (reg exp)) +(save env) +(save continue) +(assign continue (label ev-definition-1)) +(goto (label eval-dispatch)) ; evaluate the definition value +ev-definition-1 +(restore continue) +(restore env) +(restore unev) +(perform +(op define-variable!) (reg unev) (reg val) (reg env)) +(assign val (const ok)) +(goto (reg continue)) + + +"ev_function_declaration", + assign("comp", + list(op("function_decl_to_constant_decl"), reg("comp"))), +"ev_declaration", + assign("unev", list(op("declaration_symbol"), reg("comp"))), + save("unev"), // save declared name + assign("comp", + list(op("declaration_value_expression"), reg("comp"))), + save("env"), + save("continue"), + assign("continue", label("ev_declaration_assign")), + go_to(label("eval_dispatch")), // evaluate declaration value +"ev_declaration_assign", + restore("continue"), + restore("env"), + restore("unev"), + perform(list(op("assign_symbol_value"), + reg("unev"), reg("val"), reg("env"))), + assign("val", constant(undefined)), + go_to(reg("continue")), + + + + + + + + + derived componentadding to explicit-control evaluator + explicit-control evaluator for Schemederived expressions + explicit-control evaluator for Schemespecial forms (additional) + Extend the evaluator to handle derived expressions such as + cond, + let, and so on + (section). + You may cheat and assume that the syntax + transformers such as cond->if are available + as machine operations.This isnt really cheating. In an + actual implementation built from scratch, we would use our explicit-control + evaluator to interpret a Scheme program that performs source-level + transformations + + like cond->if + + in a syntax phase that runs before execution. + + + Extend the evaluator to handle + derived componentadding to explicit-control evaluator + explicit-control evaluator for JavaScriptderived components + explicit-control evaluator for JavaScriptsyntactic forms (additional) + while loops, by translating them to applications of a function + while_loop, as shown in + exercise. + You can paste the declaration of the function + while_loop in front of user programs. + You may cheat by assuming that the syntax transformer + while_to_application is available + as a machine operation. Refer to + exercise to discuss whether + this approach works if return, break, and continue statements are allowed + inside the while loop. If not, + how can you modify the explicit-control evaluator to run programs + with while loops that include these statements? + + + + + + + + Implement cond as a new basic + explicit-control evaluator for Schemesyntactic forms (additional) + special form + without reducing it to if. You will have to + construct a loop that tests the predicates of successive + cond clauses until you find one that is + true, and then use ev-sequence to evaluate + the actions of the clause. + + + + + + + Modify the evaluator so that it uses + explicit-control evaluator for JavaScriptnormal-order evaluation + normal-order evaluationexplicitin explicit-control evaluator + normal-order evaluation, + based on the lazy evaluator of + section. + + + diff --git a/xml/cn/chapter5/section4/subsection4.xml b/xml/cn/chapter5/section4/subsection4.xml new file mode 100644 index 000000000..e07bd5e92 --- /dev/null +++ b/xml/cn/chapter5/section4/subsection4.xml @@ -0,0 +1,1356 @@ + + + Running the Evaluator + + + + + explicit-control evaluator for JavaScriptrunning + + + With the implementation of the explicit-control evaluator we come to + the end of a development, begun in chapter, in which we have + explored successively more precise + models of evaluation + evaluationmodels of + models of the evaluation process. + We started with the relatively informal substitution model, then + extended this in chapter to the environment model, which enabled us + to deal with state and change. In the metacircular evaluator of + chapter, we used + + Scheme + JavaScript + + itself as a language for making more + explicit the environment structure constructed during evaluation of an + + expression. + component. + + Now, with register machines, we have taken a close look + at the evaluators mechanisms for storage management, + argument passing, and control. At + each new level of description, we have had to raise issues and resolve + ambiguities that were not apparent at the previous, less precise + treatment of evaluation. To understand the behavior of the + explicit-control evaluator, we can simulate it and monitor its + performance. + + + + We will install a + explicit-control evaluator for JavaScriptdriver loop + driver loopexplicitin explicit-control evaluator + driver loop in our evaluator machine. This plays + the role of the + + driver-loop + driver_loop + + + procedure + function + + of section. The evaluator + will repeatedly print a prompt, read + + an expression, + a program, + + evaluate + + the expression + the program + + by going to + + + eval-dispatch, + + + eval_dispatch, + + + and print the result. + + + If nothing is entered at the prompt, we jump to the label + evaluator_done, which is + the last entry point in the controller. + + + The following instructions form the beginning of the + explicit-control evaluators controller sequence:We assume + here that + + + read + + + user_read, + parse, + + + and the various printing + operations are available as primitive machine operations, which is useful + for our simulation, but completely unrealistic in practice. These are + actually extremely complex operations. In practice, + + they + reading and printing + + would be + implemented using low-level input-output operations such as transferring + single characters to and from a device. + + +

+ To support the + get-global-environment operation we define + + the-global-environment + get-global-environment + the_global + + (define the-global-environment (setup-environment)) + + (define (get-global-environment) + the-global-environment) + + +
+
+
+ + promptsexplicit-control evaluator + read_evaluate_print_loop + print_result + + read-eval-print-loop + (perform (op initialize-stack)) + (perform + (op prompt-for-input) (const ";;; EC-Eval input:")) + (assign exp (op read)) + (assign env (op get-global-environment)) + (assign continue (label print-result)) + (goto (label eval-dispatch)) + print-result + (perform + (op announce-output) (const ";;; EC-Eval value:")) + (perform (op user-print) (reg val)) + (goto (label read-eval-print-loop)) + + +"read_evaluate_print_loop", + perform(list(op("initialize_stack"))), + assign("comp", list(op("user_read"), + constant("EC-evaluate input:"))), + assign("comp", list(op("parse"), reg("comp"))), + test(list(op("is_null"), reg("comp"))), + branch(label("evaluator_done")), + assign("env", list(op("get_current_environment"))), + assign("val", list(op("scan_out_declarations"), reg("comp"))), + save("comp"), // so we can use it to temporarily hold $\texttt{*unassigned*}$ values + assign("comp", list(op("list_of_unassigned"), reg("val"))), + assign("env", list(op("extend_environment"), + reg("val"), reg("comp"), reg("env"))), + perform(list(op("set_current_environment"), reg("env"))), + restore("comp"), // the program + assign("continue", label("print_result")), + go_to(label("eval_dispatch")), +"print_result", + perform(list(op("user_print"), + constant("EC-evaluate value:"), reg("val"))), + go_to(label("read_evaluate_print_loop")), + + + + + + + + We store the current environment, initially the global environment, + in the variable current_@environment + and update it each time around the loop to remember past declarations. + The operations + get_@current_@environment and + set_@current_@environment + simply get and set this variable. + + get_current_environment + set_current_environment + the_global + +let current_environment = the_global_environment; + +function get_current_environment() { + return current_environment; +} + +function set_current_environment(env) { + current_environment = env; +} + + + + +
+ + + When we encounter an + error handlingin explicit-control evaluator + explicit-control evaluator for JavaScripterror handling + error in a + + procedure + function + + (such as the + + unknown procedure type error + unknown function type error + + indicated at + + apply-dispatch), + apply_dispatch), + + + we print an error message and return to the driver loop.There are + other errors that we would like the interpreter to handle, but these are not + so simple. See exercise. + + unknown_component_type + unknown_function_type + signal_error + + unknown-expression-type + (assign val (const unknown-expression-type-error)) + (goto (label signal-error)) + + unknown-procedure-type + (restore continue) ; clean up stack (from apply-dispatch) + (assign val (const unknown-procedure-type-error)) + (goto (label signal-error)) + + signal-error + (perform (op user-print) (reg val)) + (goto (label read-eval-print-loop)) + + +"unknown_component_type", + assign("val", constant("unknown syntax")), + go_to(label("signal_error")), + +"unknown_function_type", + restore("continue"), // clean up stack (from $\texttt{apply_dispatch}$) + assign("val", constant("unknown function type")), + go_to(label("signal_error")), + +"signal_error", + perform(list(op("user_print"), + constant("EC-evaluator error:"), reg("val"))), + go_to(label("read_evaluate_print_loop")), + + + + + + For the purposes of the simulation, we initialize the stack each time + through the driver loop, since it might not be empty after an error + + + (such as an undefined variable) + + + (such as an undeclared name) + + + interrupts an evaluation.We + could perform the stack initialization only after errors, but doing it in + the driver loop will be convenient for monitoring the evaluators + performance, as described below. + + + explicit-control evaluator for JavaScriptcontroller + + + If we combine all the code fragments presented in sections + , + we can create an + explicit-control evaluator for JavaScriptmachine model + evaluator machine model that we can run using the + register-machine simulator of section. + + + eceval + + (define eceval + (make-machine + '(exp env val proc argl continue unev) + eceval-operations + '( + read-eval-print-loop + $\langle$entire machine controller as given above$\rangle$ + ))) + + +const eceval = make_machine(list("comp", "env", "val", "fun", + "argl", "continue", "unev"), + eceval_operations, + list("read_evaluate_print_loop", + entire machine controller as given above + "evaluator_done")); + + + + eceval + functions_4_1_1 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + user_print + the_global + make_machine + start + eceval_operations + eceval_controller + +const eceval = + make_machine(list("comp", "env", "val", "fun", + "argl", "continue", "unev"), + eceval_operations, + eceval_controller); + + + + eceval_controller + +const eceval_controller = + list( + "read_evaluate_print_loop", + perform(list(op("initialize_stack"))), + assign("comp", list(op("user_read"), constant("EC-evaluate input:"))), + test(list(op("is_null"), reg("comp"))), + branch(label("evaluator_done")), + assign("comp", list(op("parse"), reg("comp"))), + assign("env", list(op("get_current_environment"))), + assign("val", list(op("scan_out_declarations"), reg("comp"))), + save("comp"), // temporarily store to comp + assign("comp", list(op("list_of_unassigned"), reg("val"))), + assign("env", list(op("extend_environment"), + reg("val"), reg("comp"), reg("env"))), + perform(list(op("set_current_environment"), reg("env"))), + restore("comp"), + assign("continue", label("print_result")), + go_to(label("eval_dispatch")), + + "print_result", + perform(list(op("user_print"), + constant("EC-evaluate value:"), reg("val"))), + go_to(label("read_evaluate_print_loop")), + + "eval_dispatch", + test(list(op("is_literal"), reg("comp"))), + branch(label("ev_literal")), + test(list(op("is_name"), reg("comp"))), + branch(label("ev_name")), + // Unsorted + test(list(op("is_operator_combination"), reg("comp"))), + branch(label("ev_operator_combination")), + test(list(op("is_function_declaration"), reg("comp"))), + branch(label("ev_function_declaration")), + test(list(op("is_operator_combination"), reg("comp"))), + branch(label("ev_operator_combination")), + // Treat let/const the same + test(list(op("is_declaration"), reg("comp"))), + branch(label("ev_declaration")), + test(list(op("is_assignment"), reg("comp"))), + branch(label("ev_assignment")), + test(list(op("is_return_statement"), reg("comp"))), + branch(label("ev_return")), + test(list(op("is_conditional"), reg("comp"))), + branch(label("ev_conditional")), + test(list(op("is_lambda_expression"), reg("comp"))), + branch(label("ev_lambda")), + test(list(op("is_sequence"), reg("comp"))), + branch(label("ev_sequence_start")), + test(list(op("is_block"), reg("comp"))), + branch(label("ev_block")), + test(list(op("is_application"), reg("comp"))), + branch(label("ev_application")), + go_to(label("unknown_component_type")), + + "ev_return", + revert_stack_to_marker(), + restore("continue"), + assign("comp", list(op("return_expression"), reg("comp"))), + go_to(label("eval_dispatch")), + + "ev_literal", + assign("val", list(op("literal_value"), reg("comp"))), + go_to(reg("continue")), + + "ev_name", + assign("val", list(op("symbol_of_name"), reg("comp"))), + assign("val", list(op("lookup_symbol_value"), reg("val"), reg("env"))), + go_to(reg("continue")), + + "ev_lambda", + assign("unev", list(op("lambda_parameter_symbols"), reg("comp"))), + assign("comp", list(op("lambda_body"), reg("comp"))), + assign("val", list(op("make_function"), + reg("unev"), reg("comp"), reg("env"))), + go_to(reg("continue")), + + "ev_operator_combination", + assign("comp", list(op("operator_combination_to_application"), + reg("comp"))), + + "ev_application", + save("continue"), + save("env"), + assign("unev", list(op("arg_expressions"), reg("comp"))), + save("unev"), + assign("comp", list(op("function_expression"), reg("comp"))), + assign("continue", label("ev_appl_did_function_expression")), + go_to(label("eval_dispatch")), + + "ev_appl_did_function_expression", + restore("unev"), // the args + restore("env"), + assign("argl", list(op("empty_arglist"))), + assign("fun", reg("val")), // the function_expression + test(list(op("is_null"), + reg("unev"))), + branch(label("apply_dispatch")), + save("fun"), + + "ev_appl_argument_expression_loop", + save("argl"), + assign("comp", list(op("head"), reg("unev"))), + test(list(op("is_last_argument_expression"), + reg("unev"))), + branch(label("ev_appl_last_arg")), + save("env"), + save("unev"), + assign("continue", label("ev_appl_accumulate_arg")), + go_to(label("eval_dispatch")), + + "ev_appl_accumulate_arg", + restore("unev"), + restore("env"), + restore("argl"), + assign("argl", list(op("adjoin_arg"), + reg("val"), reg("argl"))), + assign("unev", list(op("tail"), reg("unev"))), + go_to(label("ev_appl_argument_expression_loop")), + + "ev_appl_last_arg", + assign("continue", label("ev_appl_accum_last_arg")), + go_to(label("eval_dispatch")), + + "ev_appl_accum_last_arg", + restore("argl"), + assign("argl", list(op("adjoin_arg"), + reg("val"), reg("argl"))), + restore("fun"), + go_to(label("apply_dispatch")), + + "apply_dispatch", + test(list(op("is_primitive_function"), + reg("fun"))), + branch(label("primitive_apply")), + test(list(op("is_compound_function"), + reg("fun"))), + branch(label("compound_apply")), + go_to(label("unknown_function_type")), + + "primitive_apply", + assign("val", list(op("apply_primitive_function"), + reg("fun"), + reg("argl"))), + restore("continue"), + go_to(reg("continue")), + + "compound_apply", + assign("unev", list(op("function_parameters"), reg("fun"))), + assign("env", list(op("function_environment"), reg("fun"))), + assign("env", list(op("extend_environment"), + reg("unev"), reg("argl"), reg("env"))), + assign("comp", list(op("function_body"), reg("fun"))), + push_marker_to_stack(), + assign("continue", label("return_undefined")), + go_to(label("eval_dispatch")), + + "return_undefined", + assign("val", constant(undefined)), + revert_stack_to_marker(), + restore("continue"), + go_to(reg("continue")), + + "ev_block", + assign("comp", list(op("block_body"), reg("comp"))), + assign("val", list(op("scan_out_declarations"), reg("comp"))), + + save("comp"), // temporarily store to comp + assign("comp", list(op("list_of_unassigned"), reg("val"))), + assign("env", list(op("extend_environment"), + reg("val"), + reg("comp"), + reg("env"))), + restore("comp"), + go_to(label("eval_dispatch")), + +"ev_sequence_start", + assign("unev", list(op("sequence_statements"), reg("comp"))), + test(list(op("is_empty_sequence"), reg("unev"))), + branch(label("ev_empty_sequence")), + save("continue"), + +"ev_sequence", + assign("comp", list(op("first_statement"), reg("unev"))), + test(list(op("is_last_statement"), reg("unev"))), + branch(label("ev_sequence_last_statement")), + save("unev"), + save("env"), + assign("continue", label("ev_sequence_continue")), + go_to(label("eval_dispatch")), + +"ev_sequence_continue", + restore("env"), + restore("unev"), + assign("unev", list(op("rest_statements"), reg("unev"))), + go_to(label("ev_sequence")), + +"ev_sequence_last_statement", + restore("continue"), + go_to(label("eval_dispatch")), + +"ev_sequence_empty", + assign("val", constant(undefined)), + go_to(reg("continue")), + + "ev_conditional", + save("comp"), // save expression for later + save("env"), + save("continue"), + assign("continue", label("ev_conditional_decide")), + assign("comp", list(op("conditional_predicate"), reg("comp"))), + go_to(label("eval_dispatch")), // evaluate the predicate + + "ev_conditional_decide", + restore("continue"), + restore("env"), + restore("comp"), + test(list(op("is_falsy"), reg("val"))), + branch(label("ev_conditional_alternative")), + + "ev_conditional_consequent", + assign("comp", list(op("conditional_consequent"), reg("comp"))), + go_to(label("eval_dispatch")), + + "ev_conditional_alternative", + assign("comp", list(op("conditional_alternative"), reg("comp"))), + go_to(label("eval_dispatch")), + + "ev_assignment", + assign("unev", list(op("assignment_symbol"), reg("comp"))), + save("unev"), // save variable for later + assign("comp", list(op("assignment_value_expression"), reg("comp"))), + save("env"), + save("continue"), + assign("continue", label("ev_assignment_1")), + go_to(label("eval_dispatch")), // evaluate assignment value + + "ev_assignment_1", + restore("continue"), + restore("env"), + restore("unev"), + perform(list(op("assign_symbol_value"), + reg("unev"), reg("val"), reg("env"))), + go_to(reg("continue")), + + "ev_function_declaration", + assign("comp", list(op("function_decl_to_constant_decl"), reg("comp"))), + + "ev_declaration", + assign("unev", list(op("declaration_symbol"), + reg("comp"))), + save("unev"), // save variable for later + assign("comp", list(op("declaration_value_expression"), + reg("comp"))), + save("env"), + save("continue"), + assign("continue", label("ev_declaration_assign")), + go_to(label("eval_dispatch")), // evaluate declaration value + + "ev_declaration_assign", + restore("continue"), + restore("env"), + restore("unev"), + perform(list(op("assign_symbol_value"), + reg("unev"), reg("val"), reg("env"))), + assign("val", constant(undefined)), + go_to(reg("continue")), + + // Error handling + "unknown_component_type", + assign("val", constant("Unknown expression type")), + go_to(label("signal_error")), + + "unknown_function_type", + restore("continue"), /// clean up stack (from apply_dispatch) + assign("val", constant("Unknown function type")), + go_to(label("signal_error")), + + "signal_error", + perform(list(op("user_print"), + constant("EC-evaluator error:"), reg("comp"))), + go_to(label("read_evaluate_print_loop")), + + "evaluator_done" + ); + + + + We must define + + Scheme procedures + JavaScript functions + + to simulate the operations used as primitives by the evaluator. These are + the same + + procedures + functions + + we used for the metacircular evaluator in + section, together with the few additional + ones defined in footnotes throughout section. + + +(define eceval-operations + (list (list 'self-evaluating? self-evaluating) + \textit{complete list of operations for eceval machine})) + + +const eceval_operations = list(list("is_literal", is_literal), + $\langle\mathit{complete}\;\,\mathit{list}\;\,\mathit{of}\;\mathit{operations}\:\,\mathit{for}\;\,\mathit{eceval}\;\,\mathit{machine}\rangle$); + + + + prompt_for_input + +function prompt_for_input(input_prompt) { + const input = prompt(input_prompt); + if (is_null(input)) { + display("--- evaluator terminated ---"); + return null; + } else { + display(input_prompt + "\n" + input + "\n----------------------------"); + return parse(input); + } +} + + + + + eceval_operations + user_read + empty_arglist + is_last_argument_expression + +const eceval_operations = + list( + // args + list("arg_expressions" , arg_expressions), + list("function_expression" , function_expression), + list("is_null" + , is_null), + list("head" , head), + list("is_last_argument_expression", is_last_argument_expression), + list("tail" , tail), + + //arg + list("empty_arglist" , empty_arglist), + list("adjoin_arg" , adjoin_arg), + + // comp (sequence) + list("first_statement" , first_statement), + list("rest_statements" , rest_statements), + list("is_last_statement" , is_last_statement), + list("is_empty_sequence" , is_empty_sequence), + list("sequence_statements" , sequence_statements), + + // eval functions from meta-circular evaluator + list("is_literal" , is_literal), + list("literal_value" , literal_value), + list("is_name" , is_name), + list("symbol_of_name" , symbol_of_name), + list("is_assignment" , is_assignment), + list("assignment_symbol" , assignment_symbol), + list("assignment_value_expression" + , assignment_value_expression), + list("assign_symbol_value" , assign_symbol_value), + list("is_declaration" , is_declaration), + list("declaration_symbol" , declaration_symbol), + list("declaration_value_expression" + , declaration_value_expression), + list("assign_symbol_value" , assign_symbol_value), + list("is_lambda_expression", is_lambda_expression), + list("lambda_parameter_symbols" + , lambda_parameter_symbols), + list("lambda_body" , lambda_body), + list("is_return_statement" , is_return_statement), + list("return_expression" , return_expression), + list("is_conditional" + , is_conditional), + list("conditional_predicate" + , conditional_predicate), + list("conditional_consequent" + , conditional_consequent), + list("conditional_alternative" + , conditional_alternative), + list("is_sequence" , is_sequence), + list("is_block" , is_block), + list("block_body" , block_body), + list("scan_out_declarations" + , scan_out_declarations), + list("list_of_unassigned" , list_of_unassigned), + list("is_application" , is_application), + list("is_primitive_function" + , is_primitive_function), + list("apply_primitive_function" + , apply_primitive_function), + list("is_compound_function", is_compound_function), + list("function_parameters" , function_parameters), + list("function_environment", function_environment), + list("function_body" , function_body), + list("extend_environment" , extend_environment), + list("make_function" , make_function), + + list("lookup_symbol_value" , lookup_symbol_value), + + list("get_current_environment" + , get_current_environment), + list("set_current_environment" + , set_current_environment), + + // Unsorted + list("is_function_declaration" , is_function_declaration), + list("function_declaration_body" , function_declaration_body), + list("function_declaration_parameters" , function_declaration_parameters), + list("function_declaration_name" , function_declaration_name), + list("function_decl_to_constant_decl", function_decl_to_constant_decl), + list("declaration_symbol" , declaration_symbol), + list("is_operator_combination", is_operator_combination), + list("operator_combination_to_application", operator_combination_to_application), + list("parse", parse), + + // generic helpers + list("is_truthy", is_truthy), + list("is_falsy", v => !is_truthy(v)), + list("is_null", is_null), + + list("user_read", user_read), + list("user_print", user_print), + list("display", display), + list("list", list) + ); + + + + + + + Finally, we can initialize the global environment and run the evaluator: + + the_global_environment + ec_eval_all + eceval + user_read + type_function + + (define the-global-environment (setup-environment)) + + (start eceval) + + +;;; EC-Eval input: +(define (append x y) +(if (null? x) +y +(cons (car x) + (append (cdr x) y)))) +;;; EC-Eval value: +ok + +;;; EC-Eval input: +(append '(a b c) '(d e f)) +;;; EC-Eval value: +(a b c d e f) + + +const test_program_1 = " \ +function test1() { \ + display('A 1'); \ + const result = test2(); \ + return display(stringify(result) + ' 3'); \ + display('I should not be printed 7'); \ +} \ +function test2() { \ + display('B 2'); \ + return 'C'; \ + display('I should not be printed 4'); \ +} \ +function test3() { \ + display('Hello from test3'); \ +} \ +function test4() { \ + return display('Hello from test4'); \ +} \ +display('5 ' + stringify(test1())); \ +display('6 ' + stringify(test3())); // Should print undefined \ +display('8 ' + stringify(test4())); \ +"; + +const test_program_2 = " \ +function adder(a, b) { \ + return \ + a === 0 \ + ? b \ + : adder(a - 1, b + 1); \ + 42; \ +} \ +display(\"7 + 5 = \" + stringify(adder(7, 5)); \ +"; + +start(eceval); +get_register_contents(eceval, "val"); + + +const the_global_environment = setup_environment(); +start(eceval); + + + + +EC-evaluate input: + + +function append(x, y) { + return is_null(x) + ? y + : pair(head(x), append(tail(x), y)); +} + + +EC-evaluate value: +undefined + + + + +EC-evaluate input: + + +append(list("a", "b", "c"), list("d", "e", "f")); + + +EC-evaluate value: +["a", ["b", ["c", ["d", ["e", ["f", null]]]]]] + + + + + + Of course, evaluating + + expressions + programs + + in this way will take much longer + than if we had directly typed them into + + Scheme, + JavaScript, + + because of the + multiple levels of simulation involved. Our + + expressions + programs + + are evaluated + by the explicit-control-evaluator machine, which is being simulated by + a + + Scheme + JavaScript + + program, which is itself being evaluated by the + + Scheme + JavaScript + + interpreter. + + + explicit-control evaluator for JavaScriptrunning + + + Monitoring the performance of the evaluator + + + explicit-control evaluator for JavaScriptmonitoring performance (stack use) + + + + Simulation can be a powerful tool to guide the implementation of + evaluators. + simulationmachineas machine-design tool + Simulations make it easy not only to explore variations + of the register-machine design but also to monitor the performance of + the simulated evaluator. For example, one important factor in + performance is how efficiently the evaluator uses the stack. We can + observe the number of stack operations required to evaluate various + + expressions + programs + + by defining the evaluator register machine with the + version of the simulator that collects statistics on stack use + (section), and adding an instruction at the + evaluators + + print-result + print_result + + entry point to print the statistics: + + print_resultmonitored-stack version + + print-result + (perform (op print-stack-statistics)); added instruction + (perform + (op announce-output) (const "EC-Eval value:")) + $\ldots$ ; same as before + + +"print_result", + perform(list(op("print_stack_statistics"))), // added instruction + // rest is same as before + perform(list(op("user_print"), + constant("EC-evaluate value:"), reg("val"))), + go_to(label("read_evaluate_print_loop")), + + + Interactions with the evaluator now look like this: + + +;;; EC-Eval input: +(define (factorial n) +(if (= n 1) +1 +(* (factorial (- n 1)) n))) +(total-pushes = 3 maximum-depth = 3) +;;; EC-Eval value: +ok + +;;; EC-Eval input: +(factorial 5) +(total-pushes = 144 maximum-depth = 28) +;;; EC-Eval value: +120 + + + + +EC-evaluate input: + + +function factorial (n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + + +total pushes = 4 +maximum depth = 3 +EC-evaluate value: +undefined + + + + +EC-evaluate input: + + +factorial(5); + + +total pushes = 151 +maximum depth = 28 +EC-evaluate value: +120 + + + + Note that the driver loop of the evaluator reinitializes the stack + at the start of + each interaction, so that the statistics printed will refer only to + stack operations used to evaluate the previous + + expression. + program. + + + + + + Use the monitored stack to explore the + explicit-control evaluator for JavaScripttail recursion + tail recursionexplicit-control evaluator and + tail-recursive property of the + evaluator (section). Start the + evaluator and define the + factorialstack usage, interpreted + iterative factorial + + procedure + function + from section: + + + (define (factorial n) + (define (iter product counter) + (if (> counter n) + product + (iter (* counter product) + (+ counter 1)))) + (iter 1 1)) + + +function factorial(n) { + function iter(product, counter) { + return counter > n + ? product + : iter(counter * product, + counter + 1); + } + return iter(1, 1); +} + + + Run the + procedure + function + + with some small values of $n$. Record the + maximum stack depth and the number of pushes required to compute + $n!$ for each of these values. +
    +
  1. + You will find that the maximum depth required to evaluate + $n!$ is independent of + $n$. What is that depth? +
  2. +
  3. + Determine from your data a formula in terms of + $n$ for the total number of push operations + used in evaluating $n!$ for any + $n \geq 1$. Note that the number of + operations used is a linear function of $n$ + and is thus determined by two constants. +
  4. +
+
+ + + + + For comparison with exercise, explore + the behavior of the following + + procedure + function + + for computing + factorialstack usage, interpreted + factorials recursively: + + + (define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n))) + + +function factorial(n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + + + By running this + + procedure + function + + with the monitored stack, determine, as a function of + $n$, the maximum depth of the stack and the total + number of pushes used in evaluating $n!$ for + $n \geq 1$. (Again, these functions will be + linear.) Summarize your experiments by filling in the following table with + the appropriate expressions in terms of $n$: + +
+ +
+
+ +
+ + \begin{center} + \smallskip + \begin{tabular}{l|l|l} + & Maximum depth & Number of pushes \\ \hline + \rule{0pt}{7mm} Recursive factorial & & \\ \hline + \rule{0pt}{7mm} Iterative factorial & & \\ \hline + \end{tabular} + \smallskip + \end{center} + +
+
+ The maximum depth is a measure of the amount of space used by the + evaluator in carrying out the computation, and the number of pushes + correlates well with the time required. +
+ + + Modify the definition of the evaluator by changing + + eval-sequence + as described in section + + ev_return + as described in section + + + so that the evaluator is no longer + explicit-control evaluator for JavaScripttail recursion + tail recursionexplicit-control evaluator and + tail-recursive. Rerun your experiments from + exercises + and to demonstrate that both versions of + the factorial + + procedure + function + + now require space that grows linearly with their input. + + + + + Monitor the stack operations in the tree-recursive + fibstack usage, interpreted + Fibonacci computation: + + fibtree-recursive version + + (define (fib n) + (if (< n 2) + n + (+ (fib (- n 1)) (fib (- n 2))))) + + +function fib(n) { + return n < 2 ? n : fib(n - 1) + fib(n - 2); +} + + +
    +
  1. + Give a formula in terms of $n$ for the + maximum depth of the stack required to compute + ${\textrm{Fib}}(n)$ for + $n \geq 2$. Hint: In + section we argued that the space + used by this process grows linearly with $n$. +
  2. +
  3. + Give a formula for the total number of pushes used to compute + ${\textrm{Fib}}(n)$ for + $n \geq 2$. You should find that the number + of pushes (which correlates well with the time used) grows exponentially + with $n$. Hint: Let + $S(n)$ be the number of pushes used in + computing ${\textrm{Fib}}(n)$. You should be + able to argue that there is a formula that expresses + $S(n)$ in terms of + $S(n-1)$, $S(n-2)$, + and some fixed overhead constant + $k$ that is independent of + $n$. Give the formula, and say what + $k$ is. Then show that + $S(n)$ can be expressed as + $a {\textrm{Fib}}(n+1) + b$ and give the + values of $a$ and + $b$. +
  4. +
+ +
+ + explicit-control evaluator for JavaScriptmonitoring performance (stack use) + + + + Our evaluator currently catches and signals only two kinds of + error handlingin explicit-control evaluator + explicit-control evaluator for JavaScripterror handling + errorsunknown + + + expression + + + component + + + types and unknown + + procedure + function + + types. Other errors will take us out of the evaluator + + + read-eval-print + + + read-evaluate-print + + + loop. + When we run the evaluator using the register-machine simulator, these + errors are caught by the underlying + + Scheme + JavaScript + + system. This is analogous + to the computer crashing when a user program makes an error. + + + Regrettably, this is the normal state of affairs in + Cerror handling + conventional compiler-based language systems such as C. + UNIX + In UNIX$^{\textrm{TM}}$ the system dumps + core, and in + DOS/Windows + DOS/Windows$^{\textrm{TM}}$ + it becomes catatonic. The + Macintosh + Macintosh$^{\textrm{TM}}$ displays a picture of + an exploding bomb and offers you the opportunity to reboot the + computerif youre lucky. + + + This manifests itself as, for example, a kernel panic or a blue + screen of death or even a reboot. Automatic rebooting is an approach + typically used on phones and tablets. Most modern operating systems do a + decent job of preventing user programs from causing an entire machine to + crash. + + + It is a large project to + make a real error system work, but it is well worth the effort to understand + what is involved here. +
    +
  1. + Errors that occur in the evaluation process, such as an attempt to + access an unbound + + variable, + name, + + could be caught by changing the lookup + operation to make it return a distinguished condition code, which cannot + be a possible value of any user + + variable. + name. + + The evaluator can test + for this condition code and then do what is necessary to go to + + signal-error. + signal_error. + + + Find all of the places in the evaluator where such a + change is necessary and fix them. This is lots of work. +
  2. +
  3. + Much worse is the problem of handling errors that are signaled by + applying primitive + + procedures + functions + + such as an attempt to divide by zero or an attempt to extract the + + + car + of a symbol. + + + head + of a string. + + + In a professionally written high-quality system, each + primitive application is checked for safety as part of the primitive. + For example, every call to + + car + head + + could first check that the argument is a pair. If the argument is not + a pair, the application would return a distinguished condition code to + the evaluator, which would then report the failure. We could arrange + for this in our register-machine simulator by making each primitive + + procedure + function + + check for applicability and returning an appropriate distinguished + condition code on failure. Then the + + primitive-apply + primitive_apply + + + code in the evaluator can check for the condition code and go to + + signal-error + signal_error + + + if necessary. Build this structure and make it work. + This is a major project. +
  4. +
+
+ + explicit-control evaluator for JavaScript + +
diff --git a/xml/cn/chapter5/section5/section5.xml b/xml/cn/chapter5/section5/section5.xml new file mode 100644 index 000000000..37c8c3aee --- /dev/null +++ b/xml/cn/chapter5/section5/section5.xml @@ -0,0 +1,621 @@ +
+ Compilation + + + + + + compiler + + + 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 + universal machineexplicit-control evaluator as + explicit-control evaluator for JavaScriptuniversalas universal machine + universalit + can carry out any computational process that can be described in + + Scheme. + JavaScript. + + The + evaluators controller orchestrates the use of its data + paths to perform the desired computation. Thus, the + evaluators data paths are universal: They are sufficient + to perform any computation we desire, given an appropriate + controller.This is a theoretical statement. We are + not claiming + that the evaluators data paths are a particularly convenient or + efficient set of data paths for a general-purpose computer. For example, + they are not very good for implementing high-performance floating-point + calculations or calculations that intensively manipulate bit + vectors. + + + + + Commercial + universal machinegeneral-purpose computer as + general-purpose computer, as universal machine + general-purpose computers are + register machines organized + around a collection of registers and operations that constitute + an efficient and convenient universal set of data paths. + The controller for a general-purpose machine is an interpreter for + a register-machine language like the one we have been using. This + language is called the + native language of machine + native language of the machine, or simply + machine language + machine language. Programs written in machine language are + sequences of instructions that use the machines data paths. + For example, the + explicit-control evaluator for JavaScriptmachineas machine-language program + explicit-control evaluators 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. + + + compilerinterpreter vs. + interpretercompiler vs. + + + 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 + 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 + 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. + + + + 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 + object program) written in the machines 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 machines data paths.Actually, the machine that + runs compiled code can be simpler than the interpreter machine, because we + compiler for JavaScriptregister use + wont use the + + + exp + + + comp + + + and + unev registers. The interpreter + used these to hold pieces of unevaluated + + + expressions. + + + components. + + + With the + compiler, however, these + + + expressions + + + components + + + get built into the + compiled code that the register machine will run. For the same + reason, + compiler for JavaScriptmachine-operation use + we dont need the machine operations that deal with + + + expression + + + component + + + syntax. But compiled code will use a few additional machine + operations (to represent compiled + + procedure + function + + objects) that didnt + appear in the explicit-control evaluator machine. + + + + 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.Language implementations + often delay the compilation of program parts even when they are + assumed to be debugged, until there is enough evidence that compiling them + would lead to an overall efficiency advantage. The evidence is obtained at + run time by monitoring the number of times the program parts are being + interpreted. This technique is called + just-in-time compilation. + 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. + + + compilerinterpreter vs. + interpretercompiler vs. + compiler + + + An overview of the compiler + + + compiler for JavaScript + compiler for JavaScriptexplicit-control evaluator vs. + + + 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 + compiler for JavaScriptregister use + register usage as the interpreter: The + environment will be kept in the 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. + + + compiler for JavaScriptefficiency + + + 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 + efficiencycompilationof compilation + efficiency advantage of compilation over interpretation. Each + time the interpreter evaluates + + + an expressionfor example, + + + a componentfor 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 + compiler for JavaScriptanalyzing evaluator vs. + analyzing evaluator of section. + 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. + + + + 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. + + + + + 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. + + + compiler for JavaScriptefficiency + compiler for JavaScriptexplicit-control evaluator vs. + + + &subsection5.5.1; + + + &subsection5.5.2; + + + &subsection5.5.3; + + + &subsection5.5.4; + + + &subsection5.5.5; + + + &subsection5.5.6; + + + &subsection5.5.7; + +
diff --git a/xml/cn/chapter5/section5/subsection1.xml b/xml/cn/chapter5/section5/subsection1.xml new file mode 100644 index 000000000..8f8f49565 --- /dev/null +++ b/xml/cn/chapter5/section5/subsection1.xml @@ -0,0 +1,858 @@ + + + Structure of the Compiler + + + + + compiler for JavaScriptstructure of + + + + In section we modified our + original metacircular interpreter to separate + compiler for JavaScriptanalyzing evaluator vs. + analysis from execution. We + analyzed each + + + expression + + + component + + + to produce an execution + + procedure + function + + that took an environment as argument and performed the required operations. + In our compiler, we will do essentially the same analysis. Instead of + producing execution + + procedures, + functions, + + however, we will generate sequences of instructions to be run by our + register machine. + + + + The + + procedure + function + + compile is the top-level dispatch in the + compiler. It corresponds to the evalevaluate + + procedure + function + + of section, the + analyze + + procedure + function + + of section, and the + + eval-dispatch + eval_dispatch + + entry point of the explicit-control-evaluator in + section. The compiler, like the + interpreters, uses the + compiler for JavaScriptexpression-syntax proceduresfunctions + + expression-syntax procedures + component-syntax functions + + defined in + section.Notice, + however, that our compiler is a + + Scheme + JavaScript + + program, and the syntax + + procedures + functions + + that it uses to manipulate expressions are the actual + + Scheme procedures + JavaScript functions + + used with the metacircular evaluator. For the explicit-control evaluator, in + contrast, we assumed that equivalent syntax operations were available + as operations for the register machine. (Of course, when we simulated + the register machine in + + Scheme, + JavaScript, + + we used the actual + + Scheme procedures + JavaScript functions + + in our register machine simulation.) + + Compile + The function compile + performs a case analysis on the + syntactic type of the + + + expression + + + component + + + to be compiled. For + each type of + + + expression, + + + component, + + + it dispatches to a + specialized + code generator + code generator: + + compile + compile + +(define (compile exp target linkage) + (cond ((self-evaluating? exp) + (compile-self-evaluating exp target linkage)) + ((quoted? exp) (compile-quoted exp target linkage)) + ((variable? exp) + (compile-variable exp target linkage)) + ((assignment? exp) + (compile-assignment exp target linkage)) + ((definition? exp) + (compile-definition exp target linkage)) + ((if? exp) (compile-if exp target linkage)) + ((lambda? exp) (compile-lambda exp target linkage)) + ((begin? exp) + (compile-sequence (begin-actions exp) + target + linkage)) + ((cond? exp) (compile (cond->if exp) target linkage)) + ((application? exp) + (compile-application exp target linkage)) + (else + (error "Unknown expression type - - COMPILE" exp)))) + + +function compile(component, target, linkage) { + return is_literal(component) + ? compile_literal(component, target, linkage) + : is_name(component) + ? compile_name(component, target, linkage) + : is_application(component) + ? compile_application(component, target, linkage) + : is_operator_combination(component) + ? compile(operator_combination_to_application(component), + target, linkage) + : is_conditional(component) + ? compile_conditional(component, target, linkage) + : is_lambda_expression(component) + ? compile_lambda_expression(component, target, linkage) + : is_sequence(component) + ? compile_sequence(sequence_statements(component), + target, linkage) + : is_block(component) + ? compile_block(component, target, linkage) + : is_return_statement(component) + ? compile_return_statement(component, target, linkage) + : is_function_declaration(component) + ? compile(function_decl_to_constant_decl(component), + target, linkage) + : is_declaration(component) + ? compile_declaration(component, target, linkage) + : is_assignment(component) + ? compile_assignment(component, target, linkage) + : error(component, "unknown component type -- compile"); +} + + + + + + Targets and linkages + + + + + + Compile + + + The function + compile + + + and the code generators that it calls + take two + code generatorarguments of + arguments in addition to the + + + expression + + + component + + + to compile. There is a + target register + target, which specifies the register in which the compiled code is + to return the value of the + + + expression. + + + component. + + + There is also a + linkage descriptor + linkage descriptor, which describes how the code resulting from the + compilation of the + + + expression + + + component + + + should proceed when it has finished its + execution. The linkage descriptor can require the code to do one of + the following three things: +
    +
  • + proceed to the next instruction in sequence (this is + specified by the linkage descriptor + next (linkage descriptor) + + + next), + + + "next"), + + +
  • +
  • + + + return from the procedure being compiled + + + jump to the current value of the continue register + as part of returning from a function call + + + (this is specified + by the linkage descriptor + return\texttt{return} (linkage descriptor) + + + return), + + + "return"), + + + or +
  • +
  • + jump to a named entry point (this is specified by using the + designated label as the linkage descriptor). +
  • +
+
+ + For example, compiling the + + + expression + + + literal + + + 5 + + + (which is self-evaluating) + + + with a target of the val + register and a linkage of + + + next + + + "next" + + + should produce + the instruction + + +(assign val (const 5)) + + +assign("val", constant(5)) + + + Compiling the same expression with a linkage of + + + return + + + "return" + + + should produce the instructions + + +(assign val (const 5)) +(goto (reg continue)) + + +assign("val", constant(5)), +go_to(reg("continue")) + + + In the first case, execution will continue with the next instruction + in the sequence. In the second case, + + we will return from a procedure call. + we will jump to whatever entry point is stored in the continue register. + + In both cases, the value of the expression will be placed into + the target val register. + + + Our compiler uses the "return" linkage when compiling + the return expression of a return statement. + Just as in the explicit-control evaluator, returning from a function call happens in three steps: +
    +
  1. reverting the stack to the marker and restoring continue (which holds a continuation set up at the beginning of the function call)
  2. +
  3. computing the return value and placing it in val
  4. +
  5. jumping to the entry point in continue
  6. +
+ Compilation of a return statement explicitly generates code for reverting the stack and restoring continue. + The 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. +
+
+
+ + + Instruction sequences and stack usage + + + + + instruction sequence + + + Each code generator returns an + code generatorvalue of + instruction sequence containing + the object code it has generated for the + + + expression. + + + component. + + + Code generation for a + + compound expression + compound component + + is accomplished by combining the output from simpler code + generators for + + component expressions, + subcomponents, + + just as evaluation of a + + compound expression + compound component + + is accomplished by evaluating the + + component expressions. + subcomponents. + + + + + The simplest method for combining instruction sequences is a + + procedure + function + + called + append_instruction_sequences + + append-instruction-sequences. + + append_instruction_sequences, + + + + + It takes as arguments + any number of instruction sequences + + + which takes as arguments + two instruction sequences + + + that are to be + executed + + + sequentially; it + + + sequentially. It + + + appends them and returns the combined sequence. + That is, if $seq_1$ and + $seq_2$ are sequences of instructions, then + evaluating + + + (append-instruction-sequences $seq_1$ $seq_2$) + + + append_instruction_sequences(seq$_1$, seq$_2$) + + + produces the sequence + + + $seq_1$ + $seq_2$ + + +seq$_1$ +seq$_2$ + + + + + + + Whenever registers might need to be saved, the compilers code + generators use + preserving + preserving, which is a more subtle method for + combining instruction sequences. + + + Preserving + + + The function preserving + + + takes three arguments: a set of registers and two instruction sequences that + are to be executed sequentially. It appends the sequences in such a way + that the contents of each register in the set is preserved over the + execution of the first sequence, if this is needed for the execution of the + second sequence. That is, if the first sequence modifies the register + and the second sequence actually needs the registers original + contents, then preserving wraps a + save and a restore + of the register around the first sequence before appending the sequences. + Otherwise, preserving simply returns the + appended instruction sequences. Thus, for example, + + + (preserving (list $reg_1$ $reg_2$) $seq_1$ $seq_2$) + + + preserving(list(reg$_1$, reg$_2$), seq$_1$, seq$_2$) + + + produces one of the following four sequences of instructions, depending on + how + seq$_1$ and + seq$_2$ use + reg$_1$ and + reg$_2$: + + + + + + \[ + \begin{array}{l|l|l|l} + \textit{seq}_1 & \texttt{(save}\ \textit{reg}_1\texttt{)} & \texttt{(save}\ \textit{reg}_2\texttt{)} & \texttt{(save}\ \textit{reg}_2\texttt{)} \\ + \textit{seq}_2 & \textit{seq}_1 & \textit{seq}_1 & \texttt{(save}\ \textit{reg}_1\texttt{)} \\ + & \texttt{(restore}\ \textit{reg}_1\texttt{)} & \texttt{(restore}\ \textit{reg}_2\texttt{)} & \textit{seq}_1 \\ + & \textit{seq}_2 & \textit{seq}_2 & \texttt{(restore}\ \textit{reg}_1\texttt{)} \\ + & & & \texttt{(restore}\ \textit{reg}_2\texttt{)} \\ + & & & \textit{seq}_2 + \end{array} + \] + + + + \[ + \begin{array}{l|l|l|l} + \textit{seq}_1 & \texttt{save(}\textit{reg}_1\texttt{),} & \texttt{save(}\textit{reg}_2\texttt{),} & \texttt{save(}\textit{reg}_2\texttt{),} \\ + \textit{seq}_2 & \textit{seq}_1 & \textit{seq}_1 & \texttt{save(}\textit{reg}_1\texttt{),} \\ + & \texttt{restore(}\textit{reg}_1\texttt{),} & \texttt{restore(}\textit{reg}_2\texttt{),} & \textit{seq}_1 \\ + & \textit{seq}_2 & \textit{seq}_2 & \texttt{restore(}\textit{reg}_1\texttt{),} \\ + & & & \texttt{restore(}\textit{reg}_2\texttt{),} \\ + & & & \textit{seq}_2 + \end{array} + \] + + + \medskip + + + By using preserving to combine instruction + sequences the compiler avoids unnecessary + compiler for JavaScriptstack usage + stack operations. This also + isolates the details of whether or not to generate + save and restore + instructions within the preserving + + procedure, + function, + + separating them from the concerns that arise in writing each of the + individual code generators. + In fact no save or + restore instructions are explicitly + produced by the code + + generators. + + generators, except that the code for calling a function saves + continue and the code for returning + from a function restores it: These corresponding + save and restore + instructions are explicitly generated + by different calls to compile, + not as a matched pair by preserving + (as we will see in section). + + + + + + In principle, we could represent an instruction sequence simply as a + list of instructions. + + Append-instruction-sequences + + + The function + append_instruction_sequences + + + could then combine instruction sequences by performing an ordinary list + append. However, + preserving would then be a complex operation, + because it would have to analyze each instruction sequence to + determine how the sequence uses its registers. + + + Preserving + + + The function preserving + + + would be inefficient as well as complex, because it would have to + analyze each of its instruction sequence arguments, even though these + sequences might themselves have been constructed by calls to + preserving, in which case their parts would + have already been analyzed. To avoid such repetitious analysis we will + associate with each instruction sequence some information about its register + use. When we construct a basic instruction sequence we + will provide this information explicitly, + and the + + procedures + functions + + that combine instruction sequences will derive + register-use information for the combined sequence from the + information associated with the sequences being combined. + + + + An instruction sequence will contain three pieces of information: +
    +
  • the set of registers that must be initialized before the + instructions in the sequence are executed (these registers are said to + be needed by the sequence), +
  • +
  • the set of registers whose values are modified by the + instructions in the sequence, and +
  • +
  • the actual instructions + + + (also called statements) + + + in the sequence. +
  • +
+ We will represent an instruction sequence as a list of its three + parts. The constructor for instruction sequences is thus + make_instruction_sequence + + make_instruction_sequence + +(define (make-instruction-sequence needs modifies statements) + (list needs modifies statements)) + + +function make_instruction_sequence(needs, modifies, instructions) { + return list(needs, modifies, instructions); +} + + +
+ + + For example, the two-instruction + sequence that looks up the value of the + + + variable + x + + + symbol + "x" + + + in the current environment, + assigns the result to val, + + and then returns, + and then proceeds to the continuation, + + requires registers env and + continue to have been initialized, and + modifies register val. + This sequence would therefore be constructed as + + + (make-instruction-sequence '(env continue) '(val) + '((assign val + (op lookup-variable-value) (const x) (reg env)) + (goto (reg continue)))) + + +make_instruction_sequence(list("env", "continue"), list("val"), + list(assign("val", + list(op("lookup_symbol_value"), constant("x"), + reg("env"))), + go_to(reg("continue")))); + + + + + + The + + procedures + functions + + for combining instruction sequences are shown in + section. + + + instruction sequence + + + compiler for Schemestructure of + + + compiler for JavaScriptstructure of + + + + + + In evaluating a + compiler for JavaScriptstack usage + preserving + + procedure + function + + application, the explicit-control evaluator always saves and restores + the env register around the evaluation of the + + operator, + function expression, + + saves and restores env around the + evaluation of each + + operand + argument expression + + (except the final one), saves and restores + argl around the evaluation of each + + operand, + argument expression, + + and saves and restores + + + proc + + + fun + + + around the + evaluation of the + + operand + argument-expression + + sequence. For each of the following + + combinations, + applications, + + say which of these save and + restore operations are superfluous and + thus could be eliminated by the compilers + preserving mechanism: + + + (f 'x 'y) + + ((f) 'x 'y) + + (f (g 'x) y) + + (f (g 'x) 'y) + + +f("x", "y") + +f()("x", "y") + +f(g("x"), y) + +f(g("x"), "y") + + + + + + Using the + compiler for JavaScriptexplicit-control evaluator vs. + explicit-control evaluator for JavaScriptoptimizations (additional) + preserving mechanism, the compiler + will avoid saving and restoring env around the + evaluation of the + + + operator of a combination + + + function expression of an application + + + in the case where the + + + operator is a symbol. + + + function expression is a name. + + + We could also build such optimizations into the evaluator. + Indeed, the explicit-control evaluator of + section already performs a similar + optimization, by treating + + + combinations with no operands + + + applications with no arguments + + + as a special case. + +
    +
  1. + Extend the explicit-control evaluator to recognize as a separate class + of + + + expressions combinations whose operator is a symbol, + + + components applications whose function expression is a name, + + + and to take + advantage of this fact in evaluating such + + + expressions. + + + components. + + +
  2. +
  3. + Alyssa P. Hacker suggests that by extending the evaluator to recognize + more and more special cases we could incorporate all the compilers + optimizations, and that this would eliminate the advantage of compilation + altogether. What do you think of this idea? +
  4. +
+ +
+ +
diff --git a/xml/cn/chapter5/section5/subsection2.xml b/xml/cn/chapter5/section5/subsection2.xml new file mode 100644 index 000000000..f2c70a946 --- /dev/null +++ b/xml/cn/chapter5/section5/subsection2.xml @@ -0,0 +1,1291 @@ + + + Compiling + + Expressions + Components + + + + + + In this section and the next we implement the code generators to which the + compile + + procedure + function + + dispatches. + + + + Compiling linkage code + + + + In general, the output of each code generator will end with + instructionsgenerated by the + compiler for JavaScriptlinkage code + + procedure + function + + + compile-linkagethat + + compile_linkagethat + + + implement the required linkage. If the linkage is + + + return + + + "return" + + + then + we must generate the instruction + + (goto (reg continue)). + go_to(reg("continue")). + + + This needs the continue register and does not + modify any registers. + If the linkage is + + + next, + + + "next", + + + then we neednt include any additional instructions. Otherwise, the + linkage is a label, and we generate a + + goto + go_to + + to that label, an instruction that does not need or modify + any registers.This procedure + uses a feature of Lisp called + list(s)backquote with + backquote + quasiquote + ` (backquote) + , (comma, used with backquote) + comma, used with backquote + backquote (or quasiquote) that is handy for constructing + lists. Preceding a list with a backquote symbol is much like quoting it, + except that anything in the list that is flagged with a comma is + evaluated. +

+ For example, if the value of linkage is the + symbol branch25, + then the expression + `((goto (label ,linkage))) + evaluates to the list + ((goto (label branch25))). + Similarly, if the value of x is the list + (a b c), + then + `(1 2 ,x) + evaluates to the list + (1 2 a).
+ + compile_linkage + compile_linkage + +(define (compile-linkage linkage) + (cond ((eq? linkage 'return) + (make-instruction-sequence '(continue) '() + '((goto (reg continue))))) + ((eq? linkage 'next) + (empty-instruction-sequence)) + (else + (make-instruction-sequence '() '() + `((goto (label ,linkage))))))) + + +function compile_linkage(linkage) { + return linkage === "return" + ? make_instruction_sequence(list("continue"), null, + list(go_to(reg("continue")))) + : linkage === "next" + ? make_instruction_sequence(null, null, null) + : make_instruction_sequence(null, null, + list(go_to(label(linkage)))); +} + + + The linkage code is appended to an instruction sequence by + preserving the + continue register, since a + + + return + + + "return" + + + linkage will + require the continue register: + If the given instruction sequence modifies + continue and the linkage code needs it, + continue will be saved and restored. + + end_with_linkage + end_with_linkage + +(define (end-with-linkage linkage instruction-sequence) + (preserving '(continue) + instruction-sequence + (compile-linkage linkage))) + + +function end_with_linkage(linkage, instruction_sequence) { + return preserving(list("continue"), + instruction_sequence, + compile_linkage(linkage)); +} + + +
+ + + + Compiling simple + + + expressions + + + components + + + + + + + The code generators for + compiler for JavaScriptliterals + compiler for JavaScriptnames + + self-evaluating expressions, quotations, and variables + literal expressions and names + + construct instruction + sequences that assign the required value to the target register + and then proceed as specified by the linkage descriptor. + + + + + The literal value is extracted at compile time from the component being + compiled and put into the constant part of the + assign instruction. For a name, an + instruction is generated to use the + lookup_symbol_value operation when + the compiled program is run, to look up the value associated with a + symbol in the current environment. Like a literal value, the symbol is + extracted at compile time from the component being compiled. Thus + symbol_of_name(component) is + executed only once, when the program is being compiled, and the symbol + appears as a constant in the + assign instruction. + + + + + + compile_literal + compile_name + compile_literal + +(define (compile-self-evaluating exp target linkage) + (end-with-linkage linkage + (make-instruction-sequence '() (list target) + `((assign ,target (const ,exp)))))) + +(define (compile-quoted exp target linkage) + (end-with-linkage linkage + (make-instruction-sequence '() (list target) + `((assign ,target (const ,(text-of-quotation exp))))))) + +(define (compile-variable exp target linkage) + (end-with-linkage linkage + (make-instruction-sequence '(env) (list target) + `((assign ,target + (op lookup-variable-value) + (const ,exp) + (reg env)))))) + + +function compile_literal(component, target, linkage) { + const literal = literal_value(component); + return end_with_linkage(linkage, + make_instruction_sequence(null, list(target), + list(assign(target, constant(literal))))); +} +function compile_name(component, target, linkage) { + const symbol = symbol_of_name(component); + return end_with_linkage(linkage, + make_instruction_sequence(list("env"), list(target), + list(assign(target, + list(op("lookup_symbol_value"), + constant(symbol), + reg("env")))))); +} + + + + All these + These + + assignment instructions modify the target register, + and the one that looks up a + + + variable + + + symbol + + + needs the env register. + + + + Assignments and + compiler for JavaScriptassignments + compiler for JavaScriptdeclarations + + definitions + declarations + + are handled much as they are in the + interpreter. + + + We recursively generate code that computes the value to be + assigned to the variable, and append to it a + two-instruction sequence that actually sets or defines the + variable and assigns the value of the whole expression + (the symbol ok) to the target + register. The 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. The + appending is done preserving + env, since the environment is + needed for setting or defining the variable and the code + for the variable value could be the compilation of a + complex expression that might modify the registers in + arbitrary ways. + + + The 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. + + + + The 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. The + 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. + + + + compile_assignment + compile_declaration + compile_assignment + +(define (compile-assignment exp target linkage) + (let ((var (assignment-variable exp)) + (get-value-code + (compile (assignment-value exp) 'val 'next))) + (end-with-linkage linkage + (preserving '(env) + get-value-code + (make-instruction-sequence '(env val) (list target) + `((perform (op set-variable-value!) + (const ,var) + (reg val) + (reg env)) + (assign ,target (const ok)))))))) + +(define (compile-definition exp target linkage) + (let ((var (definition-variable exp)) + (get-value-code + (compile (definition-value exp) 'val 'next))) + (end-with-linkage linkage + (preserving '(env) + get-value-code + (make-instruction-sequence '(env val) (list target) + `((perform (op define-variable!) + (const ,var) + (reg val) + (reg env)) + (assign ,target (const ok)))))))) + + +function compile_assignment(component, target, linkage) { + return compile_assignment_declaration( + assignment_symbol(component), + assignment_value_expression(component), + reg("val"), + target, linkage); +} + +function compile_declaration(component, target, linkage) { + return compile_assignment_declaration( + declaration_symbol(component), + declaration_value_expression(component), + constant(undefined), + target, linkage); +} +function compile_assignment_declaration( + symbol, value_expression, final_value, + target, linkage) { + const get_value_code = compile(value_expression, "val", "next"); + return end_with_linkage(linkage, + preserving(list("env"), + get_value_code, + make_instruction_sequence(list("env", "val"), + list(target), + list(perform(list(op("assign_symbol_value"), + constant(symbol), + reg("val"), + reg("env"))), + assign(target, final_value))))); +} + + + The appended two-instruction sequence requires + env and val + and modifies the target. Note that although we preserve + env for this sequence, we do not preserve + val, because the + + get-value-code + get_value_code + + + is designed to explicitly place its result in + val for use by this sequence. + (In fact, if we did preserve val, we would + have a bug, because this would cause the previous contents of + val to be restored right after the + + get-value-code + get_value_code + + + is run.) + + + + + Compiling + + + conditional expressions + + + conditionals + + + + + + + The code for + compiler for JavaScriptconditionals + + + an if expression + + + a conditional + + + compiled with a given target and linkage has the form + + + $\langle compilation\ of\ predicate,\ target$ val$,\ linkage$ next$\rangle$ + (test (op false?) (reg val)) + (branch (label false-branch)) +true-branch + $\langle compilation\ of\ consequent\ with\ given\ target\ and\ given\ linkage or$ after-if$\rangle$ +false-branch + $\langle compilation\ of\ alternative\ with\ given\ target\ and\ linkage\rangle$ +after-if + + +compilation of predicate, target val, linkage "next" + test(list(op("is_falsy"), reg("val"))), + branch(label("false_branch")), +"true_branch", + compilation of consequent with given target and given linkage or after_cond +"false_branch", + compilation of alternative with given target and linkage +"after_cond" + + + + + + To generate this code, we compile the predicate, consequent, + and alternative, and combine the resulting code with instructions + to test the predicate result and with newly generated labels to mark the + true and false branches and the end of the + conditional.We cant just use the labels + + true-branch, + true_branch, + + + false-branch, + false_branch, + + + and + + after-if + after_cond + + as shown above, because there might be more than one + ifconditional + in the program. + compiler for JavaScriptlabel generation + The compiler uses the + + procedure + function + + + make-label + make_label + + to generate labels. + + Make-label + + The function + make_label + + + takes a symbolstring as argument and returns a new + symbolstring that begins with the + given symbolstring. For example, successive calls to + + (make-label 'a) + make_label("a") + + + would return + a1"a1", + a2"a2", + and so on. + + Make-label + + The function + make_label + + + + character stringsprimitive procedures for + + + can be implemented similarly to the generation of unique variable names in + the query language, as follows: + make_label + make_label + +(define label-counter 0) + +(define (new-label-number) + (set! label-counter (+ 1 label-counter)) + label-counter) + +(define (make-label name) + (string->symbol + (string-append (symbol->string name) + (number->string (new-label-number))))) + + +let label_counter = 0; + +function new_label_number() { + label_counter = label_counter + 1; + return label_counter; +} +function make_label(string) { + return string + stringify(new_label_number()); +} + + + In this arrangement of code, we must branch around the true branch + if the test is false. The only slight complication is in how the + linkage for the true branch should be handled. If the linkage for the + conditional is + + + return + + + "return" + + + or a label, then the + true and false branches will both use this same linkage. If the linkage is + + + next, + + + "next", + + + the true branch ends with a jump around + the code for the false branch to the label at the end of the conditional. + + compile_conditional + compile_conditional + +(define (compile-if exp target linkage) + (let ((t-branch (make-label 'true-branch)) + (f-branch (make-label 'false-branch)) + (after-if (make-label 'after-if))) + (let ((consequent-linkage + (if (eq? linkage 'next) after-if linkage))) + (let ((p-code (compile (if-predicate exp) 'val 'next)) + (c-code + (compile + (if-consequent exp) target consequent-linkage)) + (a-code + (compile (if-alternative exp) target linkage))) + (preserving '(env continue) + p-code + (append-instruction-sequences + (make-instruction-sequence '(val) '() + `((test (op false?) (reg val)) + (branch (label ,f-branch)))) + (parallel-instruction-sequences + (append-instruction-sequences t-branch c-code) + (append-instruction-sequences f-branch a-code)) + after-if)))))) + + +function compile_conditional(component, target, linkage) { + const t_branch = make_label("true_branch"); + const f_branch = make_label("false_branch"); + const after_cond = make_label("after_cond"); + const consequent_linkage = + linkage === "next" ? after_cond : linkage; + const p_code = compile(conditional_predicate(component), + "val", "next"); + const c_code = compile(conditional_consequent(component), + target, consequent_linkage); + const a_code = compile(conditional_alternative(component), + target, linkage); + return preserving(list("env", "continue"), + p_code, + append_instruction_sequences( + make_instruction_sequence(list("val"), null, + list(test(list(op("is_falsy"), reg("val"))), + branch(label(f_branch)))), + append_instruction_sequences( + parallel_instruction_sequences( + append_instruction_sequences(t_branch, c_code), + append_instruction_sequences(f_branch, a_code)), + after_cond))); +} + + + + + Env + + + \newpage\noindent + The env register + + + is preserved around the predicate code + because it could be needed by the true and false + branches, and continue is preserved because it could + be needed by the linkage code in those branches. + The code for the true and + false branches (which are not executed sequentially) is appended using a + special combiner + + parallel-instruction-sequences + + + parallel_instruction_sequences + + + described in + section. + + + + + + Note that cond is a derived expression, + so all that the compiler needs to do to handle it is to apply the + cond->if transformer + (from section) and + compile the resulting if expression. + + + + + + Compiling sequences + + + + The compilation of + compiler for JavaScriptsequences of statements + + + sequences (from procedure bodies or explicit begin + expressions) + parallels their evaluation. + + + statement 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. + + + Each + + + expression + + + statement + + + of the sequence is compiledthe last + + + expression + + + statement (or a return statement) + + + with + the linkage specified for the sequence, and the other + + + expressions + + + statements + + + with + linkage + + + next + + + "next" + + + (to execute the rest of the + sequence). The instruction sequences for the individual + + + expressions + + + statements + + + are + appended to form a single instruction sequence, such that + env (needed for the rest of the + sequence) + + and continue (possibly needed + for the linkage at the end of the sequence) are + preserved. + + + and continue (possibly needed + for the linkage at the end of the sequence) + are preserved. +The continue register would +be needed for a "return" +linkage, which can result from a compilation by +compile_and_go +(section). + + + + + compile_sequence + compile_sequence + +(define (compile-sequence seq target linkage) + (if (last-exp? seq) + (compile (first-exp seq) target linkage) + (preserving '(env continue) + (compile (first-exp seq) target 'next) + (compile-sequence (rest-exps seq) target linkage)))) + + +function compile_sequence(seq, target, linkage) { + return is_empty_sequence(seq) + ? compile_literal(make_literal(undefined), target, linkage) + : is_last_statement(seq) || + is_return_statement(first_statement(seq)) + ? compile(first_statement(seq), target, linkage) + : preserving(list("env", "continue"), + compile(first_statement(seq), target, "next"), + compile_sequence(rest_statements(seq), + target, linkage)); +} + + + + + Treating 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. + Removing 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.Our compiler does not detect all dead code. For example, + a conditional statement whose consequent and alternative branches both end in a return + statement will not stop subsequent statements from being compiled. See + exercises and . + + + + + + + + + + + + Compiling blocks + + + + + A + compiler for JavaScriptblocks + block is compiled by prepending an assign instruction to the compiled + body of the block. The assignment extends the current environment by a frame + that binds the names declared in the block to the value + "*unassigned*". This operation + both needs and modifies the env register. + + compile_block + scanning out declarationsin compiler + compile_block + +function compile_block(stmt, target, linkage) { + const body = block_body(stmt); + const locals = scan_out_declarations(body); + const unassigneds = list_of_unassigned(locals); + return append_instruction_sequences( + make_instruction_sequence(list("env"), list("env"), + list(assign("env", list(op("extend_environment"), + constant(locals), + constant(unassigneds), + reg("env"))))), + compile(body, target, linkage)); +} + + + + + + + + Compiling + + + lambda + + + lambda + + + expressions + + + + Lambda expressions + compiler for JavaScriptlambda expressions + construct + + procedures. + functions. + + The object code for a lambda expression must have the form + + +$\langle construct\ procedure\ object\ and\ assign\ it\ to\ target\ register\rangle$ +$\langle linkage\rangle$ + + +construct function object and assign it to target register +linkage + + + When we compile the lambda expression, we also generate the code for the + + procedure + function + + body. Although the body wont be executed at the time of + + procedure + function + + construction, it is convenient to insert it into the object code right after + the code for the + + lambda. + lambda expression. + + If the linkage for the lambda expression is a label or + + + return, + + + "return", + + + this is fine. But if the linkage is + + + next, + + + "next", + + + we will need to skip around the code for + the + + procedure + function + + body by using a linkage that jumps to a label that is inserted after the + body. The object code thus has the form + + + $\langle construct\ procedure\ object\ and\ assign\ it\ to\ target\ register\rangle$ + $\langle code\ for\ given\ linkage\rangle\ or$ (goto (label after-lambda)) + $\langle compilation\ of\ procedure\ body\rangle$ +after-lambda + + +construct function object and assign it to target register +code for given linkage or go_to(label("after_lambda")) +compilation of function body +"after_lambda" + + + + + + + + Compile-lambda + + + The function + compile_lambda_expression + + generates the code for constructing the + + procedure + function + + object followed by the code for the + + procedure + function + + body. The + + procedure + function + + object will be constructed at run time by combining the current environment + (the environment at the point of definitiondeclaration) with the entry point to the + compiled + + procedure + function + + body (a newly generated label).We need machine operations to + implement a data structure for representing compiled + + procedures, + functions, + + analogous to the structure for compound + + procedures + functions + + described in section: + + make_compiled_function + is_compiled_function + compiled_function_entry + compiled_function_env + make_compiled_function + +(define (make-compiled-procedure entry env) + (list 'compiled-procedure entry env)) + +(define (compiled-procedure? proc) + (tagged-list? proc 'compiled-procedure)) + +(define (compiled-procedure-entry c-proc) (cadr c-proc)) + +(define (compiled-procedure-env c-proc) (caddr c-proc)) + + +function make_compiled_function(entry, env) { + return list("compiled_function", entry, env); +} +function is_compiled_function(fun) { + return is_tagged_list(fun, "compiled_function"); +} +function compiled_function_entry(c_fun) { + return head(tail(c_fun)); +} +function compiled_function_env(c_fun) { + return head(tail(tail(c_fun))); +} + + + + + compile_lambda_expression + compile_lambda + +(define (compile-lambda exp target linkage) + (let ((proc-entry (make-label 'entry)) + (after-lambda (make-label 'after-lambda))) + (let ((lambda-linkage + (if (eq? linkage 'next) after-lambda linkage))) + (append-instruction-sequences + (tack-on-instruction-sequence + (end-with-linkage lambda-linkage + (make-instruction-sequence '(env) (list target) + `((assign ,target + (op make-compiled-procedure) + (label ,proc-entry) + (reg env))))) + (compile-lambda-body exp proc-entry)) + after-lambda)))) + + +function compile_lambda_expression(exp, target, linkage) { + const fun_entry = make_label("entry"); + const after_lambda = make_label("after_lambda"); + const lambda_linkage = + linkage === "next" ? after_lambda : linkage; + return append_instruction_sequences( + tack_on_instruction_sequence( + end_with_linkage(lambda_linkage, + make_instruction_sequence(list("env"), + list(target), + list(assign(target, + list(op("make_compiled_function"), + label(fun_entry), + reg("env")))))), + compile_lambda_body(exp, fun_entry)), + after_lambda); +} + + + + Compile-lambda + + The function + compile_lambda_expression + + + uses the special combiner + + tack-on-instruction-sequence + + tack_@on_@instruction_@sequence + + + (from section) rather + than + + append-instruction-sequences + + + append_instruction_@sequences + + + to append the + + procedure + function + + body to the lambda expression code, 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 the sequence only because + that was a convenient place to put it. + + + + + Compile-lambda-body + + The function + compile_lambda_body + + + constructs the code for the body of the + + procedure. + function. + + This code begins with a label for the entry point. Next come instructions + that will cause the runtime evaluation environment to switch to the correct + environment for evaluating the + + procedure + function + + bodynamely, the + definition + environment of the + + procedure, + function, + + extended to include the bindings of the + + formal + + parameters to the arguments + with which the + + procedure + function + + is called. After this comes the code for the + + + + sequence of expressions that makes up the + + + + procedure body. + + function body, augmented to ensure that it ends with a return statement. + + + + + The sequence is compiled with linkage return and target + val so that it will end by returning from + the procedure with the procedure result in + val. + + + The + augmented body is compiled with target val so that its return value will be + placed in val. The linkage descriptor passed to this compilation is + irrelevant, as it will be ignored. + The augmented function body is a sequence ending with a return statement. + Compilation of a sequence of statements + uses the linkage "next" for all its component statements except the last, + for which it uses the given linkage. + In this case, the last statement is a return statement, and + as we will see in section, + a return statement always uses the + "return" linkage + descriptor for its return expression. Thus all function bodies will end with a + "return" + linkage, not the "next" we + pass as the linkage argument to compile in + compile_lambda_body. + Since a linkage argument is required, we arbitrarily pick "next". + + + + compile_lambda_body + compile_lambda_body + append_return_undefined + +(define (compile-lambda-body exp proc-entry) + (let ((formals (lambda-parameters exp))) + (append-instruction-sequences + (make-instruction-sequence '(env proc argl) '(env) + `(,proc-entry + (assign env (op compiled-procedure-env) (reg proc)) + (assign env + (op extend-environment) + (const ,formals) + (reg argl) + (reg env)))) + (compile-sequence (lambda-body exp) 'val 'return)))) + + +function compile_lambda_body(exp, fun_entry) { + const params = lambda_parameter_symbols(exp); + return append_instruction_sequences( + make_instruction_sequence(list("env", "fun", "argl"), + list("env"), + list(fun_entry, + assign("env", + list(op("compiled_function_env"), + reg("fun"))), + assign("env", + list(op("extend_environment"), + constant(params), + reg("argl"), + reg("env"))))), + compile(append_return_undefined(lambda_body(exp)), + "val", "next")); +} + + + + + + + + To ensure that all functions end by executing a return statement, + compile_@lambda_@body + appends to the lambda body a return statement whose return expression is the literal + undefined. + To do so, it uses the function append_@return_@undefined, + which constructs the parsers tagged-list representation (from section) of a + sequence consisting of the body and a return undefined; statement. + + append_return_undefined + +function append_return_undefined(body) { + return list("sequence", list(body, + list("return_statement", + list("literal", undefined)))); +} + + + This simple transformation of lambda bodies is a third way to ensure that a + function that does not return explicitly has the return value + return valueundefined as + undefined. + In the metacircular evaluator, we used a return-value object, which also played a + role in stopping a sequence evaluation. + In the explicit-control evaluator, functions that did not return explicitly continued + to an entry point that stored undefined + in val. + See exercise for a more elegant way + to handle insertion of return statements. + + + + + + Footnote pointed out that the compiler does not + identify all instances of + compiler for JavaScriptdead code analysis + dead code. What would be required of a compiler to + detect all instances of dead code? + + Hint: The answer depends on how we define dead code. One possible (and useful) + definition is code following a return statement in a sequencebut + what about code in the consequent + branch of if (false) $\ldots$ or + code following a + call to run_forever() in exercise? + + + + + + The current design of append_return_undefined + is a bit crude: It always appends a + return undefined; to a lambda body, even if there is already a return statement in every execution + path of the body. Rewrite + append_return_undefined so that it inserts + return undefined; at the end of only + those paths that do not contain a return statement. Test your + solution on the functions below, substituting any expressions for + $e_1$ and + $e_2$ + and any (non-return) statements for + $s_1$ and + $s_2$. + In t, a return statement should be + added either at both (*)'s or just at + (**). + In w and + h, a return statement should be added + at one of the (*)'s. + In m, no return statement should be added. + + +function t(b) { function w(b) { function m(b) { function h(b1, b2) { + if (b) { if (b) { if (b) { if (b1) { + $s_1~~$ return $e_1;~$ return $e_1;~$ return $e_1$; + (*) } else { + } else { } else { } else { if (b2) { + $s_2~~$ $s_1~~$ return $e_2$; $s_1$ + (*) (*) (*) + } } } } else { + (**) (*) return $e_2$; +} } } } + (*) + } + (*) + } + + + + + + +
diff --git a/xml/cn/chapter5/section5/subsection3.xml b/xml/cn/chapter5/section5/subsection3.xml new file mode 100644 index 000000000..96a489ed1 --- /dev/null +++ b/xml/cn/chapter5/section5/subsection3.xml @@ -0,0 +1,1460 @@ + + + Compiling + + + Combinations + + + Applications and Return Statements + + + + + + + compiler for JavaScriptprocedurefunction applications + compiler for JavaScriptcombinations + + + The essence of the compilation process is the compilation of + + procedure + function + + applications. The code for + + a combination + an application + + compiled with a given target and + linkage has the form + + +$\langle compilation\ of\ operator,\ target$ proc$,\ linkage$ next$\rangle$ +$\langle evaluate\ operands\ and\ construct\ argument\ list\ in$ argl$\rangle$ +$\langle compilation\ of\ procedure\ call\ with\ given\ target\ and\ linkage\rangle$ + + +compilation of function expression, target fun, linkage "next" +evaluate argument expressions and construct argument list in argl +compilation of function call with given target and linkage + + + + The registers env, + + + proc, + + + fun, + + + and argl may + have to be saved and restored during evaluation of the + + + operator and operands. + + + function and argument expressions. + + + Note that this is the only place in the compiler where a target other + than val is specified. + + + + + The required code is generated by + + compile-application. + compile_application. + + + This recursively compiles the + + operator, + function expression, + + to produce code that puts the + + procedure + function + + to be applied into + + proc, + fun, + + + and compiles the + + operands, + argument expressions, + + to produce code that evaluates the individual + + operands + argument expressions + + of the + application. The instruction sequences for the + + operands + argument expressions + + are combined + (by + + construct-arglist) + construct_arglist) + + + with code that constructs the list of + arguments in argl, and the resulting + argument-list code is combined with the + + procedure + function + + code and the code that performs the + + procedure + function + + call (produced by + + compile-procedure-call). + compile_function_call). + + + In appending the code sequences, the env + register must be preserved around the evaluation of the + + operator + function expression + + (since + evaluating the + + operator + function expression + + might modify env, which + will be needed to evaluate the + + operands), + argument expressions), + + and the + + + proc + + + fun + + + register must be preserved around the + construction of the argument list (since evaluating the + + operands + argument expressions + + might + modify + + + proc, + + + fun, + + + which will be needed for the actual + + procedure + function + + application). + + Continue + + + The continue register + + + must also be preserved throughout, since + it is needed for the linkage in the + + procedure + function + + call. + + compile_application + compile_application + +(define (compile-application exp target linkage) + (let ((proc-code (compile (operator exp) 'proc 'next)) + (operand-codes + (map (lambda (operand) (compile operand 'val 'next)) + (operands exp)))) + (preserving '(env continue) + proc-code + (preserving '(proc continue) + (construct-arglist operand-codes) + (compile-procedure-call target linkage))))) + + +function compile_application(exp, target, linkage) { + const fun_code = compile(function_expression(exp), "fun", "next"); + const argument_codes = map(arg => compile(arg, "val", "next"), + arg_expressions(exp)); + return preserving(list("env", "continue"), + fun_code, + preserving(list("fun", "continue"), + construct_arglist(argument_codes), + compile_function_call(target, linkage))); +} + + + + + The code to construct the argument list will evaluate each + + + operand + + + argument expression + + + into + val and then + + + cons + that value onto the argument list being accumulated in + argl. + + + combine + that value with the argument list being accumulated in + argl + using pair. + + + Since we + + + cons + the arguments onto argl + in sequence, + + + adjoin the arguments to the front of argl + in sequence, + + + we must start with the last argument and end with the first, so that the + arguments will appear in order from first to last in the resulting list. + Rather than waste an instruction by initializing + argl to the empty list + to set up for this sequence of evaluations, + we make the first code sequence construct the initial + argl. + The general form of the argument-list construction is thus as follows: + + +$\langle compilation\ of\ last\ operand,\ targeted\ to$ val$\rangle$ +(assign argl (op list) (reg val)) +$\langle compilation\ of\ next\ operand,\ targeted\ to$ val$\rangle$ +(assign argl (op cons) (reg val) (reg argl)) +$\ldots$ +$\langle compilation\ of\ first\ operand,\ targeted\ to$ val$\rangle$ +(assign argl (op cons) (reg val) (reg argl)) + + +compilation of last argument, targeted to val +assign("argl", list(op("list"), reg("val"))), +compilation of next argument, targeted to val +assign("argl", list(op("pair"), reg("val"), reg("argl"))), +$\ldots$ +compilation of first argument, targeted to val +assign("argl", list(op("pair"), reg("val"), reg("argl"))), + + + + + Argl + must be preserved around each operand + + + The argl register + must be preserved around each argument + + + evaluation except the first (so that arguments accumulated so far + wont be lost), and env must be + preserved around each + + + operand evaluation except the last (for use by subsequent operand evaluations). + + + argument evaluation except the last (for use by subsequent argument evaluations). + + + + + + + Compiling this argument code is a bit tricky, because of the special + treatment of the first + + operand + argument expression + + to be evaluated and the need to preserve + argl and env in + different places. The + + construct-arglist + construct_arglist + + + + procedure + function + + takes as arguments the code that evaluates the individual + + operands. + argument expressions. + + If there are no + + operands + argument expressions + + at all, it simply emits the instruction + + + (assign argl (const ())) + + +assign(argl, constant(null)) + + + Otherwise, + + construct-arglist + construct_arglist + + + creates code that initializes argl with the + last argument, and appends code that evaluates the rest of the arguments and + adjoins them to argl in succession. In order + to process the arguments from last to first, we must reverse the list of + + operand + argument + + code sequences from the order supplied by + + compile-application. + compile_application. + + + + construct_arglist + construct_arglist + +(define (construct-arglist operand-codes) + (let ((operand-codes (reverse operand-codes))) + (if (null? operand-codes) + (make-instruction-sequence '() '(argl) + '((assign argl (const ())))) + (let ((code-to-get-last-arg + (append-instruction-sequences + (car operand-codes) + (make-instruction-sequence '(val) '(argl) + '((assign argl (op list) (reg val))))))) + (if (null? (cdr operand-codes)) + code-to-get-last-arg + (preserving '(env) + code-to-get-last-arg + (code-to-get-rest-args + (cdr operand-codes)))))))) + +(define (code-to-get-rest-args operand-codes) + (let ((code-for-next-arg + (preserving '(argl) + (car operand-codes) + (make-instruction-sequence '(val argl) '(argl) + '((assign argl + (op cons) (reg val) (reg argl))))))) + (if (null? (cdr operand-codes)) + code-for-next-arg + (preserving '(env) + code-for-next-arg + (code-to-get-rest-args (cdr operand-codes)))))) + + +function construct_arglist(arg_codes) { + if (is_null(arg_codes)) { + return make_instruction_sequence(null, list("argl"), + list(assign("argl", constant(null)))); + } else { + const rev_arg_codes = reverse(arg_codes); + const code_to_get_last_arg = + append_instruction_sequences( + head(rev_arg_codes), + make_instruction_sequence(list("val"), list("argl"), + list(assign("argl", + list(op("list"), reg("val")))))); + return is_null(tail(rev_arg_codes)) + ? code_to_get_last_arg + : preserving(list("env"), + code_to_get_last_arg, + code_to_get_rest_args(tail(rev_arg_codes))); + } +} +function code_to_get_rest_args(arg_codes) { + const code_for_next_arg = + preserving(list("argl"), + head(arg_codes), + make_instruction_sequence(list("val", "argl"), list("argl"), + list(assign("argl", list(op("pair"), + reg("val"), reg("argl")))))); + return is_null(tail(arg_codes)) + ? code_for_next_arg + : preserving(list("env"), + code_for_next_arg, + code_to_get_rest_args(tail(arg_codes))); +} + + + + + + + Applying + + procedures + functions + + + + + + + + After evaluating the elements of a + + combination, + function application, + + the compiled code must apply the + + procedure + function + + in + + + proc + + + fun + + + to the arguments in + argl. The code performs essentially the same + dispatch as the apply + + procedure + function + + in the metacircular evaluator of + section or the + + apply-dispatch + apply_dispatch + + + entry point in the explicit-control evaluator of + section. It checks whether + the + + procedure + function + + to be applied is a primitive + + procedure + function + + or a compiled + + procedure. + function. + + For a primitive + + procedure, + function, + + it uses + + apply-primitive-procedure; + + apply_primitive_function; + + + we will see shortly how it handles compiled + + procedures. + functions. + + The + + procedure-application + function-application + + code has the following form: + + + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-branch)) +compiled-branch + $\langle code\ to\ apply\ compiled\ procedure\ with\ given\ target\ and\ appropriate\ linkage\rangle$ +primitive-branch + (assign $\langle target\rangle$ + (op apply-primitive-procedure) + (reg proc) + (reg argl)) + $\langle linkage \rangle$ +after-call + + +$\texttt{ }\texttt{ }$test(list(op("primitive_function"), reg("fun"))), + branch(label("primitive_branch")), +"compiled_branch", + code to apply compiled function with given target and appropriate linkage +"primitive_branch", + assign(target, + list(op("apply_primitive_function"), reg("fun"), reg("argl"))), + linkage +"after_call" + + + Observe that the compiled branch must skip around the primitive + branch. Therefore, if the linkage for the original + + procedure + function + + call was + + + next, + + + "next", + + + the compound branch must use a + linkage that jumps to a label that is inserted after the primitive branch. + (This is similar to the linkage used for the true branch in + + compile-if.) + compile_conditional.) + + + compile_function_call + compile_function_call + +(define (compile-procedure-call target linkage) + (let ((primitive-branch (make-label 'primitive-branch)) + (compiled-branch (make-label 'compiled-branch)) + (after-call (make-label 'after-call))) + (let ((compiled-linkage + (if (eq? linkage 'next) after-call linkage))) + (append-instruction-sequences + (make-instruction-sequence '(proc) '() + `((test (op primitive-procedure?) (reg proc)) + (branch (label ,primitive-branch)))) + (parallel-instruction-sequences + (append-instruction-sequences + compiled-branch + (compile-proc-appl target compiled-linkage)) + (append-instruction-sequences + primitive-branch + (end-with-linkage linkage + (make-instruction-sequence '(proc argl) + (list target) + `((assign ,target + (op apply-primitive-procedure) + (reg proc) + (reg argl))))))) + after-call)))) + + +function compile_function_call(target, linkage) { + const primitive_branch = make_label("primitive_branch"); + const compiled_branch = make_label("compiled_branch"); + const after_call = make_label("after_call"); + const compiled_linkage = linkage === "next" ? after_call : linkage; + return append_instruction_sequences( + make_instruction_sequence(list("fun"), null, + list(test(list(op("is_primitive_function"), reg("fun"))), + branch(label(primitive_branch)))), + append_instruction_sequences( + parallel_instruction_sequences( + append_instruction_sequences( + compiled_branch, + compile_fun_appl(target, compiled_linkage)), + append_instruction_sequences( + primitive_branch, + end_with_linkage(linkage, + make_instruction_sequence(list("fun", "argl"), + list(target), + list(assign( + target, + list(op("apply_primitive_function"), + reg("fun"), reg("argl")))))))), + after_call)); +} + + + The primitive and compound branches, like the true + and false branches in + + compile-if, + compile_@conditional, + + are appended using + + parallel-instruction-sequences + + + parallel_instruction_sequences + + + rather than the ordinary + + append-instruction-sequences, + + + append_instruction_sequences, + + + because they will not be executed sequentially. + + + + + + Applying compiled + + procedures + functions + + + + + + + The code that handles procedure + The handling of function + + application + + + + + and return + + + is the most subtle part of the + + + compiler, even though the instruction sequences it generates are very short. + + + compiler. + + + A compiled + + procedure + function + + (as constructed by + + compile-lambda) + compile_lambda_expression) + + + has an entry point, which is a label that designates where the code for the + + procedure + function + + starts. The code at this entry point computes a result in + val + + and returns by executing the instruction (goto (reg continue)). + and ends by executing the instructions from a compiled return statement. + + + + + + The 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 entry point, it + saves the continuation of the function call to the stack, + followed by a mark that allows reverting the stack to the + state right before the call with the continuation on top. + + +$\texttt{ }\texttt{ }$// set up for return from function + save("continue"), + push_marker_to_stack(), + // jump to the function's entry point + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), + + + Compiling a return statement (with + compile_return_statement) + generates corresponding code for reverting the stack and restoring + and jumping to continue. + + +$\texttt{ }\texttt{ }$revert_stack_to_marker(), + restore("continue"), + evaluate the return expression and store the result in val + go_to(reg("continue")), // $\texttt{"return"}$-linkage code + + + Unless a function enters an infinite loop, + it will end by executing the above return code, + resulting from either a return statement in the program + or one inserted by compile_@lambda_@body + return valueundefined as + to return undefined. + Because the execution of a function body always ends with a return, + there is no need here for a mechanism like the return_@undefined + entry point from section. + + + + + + + Thus, we might expect the code for a + compiled-procedure application (to be + generated by + compile-proc-appl) with a + given target and linkage to look like this if the linkage + is a label: + + + Straightforward 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. + It would look like this if the linkage is a label: + + + + + (assign continue (label proc-return)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +proc-return + (assign $target$ (reg val)) ; included if target is not $\texttt{val}$ + (goto (label $\langle linkage\rangle$)) ; linkage code + + +$\texttt{ }\texttt{ }$assign("continue", label("fun_return")), // where function should return to + save("continue"), // will be restored by the function + push_marker_to_stack(), // allows the function to revert stack to find $\texttt{fun_return}$ + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), // eventually reverts stack, restores and jumps to $\texttt{continue}$ +"fun_return", // the function returns to here + assign($\mathit{target}$, reg("val")), // included if target is not $\texttt{val}$ + go_to(label(linkage)), // linkage code + + + or like + + + this if the linkage is return. + + + thissaving the callers continuation at the start in + order to restore and go to it at the + endif the linkage is "return" (that is, if the application is in a return statement and its value is the result to be returned): + + + + + (save continue) + (assign continue (label proc-return)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +proc-return + (assign $\langle target\rangle$ (reg val)) ; included if target is not $\texttt{val}$ + (restore continue) + (goto (reg continue)) ; linkage code + + +$\texttt{ }\texttt{ }$save("continue"), // save the caller's continuation + assign("continue", label("fun_return")), // where function should return to + save("continue"), // will be restored by the function + push_marker_to_stack(), // allows the function to revert stack to find $\texttt{fun_return}$ + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), // eventually reverts stack, restores and jumps to $\texttt{continue}$ +"fun_return", // the function returns to here + assign($\mathit{target}$, reg("val")), // included if target is not $\texttt{val}$ + restore("continue"), // restore the caller's continuation + go_to(reg("continue")), // linkage code + + + This code sets up continue so that the + + procedure + function + + will return to a label + + + proc-return + + + fun_return + + + and jumps to the + + procedures + functions + + entry point. The code at + + proc-return + fun_return + + transfers the + + procedures + functions + + result from val to the target register (if + necessary) and then jumps to the location specified by the linkage. + (The linkage is always + + + return + + + "return" + + + or a label, + because + + compile-procedure-call + + + compile_@function_@call + + + replaces a + + + next + + + "next" + + + linkage for the + + + compound-procedure branch by an + after-call + + compound-function branch by an + after_@call + + label.) + + + + Before 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. Matching + revert_@stack_@to_@marker() and + restore("continue") instructions + are generated by compile_@return_@statement for each return statement in the body of the + function.Elsewhere in the compiler, all saves and restores of registers +are generated by preserving +to preserve a register's value across a sequence of instructions +by saving it before those instructions and restoring it +afterfor example over the evaluation of the predicate of a +conditional. But this mechanism cannot generate instructions to +save and restore continue for a function application and the +corresponding return, because these are compiled separately and +are not contiguous. Instead, these saves and restores must be +explicitly generated by compile_@fun_@appl and +compile_@return_@statement. + + + + + + In fact, if + the target is not val, + + that is + the above is + + exactly the code our compiler will generate.Actually, we signal + an error when the target is not val and the + linkage is + + + return, + + + "return", + + + since the only place we request + + + returnlinkages + + + a "return" linkage + + + is in compiling + + procedures, + return expressions, + + and our convention is that + + procedures + functions + + return their values in val. + Usually, however, the target is val (the only + time the compiler specifies a different register is when targeting the + evaluation of + + an operator + a function expression + + to + + + proc), + + + fun), + + + so the + + procedure + function + + result is put directly into + the target register and there is no need to + + return + jump + + to a special + location that copies it. Instead we simplify the code by + setting up continue so that the + + procedure + called function + + will + return + directly to the place specified by the callers linkage: + + + + $\langle set\ up$ continue $for\ linkage\rangle$ + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) + + +set up continue for linkage and push the marker +assign("val", list(op("compiled_function_entry"), reg("fun"))), +go_to(reg("val")), + + + If the linkage is a label, we set up continue + so that the + + procedure + function + + will return tocontinue at + that label. (That is, the + + (goto (reg continue)) + + go_to(reg("continue")) + + + the + + procedure + called function + + ends with becomes equivalent to the + + (goto (label linkage)) + + go_to(label(linkage)) + + + at + + proc-return + fun_return + + + above.) + + + (assign continue (label linkage)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) + + +assign("continue", label(linkage)), +save("continue"), +push_marker_to_stack(), +assign("val", list(op("compiled_function_entry"), reg("fun"))), +go_to(reg("val")), + + + If the linkage is + + + return, + + + "return", + + + we dont need to + + + set up continue at all: + + + assign continue: + + + It already holds the desired location. + (That is, the + + (goto (reg continue)) + + go_to(reg("continue")) + + + the + + procedure + called function + + ends with goes directly to the + place where the + + (goto (reg continue)) + + + go_to(reg("continue")) + + + at + + proc-return + fun_return + + + would have gone.) + + + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) + + +save("continue"), +push_marker_to_stack(), +assign("val", list(op("compiled_function_entry"), reg("fun"))), +go_to(reg("val")), + + + With this implementation of the + + + return + + + "return" + + + linkage, + the compiler generates + return statementtail recursion and + compiler for JavaScripttail-recursive code generated by + tail recursioncompiler and + tail-recursive code. + + Calling a procedure as the final step in a procedure body + A function call in a return statement whose value is the result to be returned + + does a direct transfer, without saving + + any information on the stack. + unnecessary information on the stack. + + + + Suppose instead that we had handled the case of a + + procedure + function + + call with a + linkage of return"return" and a target of + val in the same way as for a + non-val target. This would destroy tail + recursion. Our system would still + + + give + + + return + + the same value for any + + + + expression. + + + function call. + + + But each time we called a + + procedure, + function, + + we would save + continue + and return after the call to undo the (useless) save. These extra + saves would accumulate during a nest of + + procedure + function + + calls.Making a + compilertail recursion, stack allocation, and garbage-collection + compiler generate tail-recursive + + + code might seem like a straightforward idea. But + + + code is desirable, especially in the functional paradigm. + + + However, compilers for common languages, + including + + C and Pascal, + C and C++, + + do not always do this, and therefore these languages + cannot represent iterative processes in terms of + + procedure + function + + call alone. The difficulty with + tail recursiongarbage collection and + garbage collectiontail recursion and + stack allocation and tail recursion + tail recursion in these languages is that their + implementations use the stack to store + + procedure + function + + arguments and local + + variables + names + + as well as return addresses. The + + + Scheme + + + JavaScript + + + implementations described in this book store arguments and + + variables + names + + in memory to be garbage-collected. The reason for using the stack for + + variables + names + + and arguments is that it avoids the need for garbage collection + in languages that would not otherwise require it, and is generally + believed to be more efficient. Sophisticated + + Lisp + + compilers can, in fact, use the stack for arguments without destroying tail + recursion. (See + Hanson, Christopher P. + Hanson 1990 for a description.) There is also some + debate about whether stack allocation is actually more efficient than garbage + collection in the first place, but the details seem to hinge on fine + points of computer architecture. (See + Appel, Andrew W. + Appel 1987 and + Miller, James S. + Rozas, Guillermo Juan + Miller and Rozas 1994 + for opposing views on this issue.) + + + + + + Compile-proc-appl + The function compile_@fun_@appl + + + generates the above + + procedure-application + function-application + + code by considering four cases, + depending on whether the target for the call + is val and whether the linkage is + + + return. + + + "return". + + + Observe that the instruction sequences + are declared to modify all the registers, since executing the + + procedure + function + + body can change the registers in arbitrary ways.The + + variable + constant + + compiler for JavaScriptregister use + + all-regs + all_regs + + is bound to the list of names of all the registers: + + all_regs (compiler) + all_regs + +(define all-regs '(env proc val argl continue)) + + +const all_regs = list("env", "fun", "val", "argl", "continue"); + + + + + Also note that the code sequence for the case with target + val and linkage + return + is declared to need + continue: Even though + continue is not explicitly used in the + two-instruction sequence, we must be sure that + continue will have the correct + value when we enter the compiled procedure. + + + + compile_fun_appl + compile_fun_appl + + (define (compile-proc-appl target linkage) + (cond ((and (eq? target 'val) (not (eq? linkage 'return))) + (make-instruction-sequence '(proc) all-regs + `((assign continue (label ,linkage)) + (assign val (op compiled-procedure-entry) + (reg proc)) + (goto (reg val))))) + ((and (not (eq? target 'val)) + (not (eq? linkage 'return))) + (let ((proc-return (make-label 'proc-return))) + (make-instruction-sequence '(proc) all-regs + `((assign continue (label ,proc-return)) + (assign val (op compiled-procedure-entry) + (reg proc)) + (goto (reg val)) + ,proc-return + (assign ,target (reg val)) + (goto (label ,linkage)))))) + ((and (eq? target 'val) (eq? linkage 'return)) + (make-instruction-sequence '(proc continue) all-regs + '((assign val (op compiled-procedure-entry) + (reg proc)) + (goto (reg val))))) + ((and (not (eq? target 'val)) (eq? linkage 'return)) + (error "return linkage, target not val - - COMPILE" + target)))) + + +function compile_fun_appl(target, linkage) { + const fun_return = make_label("fun_return"); + return target === "val" && linkage !== "return" + ? make_instruction_sequence(list("fun"), all_regs, + list(assign("continue", label(linkage)), + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), + reg("fun"))), + go_to(reg("val")))) + : target !== "val" && linkage !== "return" + ? make_instruction_sequence(list("fun"), all_regs, + list(assign("continue", label(fun_return)), + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), + reg("fun"))), + go_to(reg("val")), + fun_return, + assign(target, reg("val")), + go_to(label(linkage)))) + : target === "val" && linkage === "return" + ? make_instruction_sequence(list("fun", "continue"), + all_regs, + list(save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), + reg("fun"))), + go_to(reg("val")))) + : // $\texttt{target !== "val" \&\& linkage === "return"}$ + error(target, "return linkage, target not val -- compile"); +} + + + compiler for JavaScriptprocedurefunction applications + compiler for JavaScriptcombinations + + + + + We have shown how to generate tail-recursive linkage code for a + function application when the linkage is "return"that is, when the application is in a return statement +and its value is the result to be returned. Similarly, as explained in + section, the stack-marker mechanism used here (and in the + explicit-control evaluator) for the call and return produces + return statementtail recursion and + tail recursionreturn statement necessary for + tail-recursive behavior only in that situation. These two aspects of the code generated for function + application combine to ensure that when a function ends by returning + the value of a function call, no stack accumulates. + + + + + + + + + Compiling return statements + + + + The code for a + compiler for JavaScriptreturn statements + return statementhandling in compiler + return statement takes the following form, regardless of the given linkage and target: + + +revert_stack_to_marker(), +restore("continue"), // saved by $\texttt{compile\char`_fun\char`_appl}$ +evaluate the return expression and store the result in val +go_to(reg("continue")) // $\texttt{"return"}$-linkage code + + + The instructions to revert the stack using the marker and then restore + continue correspond to the + instructions generated by compile_@fun_@appl + to save continue and mark the stack. + The final jump to continue is + generated by the use of the "return" + linkage when compiling the return expression. + The function compile_@return_@statement + is different from all other code generators in that it ignores the target + and linkage argumentsit always compiles the return expression with + target val and linkage + "return". + + compile_return_statement + compile_return + +function compile_return_statement(stmt, target, linkage) { + return append_instruction_sequences( + make_instruction_sequence(null, list("continue"), + list(revert_stack_to_marker(), + restore("continue"))), + compile(return_expression(stmt), "val", "return")); +} + + + + + + diff --git a/xml/cn/chapter5/section5/subsection4.xml b/xml/cn/chapter5/section5/subsection4.xml new file mode 100644 index 000000000..83d70bda4 --- /dev/null +++ b/xml/cn/chapter5/section5/subsection4.xml @@ -0,0 +1,476 @@ + + + Combining Instruction Sequences + + + + + instruction sequence + + + This section describes the details on how instruction sequences are + represented and combined. Recall from + section that an instruction + sequence is represented as a list of the registers needed, the registers + modified, and the actual instructions. We will also consider a label + (symbol)(string) + to be a degenerate case of an instruction sequence, which + doesnt need or modify any registers. + So to determine the registers needed + and modified by instruction sequences we use the selectors + + registers_needed + registers_modified + instructions + registers_needed + +(define (registers-needed s) + (if (symbol? s) '() (car s))) + + (define (registers-modified s) + (if (symbol? s) '() (cadr s))) + +(define (statements s) + (if (symbol? s) (list s) (caddr s))) + + +function registers_needed(s) { + return is_string(s) ? null : head(s); +} +function registers_modified(s) { + return is_string(s) ? null : head(tail(s)); +} +function instructions(s) { + return is_string(s) ? list(s) : head(tail(tail(s))); +} + + + and to determine whether a given + sequence needs or modifies a given register we use the predicates + + needs_register + modifies_register + needs_register + +(define (needs-register? seq reg) + (memq reg (registers-needed seq))) + +(define (modifies-register? seq reg) + (memq reg (registers-modified seq))) + + +function needs_register(seq, reg) { + return ! is_null(member(reg, registers_needed(seq))); +} +function modifies_register(seq, reg) { + return ! is_null(member(reg, registers_modified(seq))); +} + + + In terms of these predicates and selectors, we can implement the + various instruction sequence combiners used throughout the compiler. + + + + The basic combiner is + + append-instruction-sequences. + + + append_instruction_sequences. + + + This takes as + arguments + + an arbitrary number of + two + + instruction sequences that are to be + executed sequentially and returns an instruction sequence whose statements + are the statements of + + all the + the two + + sequences appended together. + The subtle point is to determine the registers that are needed and modified by the resulting sequence. + It modifies those registers that + + are modified by any of the sequences; + are modified by either sequence; + + it needs those registers that must be initialized before the + first sequence can be run (the registers needed by the first sequence), together with those registers needed by + + any of the other sequences that are not initialized (modified) by sequences preceding it. + the second sequence that are not initialized (modified) by the first sequence. + + + + + + + The sequences are appended two at a time by + append-2-sequences. This + + The function append_instruction_sequences + + + is given two instruction sequences seq1 and + seq2 and returns the instruction sequence whose + + + statements + + + instructions + + + are the + + + statements + + + instructions + + + of seq1 followed + by the + + + statements + + + instructions + + + of seq2, whose modified + registers are those registers that are modified by either + seq1 or seq2, and + whose needed registers are the registers needed by + seq1 together with those registers needed by + seq2 that are not modified by + seq1. (In terms of set operations, the new set + of needed registers is the union of the set of registers needed by + seq1 with the set difference of the registers + needed by seq2 and the registers modified by + seq1.) Thus, + + append-instruction-sequences + + append_instruction_sequences + + + is implemented as follows: + + append_instruction_sequences + append_instruction_sequences + +(define (append-instruction-sequences . seqs) + (define (append-2-sequences seq1 seq2) + (make-instruction-sequence + (list-union (registers-needed seq1) + (list-difference (registers-needed seq2) + (registers-modified seq1))) + (list-union (registers-modified seq1) + (registers-modified seq2)) + (append (statements seq1) (statements seq2)))) + (define (append-seq-list seqs) + (if (null? seqs) + (empty-instruction-sequence) + (append-2-sequences (car seqs) + (append-seq-list (cdr seqs))))) + (append-seq-list seqs)) + + +function append_instruction_sequences(seq1, seq2) { + return make_instruction_sequence( + list_union(registers_needed(seq1), + list_difference(registers_needed(seq2), + registers_modified(seq1))), + list_union(registers_modified(seq1), + registers_modified(seq2)), + append(instructions(seq1), instructions(seq2))); +} + + + + + This + + procedure + function + + uses some simple operations for manipulating sets + represented as lists, similar to the (unordered) set representation + described in section: + + list_union + list_difference + list_union + +(define (list-union s1 s2) + (cond ((null? s1) s2) + ((memq (car s1) s2) (list-union (cdr s1) s2)) + (else (cons (car s1) (list-union (cdr s1) s2))))) + +(define (list-difference s1 s2) + (cond ((null? s1) '()) + ((memq (car s1) s2) (list-difference (cdr s1) s2)) + (else (cons (car s1) + (list-difference (cdr s1) s2))))) + + +function list_union(s1, s2) { + return is_null(s1) + ? s2 + : is_null(member(head(s1), s2)) + ? pair(head(s1), list_union(tail(s1), s2)) + : list_union(tail(s1), s2); +} +function list_difference(s1, s2) { + return is_null(s1) + ? null + : is_null(member(head(s1), s2)) + ? pair(head(s1), list_difference(tail(s1), s2)) + : list_difference(tail(s1), s2); +} + + + + + + + Preserving, + + + The function preserving, + + + the second major instruction + sequence combiner, takes a list of registers + regs and two instruction sequences + seq1 and seq2 that + are to be executed sequentially. It returns an instruction sequence whose + + + statements + + + instructions + + + are the + + + statements + + + instructions + + + of seq1 followed + by the + + + statements + + + instructions + + + of seq2, with appropriate + save and restore + instructions around seq1 to protect the + registers in regs that are modified by + seq1 but needed by + seq2. To accomplish this, + preserving first creates a sequence that has + the required saves followed by the + + + statements + + + instructions + + + of seq1 followed by the required + restores. This sequence needs the registers + being saved and restored in addition to the registers needed by + seq1, and modifies the registers modified by + seq1 except for the ones being saved and + restored. This augmented sequence and seq2 + are then appended in the usual way. The following + + procedure + function + + implements this strategy recursively, walking down the list of registers to + be preserved:Note that preserving + calls append with three + appendarbitrarywith arbitrary number of arguments + arguments. Though the definition of append + shown in this book accepts only two arguments, Scheme standardly provides an + append procedure + that takes an arbitrary number of arguments. + + preserving + preserving + +(define (preserving regs seq1 seq2) + (if (null? regs) + (append-instruction-sequences seq1 seq2) + (let ((first-reg (car regs))) + (if (and (needs-register? seq2 first-reg) + (modifies-register? seq1 first-reg)) + (preserving (cdr regs) + (make-instruction-sequence + (list-union (list first-reg) + (registers-needed seq1)) + (list-difference (registers-modified seq1) + (list first-reg)) + (append `((save ,first-reg)) + (statements seq1) + `((restore ,first-reg)))) + seq2) + (preserving (cdr regs) seq1 seq2))))) + + +function preserving(regs, seq1, seq2) { + if (is_null(regs)) { + return append_instruction_sequences(seq1, seq2); + } else { + const first_reg = head(regs); + return needs_register(seq2, first_reg) && + modifies_register(seq1, first_reg) + ? preserving(tail(regs), + make_instruction_sequence( + list_union(list(first_reg), + registers_needed(seq1)), + list_difference(registers_modified(seq1), + list(first_reg)), + append(list(save(first_reg)), + append(instructions(seq1), + list(restore(first_reg))))), + seq2) + : preserving(tail(regs), seq1, seq2); + } +} + + + + + + Another sequence combiner, + + tack-on-instruction-sequence, + + + tack_on_instruction_sequence, + + + is used by + + compile-lambda + compile_lambda_expression + + + to append a + + procedure + function + + body to another sequence. Because the + + procedure + function + + body is not in line to be executed as part of the combined + sequence, its register use has no impact on the register use of the sequence + in which it is embedded. We thus ignore the + + procedure + function + + bodys sets of needed and modified + registers when we tack it onto the other sequence. + + tack_on_instruction_sequence + tack_on_instruction_sequence + +(define (tack-on-instruction-sequence seq body-seq) + (make-instruction-sequence + (registers-needed seq) + (registers-modified seq) + (append (statements seq) (statements body-seq)))) + + +function tack_on_instruction_sequence(seq, body_seq) { + return make_instruction_sequence( + registers_needed(seq), + registers_modified(seq), + append(instructions(seq), instructions(body_seq))); +} + + + + + + + Compile-if + The functions + compile_conditional + + + and + + compile-procedure-call + + compile_function_call + + + use a special combiner called + + + parallel-instruction-sequences + + + parallel_instruction_sequences + + + to append the two alternative branches that follow a test. The two branches + will never be executed sequentially; for any particular evaluation of the + test, one branch or the other will be entered. Because of this, the + registers needed by the second branch are still needed by the combined + sequence, even if these are modified by the first branch. + + parallel_instruction_sequences + parallel_instruction_sequences + +(define (parallel-instruction-sequences seq1 seq2) + (make-instruction-sequence + (list-union (registers-needed seq1) + (registers-needed seq2)) + (list-union (registers-modified seq1) + (registers-modified seq2)) + (append (statements seq1) (statements seq2)))) + + +function parallel_instruction_sequences(seq1, seq2) { + return make_instruction_sequence( + list_union(registers_needed(seq1), + registers_needed(seq2)), + list_union(registers_modified(seq1), + registers_modified(seq2)), + append(instructions(seq1), instructions(seq2))); +} + + + + + instruction sequence + + diff --git a/xml/cn/chapter5/section5/subsection5.xml b/xml/cn/chapter5/section5/subsection5.xml new file mode 100644 index 000000000..a27e56a8a --- /dev/null +++ b/xml/cn/chapter5/section5/subsection5.xml @@ -0,0 +1,1453 @@ + + + An Example of Compiled Code + + + + + compiler for JavaScriptexample compilation + factorialcompilation of + + + 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 + ''` (back quote) + quotation marksback quotes + back quotes + string(s)typed over multiple lines + back quotes + `$\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 dont 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. 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)) + + + + + + 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 + + procedures definition + functions 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 + + doesnt modify the env register, the + optional save + and restore shown above arent + generated. (The + + procedure + function + + code at + entry2entry1 + isnt 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)) + + + + + + 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: + Because of the append_return_undefined in + compile_lambda_body, the body actually + consists of a sequence with two return statements. However, the dead-code check + in compile_@sequence will stop after the compilation + of the first return statement, + so the body effectively consists of only a single 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", + + + + + + 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 + + + dont result in + any saving of registers, because in this case those evaluations dont + 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", + + + + + + 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. + + +
+ + compiled_factorial_1 + +;; construct the procedure and skip over code for the procedure body + (assign val + (op make-compiled-procedure) (label entry2) (reg env)) + (goto (label after-lambda1)) + +entry2 ; calls to factorial will enter here + (assign env (op compiled-procedure-env) (reg proc)) + (assign env + (op extend-environment) (const (n)) (reg argl) (reg env)) +;; begin actual procedure body + (save continue) + (save env) + +;; compute (= n 1) + (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 ; val now contains result of (= n 1) + (restore env) + (restore continue) + (test (op false?) (reg val)) + (branch (label false-branch4)) +true-branch5 ; return 1 + (assign val (const 1)) + (goto (reg continue)) + +false-branch4 +;; compute and return $\texttt{(* (factorial (- n 1)) n)}$ + (assign proc (op lookup-variable-value) (const *) (reg env)) + (save continue) + (save proc) ; save * procedure + (assign val (op lookup-variable-value) (const n) (reg env)) + (assign argl (op list) (reg val)) + (save argl) ; save partial argument list for * + +;; compute $\texttt{(factorial (- n 1))}$, which is the other argument for * + (assign proc + (op lookup-variable-value) (const factorial) (reg env)) + (save proc) ; save factorial procedure + + +// construct the function and skip over the code for the function body + assign("val", list(op("make_compiled_function"), + label("entry1"), reg("env"))), + go_to(label("after_lambda2")), +"entry1", // calls to $\texttt{factorial}$ will enter here + assign("env", list(op("compiled_function_env"), reg("fun"))), + assign("env", list(op("extend_environment"), constant(list("n")), + reg("argl"), reg("env"))), +// begin actual function body + revert_stack_to_marker(), // starts with a return statement + restore("continue"), + save("continue"), // preserve registers across predicate + save("env"), +// compute $\texttt{n === 1}$ + 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", // $\texttt{val}$ now contains result of $\texttt{n === 1}$ + restore("env"), + restore("continue"), + test(list(op("is_falsy"), reg("val"))), + branch(label("false_branch4")), +"true_branch3", // return 1 + assign("val", constant(1)), + go_to(reg("continue")), +"false_branch4", +// compute and return $\texttt{factorial(n - 1) * n}$ + assign("fun", list(op("lookup_symbol_value"), constant("*"), reg("env"))), + save("continue"), + save("fun"), // save $\texttt{*}$ function + assign("val", list(op("lookup_symbol_value"), constant("n"), reg("env"))), + assign("argl", list(op("list"), reg("val"))), + save("argl"), // save partial argument list for $\texttt{*}$ +// compute $\texttt{factorial(n - 1)}$ which is the other argument for $\texttt{*}$ + assign("fun", list(op("lookup_symbol_value"), + constant("factorial"), reg("env"))), + save("fun"), // save $\texttt{factorial}$ function + + + + Compilation of the declaration of the factorial + + procedure + function + + (continued on next page). + + +
+
+ + compiled_factorial_2 + + ;; $\texttt{compute (- n 1)}$, which is the argument for $\texttt{factorial}$ + (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-branch8)) +compiled-branch7 + (assign continue (label after-call6)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +primitive-branch8 + (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) + +after-call6 ; val now contains result of $\texttt{(- n 1)}$ + (assign argl (op list) (reg val)) + (restore proc) ; restore factorial +;; apply factorial + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-branch11)) +compiled-branch10 + (assign continue (label after-call9)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +primitive-branch11 + (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) + +after-call9 ; val now contains result of $\texttt{(factorial (- n 1))}$ + (restore argl) ; restore partial argument list for * + (assign argl (op cons) (reg val) (reg argl)) + (restore proc) ; restore * + (restore continue) + ;; apply * and return its value + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-branch14)) +compiled-branch13 +;; note that a compound procedure here is called tail-recursively + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +primitive-branch14 + (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) + (goto (reg continue)) +after-call12 +after-if3 +after-lambda1 +;; assign the procedure to the variable factorial + (perform + (op define-variable!) (const factorial) (reg val) (reg env)) + (assign val (const ok)) + + +// compute $\texttt{n - 1}$ which is the argument for $\texttt{factorial}$ + 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_branch10")), +"compiled_branch11", + assign("continue", label("after_call12")), + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), +"primitive_branch10", + assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))), +"after_call12", // $\texttt{val}$ now contains result of $\texttt{n - 1}$ + assign("argl", list(op("list"), reg("val"))), + restore("fun"), // restore $\texttt{factorial}$ +// apply $\texttt{factorial}$ + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_branch14")), +"compiled_branch15", + assign("continue", label("after_call16")), + save("continue"), // set up for compiled function $-$ + push_marker_to_stack(), // return in function will restore stack + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), +"primitive_branch14", + assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))), +"after_call16", // $\texttt{val}$ now contains result of $\texttt{factorial(n - 1)}$ + restore("argl"), // restore partial argument list for $\texttt{*}$ + assign("argl", list(op("pair"), reg("val"), reg("argl"))), + restore("fun"), // restore $\texttt{*}$ + restore("continue"), +// apply $\texttt{*}$ and return its value + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_branch18")), +"compiled_branch19", // note that a compound function here is called tail-recursively + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), +"primitive_branch18", + assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))), + go_to(reg("continue")), +"after_call20", +"after_cond5", +"after_lambda2", +// assign the function to the name $\texttt{factorial}$ + perform(list(op("assign_symbol_value"), + constant("factorial"), reg("val"), reg("env"))), + assign("val", constant(undefined)) + + + (continued) + +
+ + + + compiler for Schemeexample compilation + + + compiler for JavaScriptexample compilation + + + + + Consider the following declaration of a factorial + + procedure, + function, + + which is slightly different from the one given above: + + +(define (factorial-alt n) + (if (= n 1) + 1 + (* n (factorial-alt (- n 1))))) + + +function factorial_alt(n) { + return n === 1 + ? 1 + : n * factorial_alt(n - 1); +} + + + Compile this + + procedure + function + + and compare the resulting code with that produced for + factorial. Explain any differences you find. + Does either program execute more efficiently than the other? + + + + + Compile the + iterative processrecursive process vs. + recursive processiterative process vs. + iterative factorial + + procedure + function + + + +(define (factorial n) + (define (iter product counter) + (if (> counter n) + product + (iter (* counter product) + (+ counter 1)))) + (iter 1 1)) + + +function factorial(n) { + function iter(product, counter) { + return counter > n + ? product + : iter(product * counter, counter + 1); + } + return iter(1, 1); +} + + + Annotate the resulting code, showing the essential difference between + the code for iterative and recursive versions of + factorial that makes one process build up + stack space and the other run in constant stack space. + + + + + What + + + expression + + + program + + + was compiled to produce the code shown in + figure? + + + +
+ + + (assign val (op make-compiled-procedure) label entry16) + (reg env)) + (goto (label after-lambda15)) +entry16 + (assign env (op compiled-procedure-env) (reg proc)) + (assign env + (op extend-environment) (const (x)) (reg argl) (reg env)) + (assign proc (op lookup-variable-value) (const +) (reg env)) + (save continue) + (save proc) + (save env) + (assign proc (op lookup-variable-value) (const g) (reg env)) + (save proc) + (assign proc (op lookup-variable-value) (const +) (reg env)) + (assign val (const 2)) + (assign argl (op list) (reg val)) + (assign val (op lookup-variable-value) (const x) (reg env)) + (assign argl (op cons) (reg val) (reg argl)) + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-branch19)) +compiled-branch18 + (assign continue (label after-call17)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +primitive-branch19 + (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) +after-call17 + (assign argl (op list) (reg val)) + (restore proc) + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-branch22)) +compiled-branch21 + (assign continue (label after-call20)) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +primitive-branch22 + (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) + + +$\texttt{ }\texttt{ }$assign("val", list(op("make_compiled_function"), + label("entry1"), reg("env"))), +"entry1" + assign("env", list(op("compiled_function_env"), reg("fun"))), + assign("env", list(op("extend_environment"), + constant(list("x")), reg("argl"), reg("env"))), + revert_stack_to_marker(), + restore("continue"), + assign("fun", list(op("lookup_symbol_value"), constant("+"), reg("env"))), + save("continue"), + save("fun"), + save("env"), + assign("fun", list(op("lookup_symbol_value"), constant("g"), reg("env"))), + save("fun"), + assign("fun", list(op("lookup_symbol_value"), constant("+"), reg("env"))), + assign("val", constant(2)), + assign("argl", list(op("list"), reg("val"))), + assign("val", list(op("lookup_symbol_value"), constant("x"), reg("env"))), + assign("argl", list(op("pair"), reg("val"), reg("argl"))), + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_branch3")), +"compiled_branch4" + assign("continue", label("after_call5")), + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), +"primitive_branch3", + assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))), +"after_call5", + assign("argl", list(op("list"), reg("val"))), + restore("fun"), + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_branch7")), +"compiled_branch8", + assign("continue", label("after_call9")), + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), +"primitive_branch7", + assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))), +"after_call9", + assign("argl", list(op("list"), reg("val"))), + restore("env"), + assign("val", list(op("lookup_symbol_value"), constant("x"), reg("env"))), + assign("argl", list(op("pair"), reg("val"), reg("argl"))), + restore("fun"), + restore("continue"), + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_branch11")), + + + + An example of compiler output (continued on next page). + See exercise. + + +
+
+ + +after-call20 + (assign argl (op list), (reg val)) + (restore env) + (assign val (op lookup-variable-value) (const x) (reg env)) + (assign argl (op cons) (reg val) (reg argl)) + (restore proc) + (restore continue) + (test (op primitive-procedure?) (reg proc)) + (branch label primitive-branch25)) +compiled-branch24 + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) +primitive-branch25 + (assign val (op apply-primitive-procedure) (reg proc) (reg argl)) + (goto (reg continue)) +after-call23 +after-lambda15 + (perform (op define-variable!) (const f) (reg val) (reg env)) + (assign val (const ok)) + + +"compiled_branch12", + save("continue"), + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), +"primitive_branch11", + assign("val", list(op("apply_primitive_function"), reg("fun"), reg("argl"))), + go_to(reg("continue")), +"after_call13", +"after_lambda2", + perform(list(op("assign_symbol_value"), + constant("f"), reg("val"), reg("env"))), + assign("val", constant(undefined)) + + + (continued) + +
+ + factorialcompilation of + + + What + order of evaluationin compiler + + + compiler for Schemeorder of operand evaluation + + + compiler for JavaScriptorder of argument evaluation + + + order of evaluation does our compiler produce for + + operands of a combination? + arguments of an application? + + Is it left-to-right (as mandated by the ECMAScript specification), right-to-left, or some other order? + Where in the compiler is this order determined? Modify the compiler + so that it produces some other order of evaluation. (See the + discussion of order of evaluation for the explicit-control evaluator + in section.) How does changing the + order of + + operand + argument + + evaluation affect the efficiency of the code that + constructs the argument list? + + + + + One way to understand the compilers + compiler for JavaScriptstack usage + preserving + preserving mechanism for + optimizing stack usage is to see what extra operations would + be generated if we did not use this idea. Modify + preserving so + that it always generates the save and + restore operations. + Compile some simple expressions and identify the unnecessary stack + operations that are generated. + Compare the code to that generated with the + preserving mechanism intact. + + + + + + Our compiler is clever about avoiding unnecessary stack operations, + but it is not clever at all when it comes to compiling calls to the primitive + + procedures + functions + + of the language in terms of the primitive operations + supplied by the machine. For example, consider how much code is + compiled to compute + + (+ a 1): + a + 1: + + The code sets up an argument list in argl, puts + the primitive addition + + procedure + function + + (which it finds by looking up the symbol + + + + "+" + + in the environment) into + + proc, + fun, + + and tests whether the + + procedure + function + + is primitive or compound. The + compiler always generates code to perform the test, as well as code + for primitive and compound branches (only one of which will be executed). + We have not shown the part of the controller that implements + primitives, but we presume that these instructions make use of + primitive arithmetic operations in the machines data paths. Consider + how much less code would be generated if the compiler could + compiler for JavaScriptopen coding of primitives + open coding of primitives + open-code primitivesthat is, if it could generate code to + directly use these primitive machine operations. The expression + + (+ a 1) + a + 1 + + might be compiled into something as simple asWe have used + the same symbol + here to denote both the + source-language + + procedure + function + + and the machine operation. In general there will not be a + one-to-one correspondence between primitives of the source language + and primitives of the machine. + + +(assign val (op lookup-variable-value) (const a) (reg env)) +(assign val (op +) (reg val) (const 1)) + + +assign("val", list(op("lookup_symbol_value"), constant("a"), reg("env"))), +assign("val", list(op("+"), reg("val"), constant(1))) + + + In this exercise we will extend our compiler to support open coding of + selected primitives. Special-purpose code will be generated for calls to these primitive + + procedures + functions + + instead of the general + + procedure-application + function-application + + code. In order to support this, we will augment + our machine with special argument registers + arg1 and arg2. + The primitive arithmetic operations of the machine will take their + inputs from arg1 and + arg2. The results may be put into + val, arg1, or + arg2. +

+ The compiler must be able to recognize the application of an + open-coded primitive in the source program. We will augment the + dispatch in the compile + + procedure + function + + to recognize the names of these primitives in addition to the + + + reserved words + reserved words (the special forms) + it currently recognizes.Making + the primitives into reserved words is in general a bad idea, since a user + cannot then rebind these names to different procedures. + Moreover, if we add reserved words to + a compiler that is in use, existing programs that define procedures + with these names will stop working. See + exercise for ideas on how to avoid + this problem. + + + syntactic forms it currently recognizes. + + + For each + + special + syntactic + + form our compiler has a code + generator. In this exercise we will construct a family of code generators + for the open-coded primitives. +
    +
  1. + The open-coded primitives, unlike the + + special + syntactic + + forms, all need their + + operands + argument expressions + + evaluated. Write a code generator + + spread-arguments + spread_arguments + + + for use by all the open-coding code generators. + + Spread-arguments + + The function + spread_arguments + + + should take + + an operand list + a list of argument expressions + + and compile the given + + operands + argument expressions + + targeted to + successive argument registers. Note that an + + operand + argument expression + + may contain a call + to an open-coded primitive, so argument registers will have to be + preserved during + + operand + argument-expression + + evaluation. +
  2. +
  3. + + + The JavaScript operators + ===, + *, + -, and +, + among others, are implemented in the register machine as + primitive functions and are referred to in the global environment + with the symbols + "===", + "*", + "-", and + "+". In JavaScript, it is + not possible to redeclare these names, because they do not + meet the syntactic restrictions for names. This means it is safe + to open-code them. + + + For each of the primitive + + procedures + functions + + + =, + ===, + + *, + -, and +, + write a code generator that takes + + a combination with that operator, + + an application with a function expression that + names that function, + + + together with a target and a linkage descriptor, and + produces code to spread the arguments into the registers and then + perform the operation targeted to the given target with the given + linkage. + + You need only handle expressions with two operands. + + Make compile dispatch to these code + generators. +
  4. +
  5. + Try your new compiler on the factorial + example. Compare the resulting code with the result produced without + open coding. +
  6. + + +
  7. + Extend your code generators for + and + * so that they + can handle expressions with arbitrary numbers of operands. An + expression with more than two operands will have to be compiled into a + sequence of operations, each with only two inputs. +
  8. +
    +
    +
+
+
+ diff --git a/xml/cn/chapter5/section5/subsection6.xml b/xml/cn/chapter5/section5/subsection6.xml new file mode 100644 index 000000000..291311608 --- /dev/null +++ b/xml/cn/chapter5/section5/subsection6.xml @@ -0,0 +1,880 @@ + + + Lexical Addressing + + + + + compiler for JavaScriptlexical addressing + lexical addressing + + + One of the most common optimizations performed by compilers is the + optimization of + + variable + name + + lookup. Our compiler, as we have implemented it so far, generates code that + uses the + + + lookup-variable-value + + + lookup_symbol_value + + + operation of the evaluator machine. + + + This searches for a + + + This searches for a + + + + variable + name + + by comparing + + it + it + + with each + + + variable + + + name + + + that is currently bound, working frame + by frame outward through the runtime environment. This search can be + expensive if the frames are deeply nested or if there are many + + variables. + names. + + For example, consider the problem of looking up the value + of x while evaluating the expression + + (* x y z) + x * y * z + + in an application of the + + procedure + function of five arguments + + that is returned by + + +(let ((x 3) (y 4)) + (lambda (a b c d e) + (let ((y (* a b x)) + (z (+ c d x))) + (* x y z)))) + + +((x, y) => + (a, b, c, d, e) => + ((y, z) => x * y * z)(a * b * x, c + d + x))(3, 4) + + + + + Since a let expression is just syntactic + sugar for a lambda + combination, this expression is equivalent to + + +((lambda (x y) + (lambda (a b c d e) + ((lambda (y z) (* x y z)) + (* a b x) + (+ c d x)))) + 3 + 4) + + + + + + Each time + + + lookup-variable-value + + + lookup_symbol_value + + + searches for x, it must determine that + the symbol + + + x is not + eq? to + y or + z (in the first frame), nor to + a, b, + c, d, or + e (in the second frame). + + + "x" is not + equal to + "y" or + "z" (in the first frame), nor to + "a", + "b", + "c", + "d", or + "e" (in the second frame). + + + + + We will assume, for the moment, that our programs do not use + definethat variables are bound only + with lambda. + + + Because our language is + lexical scopingenvironment structure and + lexically scoped, the runtime environment for any + + + expression + + + component + + + will have a + structure that parallels the lexical structure of the program in which + the + + + expression + + + component + + + appears.This is not true if + we allow internal definitions, unless we scan them out. + See exercise. + + Thus, the compiler can know, when it analyzes the + above expression, + that each time the + + procedure + function + + is applied the + + + variable + x + + + binding for + x + + + in + + (* x y z) + x * y * z + + will be found two frames out from the + current frame and will be the first + + variable + binding + + in that frame. + + + We can exploit this fact by inventing a new kind of + + variable-lookup + name-lookup + + operation, + + lexical-address-lookup, + lexical_address_lookup, + + + that takes as arguments an environment and a + lexical addressinglexical address + lexical address that + consists of two numbers: a frame number, which specifies how many + frames to pass over, and a displacement number, which specifies + how many + + variables + bindings + + to pass over in that frame. + lexical_address_lookup + + Lexical-address-lookup + + The operation + lexical_address_lookup + + + will produce the value of the + + variable + name + + stored at that lexical address + relative to the current environment. If we add the + + lexical-address-lookup + lexical_address_lookup + + + operation to our machine, we can make the compiler generate code that + references + + variables + names + + using this operation, rather than + + lookup-variable-value. + lookup_symbol_value. + + + Similarly, our compiled code can use a new + lexical_address_assign + + lexical-address-set! + lexical_address_assign + + + operation instead of + + set-variable-value!. + assign_symbol_value. + + + + + With 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. + + + + + In order to generate such code, the compiler must be able to determine + the lexical address of a + + variable + name + + it is about to compile a reference + to. The lexical address of a + + variable + name + + in a program depends on where + one is in the code. For example, in the following program, the + address of x in expression + $e_1$ is (2,0)two frames back + and the first + + variable + name + + in the frame. At that point + y is at + address (0,0) and c is at address (1,2). + In expression + $e_2$, + x is at (1,0), + y is at (1,1), and + c is at (0,2). + + +((lambda (x y) + (lambda (a b c d e) + ((lambda (y z) e1) + e2 + (+ c d x)))) + 3 + 4) + + +((x, y) => + (a, b, c, d, e) => + ((y, z) => $e_1$)($e_2$, c + d + x))(3, 4); + + + + + + One way for the compiler to produce code that uses lexical addressing + is to maintain a data structure called a + compile-time environment + compile-time environment. This keeps track of which + + variables + bindings + + will be at which + positions in which frames in the runtime environment when a + particular + + variable-access + name-access + + operation is executed. The compile-time + environment is a list of frames, each containing a list of + + variables. + symbols. + + + + (There will of course be no values bound to the + variables, + since values are not computed at compile time.) + + + There will be no values associated with the symbols, + since values are not computed at compile time. + (Exercise + will change this, as an optimization for constants.) + + + The compile-time + environment becomes an additional argument to + compile and is + passed along to each code generator. The top-level call to + compile uses + + + an empty compile-time environment. + + + a compile-time-environment that includes the names of all primitive + functions and primitive values. + + + When + + + a lambda body + + + the body of a lambda expression + + + is compiled, + + compile-lambda-body + compile_lambda_body + + + extends the compile-time environment by a frame containing the + + + procedures + + functions + + + parameters, so that the + + sequence making up the + + body is compiled with that extended environment. + + + Similarly, when + the body of a block + is compiled, + compile_block + extends the compile-time environment by a frame containing the + compiler for JavaScriptscanning out internal declarations + scanned-out local names of the body. + + + At each point in the compilation, + + compile-variable + compile_name + + + and + + compile-assignment + + compile_assignment_declaration + + + use the compile-time + environment in order to generate the appropriate lexical addresses. + + + + + + Exercises + through describe how to + complete this sketch of the lexical-addressing strategy in order to + incorporate lexical lookup into the compiler. + Exercise describes another use for the + compile-time environment. + + + Exercises + through describe how to + complete this sketch of the lexical-addressing strategy in order to + incorporate lexical lookup into the compiler. + Exercises + and + describe other uses for the compile-time environment. + + + + + + + compiler for Schemelexical addressing + lexical addressing + + + compiler for JavaScriptlexical addressing + lexical addressing + + + + + Write a + + procedure + function + + lexical_address_lookup + lexical_address_assign + + lexical-address-lookup + lexical_address_lookup + + + that implements the new lookup operation. It should take two + argumentsa lexical address and a runtime environmentand + return the value of the + + variable + name + + stored at the specified lexical address. + + Lexical-address-lookup + The function + lexical_@address_@lookup + + + should signal an error if the value + + of the variable + of the name + + is the + + symbol *unassigned*.This + is the modification to variable lookup + required if we implement the + scanning out internal definitionscompilerin compiler + compiler for JavaScriptscanning out internal definitions + scanning method to eliminate internal + definitions (exercise). + We will need to eliminate these definitions in order for lexical addressing + to work. + + string "*unassigned*". + + + + The footnote can be safely dropped, because the scanning + method is the default in the JavaScript adaptation. + + Also write a + + procedure + function + + + lexical-address-set! + lexical_address_assign + + + that implements the operation that changes the value + + of the variable + of the name + + at a specified lexical address. + + + + + + Modify the compiler to maintain the + compile-time environment + compile-time environment as + described above. That is, add a compile-time-environment argument to + compile and the various code generators, and + extend it in + + compile-lambda-body. + + compile_lambda_body and + compile_block. + + + + + + + Write a + + procedure + function + + + find-variable + find_symbol + + + that takes as arguments a + + variable + symbol + + and a + compile-time environment + compile-time environment and + returns the lexical address of the + + variable + symbol + + with respect to that + environment. For example, in the program fragment that is shown above, the + compile-time environment during the compilation of expression + $e_1$ is + + ((y z) (a b c d e) (x y)). + + + + +list(list("y", "z"), + list("a", "b", "c", "d", "e"), + list("x", "y")) + + + + + + Find-variable + The function find_symbol + + + should produce + + +(find-variable 'c '((y z) (a b c d e) (x y))) + + +(1 2) + + +find_symbol("c", list(list("y", "z"), + list("a", "b", "c", "d", "e"), + list("x", "y"))); + + +list(1, 2) + + + + + +(find-variable 'x '((y z) (a b c d e) (x y))) + + + (2 0) + + +find_symbol("x", list(list("y", "z"), + list("a", "b", "c", "d", "e"), + list("x", "y"))); + + +list(2, 0) + + + + +(find-variable 'w '((y z) (a b c d e) (x y))) + + +not-found + + +find_symbol("w", list(list("y", "z"), + list("a", "b", "c", "d", "e"), + list("x", "y"))); + + +"not found" + + + + + + + Using + + find-variable + find_symbol + + + from exercise, + rewrite + + + compile-variable + and + compile-assignment + + + compile_assignment_declaration + and + compile_name + + + to output lexical-address instructions. + + + In cases where find-variable + returns not-found + (that is, where the variable is not in the + compile-time environment), you should have the code generators use the + evaluator operations, as before, to search for the binding. + (The only place a variable that is not found at compile time can be is in + the global environment, which is part of the runtime environment but + is not part of the compile-time + environment.Lexical addresses cannot be used to access + variables in the global environment, because these names can be defined + and redefined interactively at any time. With internal definitions + scanned out, as + in exercise, + the only definitions the + compiler sees are those at top level, which act on the global + environment. Compilation of a definition does not cause the defined + name to be entered in the compile-time environment. + Thus, if you wish, you may have the evaluator operations looks directly + in the global environment, which can be obtained with the operation + (opget-global-environment), + instead of having them search the whole runtime + environment found in env.) + + This is not possible in an interactive system that keeps + extending the program environment. Note the new + driver_loop in + and the same idea recurring in + compile_and_go in + . + + + + In cases where find_symbol + returns "not found" + (that is, where the name is not in the compile-time environment), + you should report a compile-time error. + + + Test the modified compiler on a few simple cases, such as the nested + + lambda + lambda + + combination at the beginning of this section. + + + + + + + We argued in section that + internal definitions for block structure should not be considered + real defines. Rather, a + procedure + body should be interpreted as if the internal variables being defined + were installed as ordinary lambda variables + initialized to their correct values using + set!. + Section and + exercise showed how to modify the + metacircular interpreter to accomplish this by + scanning out internal definitionscompilerin compiler + compiler for Schemescanning out internal definitions + scanning out internal + definitions. Modify the compiler to perform the same transformation + before it compiles a + procedure + body. + + + + + + + In JavaScript, an attempt to assign a new value to a name that is declared + as a + constant (in JavaScript)detecting assignment to + constant leads to an error. + Exercise shows how to + detect such errors at run time. With the techniques presented in this + section, we can detect attempts to assign a new value to a constant + at compile time. For this purpose, extend the functions + compile_lambda_body and + compile_block + to record in the compile-time environment whether a name is declared as a variable (using + let or as a parameter), or as + a constant (using + const + or + function). + Modify compile_assignment + to report an appropriate error when it detects an + assignment to a constant. + + + + + + + + + In this section we have focused on the use of the compile-time + environment to produce lexical addresses. But there are other uses + for compile-time environments. For instance, in + exercise we increased the efficiency of + compiled code by + compiler for Schemeopen-coding of primitives + open-coding of primitives + reserved words + compile-time environmentopen-coding and + open-coding primitive + procedures. Our implementation treated the names of open-coded + procedures as reserved words. If a program were to rebind such a name, the + mechanism described in exercise would still + open-code it as a primitive, ignoring the new binding. For example, + consider the + procedure + + +(lambda (+ * a b x y) + (+ (* a x) (* b y))) + + + which computes a linear combination of x and + y. We might call it with arguments + +matrix, *matrix, + and four matrices, but the open-coding compiler would still open-code the + + and the * in + (+ (* a x) (* b y)) + as primitive + and + *. Modify the open-coding compiler to consult + the compile-time environment in order to compile the correct code for + expressions involving the names of primitive + procedures. + (The code will work correctly as long as the program does not + define + or + set! + these names.) + + + + + + + Knowledge about constants at compile time opens the door to many + optimizations that allow us to generate more efficient object code. In + addition to the extension of the + compile-time environment + compile-time environment in + exercise to indicate names + declared as constants, we may store the + value of a constant if it is known at compile time, or other information + that can help us optimize the code. +
    +
  1. + A constant declaration such as constname=literal; allows us + to replace all occurrences of name within the scope of + the declaration by literal so that name + doesnt have to be looked up in the runtime environment. This optimization is + called constant propagation. Use an extended compile-time + environment to store literal constants, and modify + compile_name to use the stored + constant in the generated assign + instruction instead of the + lookup_symbol_value operation. +
  2. +
  3. + Function declaration is a derived component that expands to + constant declaration. Let us assume that the names of primitive functions + in the global environment are also considered constants. + If we further extend our compile-time + environment to keep track of which names refer to compiled + functions and which ones to primitive functions, we can move + the test that checks whether a function is compiled or primitive + from run time to compile time. This makes the object code more + efficient because it replaces a test that must be performed once per + function application in the generated code by one that is performed + by the compiler. Using such an extended compile-time environment, + modify compile_function_call + so that if it can be determined at + compile time whether the called function is compiled or primitive, + only the instructions in the + compiled_branch or the + primitive_branch + are generated. +
  4. +
  5. + Replacing constant names with their literal values as in part (a) + paves the way for another optimization, namely replacing + applications of primitive functions to literal values with the + compile-time computed result. This optimization, called + constant folding, replaces expressions such as + 40 + 2 by + 42 by performing the addition + in the compiler. Extend the compiler to perform constant folding for + arithmetic operations on numbers and for string concatenation. + +
  6. +
+
+
+
+
diff --git a/xml/cn/chapter5/section5/subsection7.xml b/xml/cn/chapter5/section5/subsection7.xml new file mode 100644 index 000000000..92d95865b --- /dev/null +++ b/xml/cn/chapter5/section5/subsection7.xml @@ -0,0 +1,1624 @@ + + + Interfacing Compiled Code to the Evaluator + + + + + compiler for JavaScriptinterfacing to evaluator + compiler for JavaScriptrunning compiled code + explicit-control evaluator for JavaScriptmodified for compiled code + + + + We have not yet explained how to load compiled code into the evaluator + machine or how to run it. We will assume that the explicit-control-evaluator + machine has been defined as in + section, with the additional + operations specified in footnote (section). + We will implement a + + procedure + function + + compile_and_go + + compile-and-go + compile_and_go + + + that compiles a + + Scheme expression, + JavaScript program, + + loads the resulting object code into the evaluator machine, + and causes the machine to run the code in the + evaluator global environment, print the result, and + enter the evaluators driver loop. We will also modify the evaluator + so that interpreted + + + expressions + + + components + + + can call compiled + + procedures + functions + + as well as interpreted ones. We can then put a compiled + + procedure + function + + into the machine and use the + evaluator to callit: + + compile_and_go_example + compile_and_go + +(compile-and-go + '(define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n)))) + + +;;; EC-Eval value: +ok + + +compile_and_go(parse(` +function factorial(n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + `)); + + +EC-evaluate value: +undefined + + + + +;;; EC-Eval input: + + +(factorial 5) + + +;;; EC-Eval value: +120 + + +EC-evaluate input: + + +factorial(5); + + +EC-evaluate value: +120 + + + + + + To allow the evaluator to handle compiled + + procedures + functions + + (for example, + to evaluate the call to factorial above), + we need to change the code at + + apply-dispatch + apply_dispatch + + + (section) so that it + recognizes compiled + + procedures + functions + + (as distinct from compound or primitive + + procedures) + functions) + + and transfers control directly to the entry point of the + compiled code:Of course, compiled + + procedures + functions + + as well as interpreted + + procedures + functions + + are compound (nonprimitive). For compatibility with the terminology used + in the explicit-control evaluator, in this section we will use + compound to mean interpreted (as opposed to + compiled). + + apply_dispatchmodified for compiled code + compiled_apply + +apply-dispatch + (test (op primitive-procedure?) (reg proc)) + (branch (label primitive-apply)) + (test (op compound-procedure?) (reg proc)) + (branch (label compound-apply)) + (test (op compiled-procedure?) (reg proc)) + (branch (label compiled-apply)) + (goto (label unknown-procedure-type)) + +compiled-apply + (restore continue) + (assign val (op compiled-procedure-entry) (reg proc)) + (goto (reg val)) + + +"apply_dispatch", + test(list(op("is_primitive_function"), reg("fun"))), + branch(label("primitive_apply")), + test(list(op("is_compound_function"), reg("fun"))), + branch(label("compound_apply")), + test(list(op("is_compiled_function"), reg("fun"))), + branch(label("compiled_apply")), + go_to(label("unknown_function_type")), + +"compiled_apply", + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), + + + + + Note the restore of continue at + + + compiled-apply. + + compiled_apply. + + + Recall that the evaluator was arranged so that at + + apply-dispatch, + apply_dispatch, + + + the continuation would be at the top of the stack. The compiled code entry + point, on the other hand, expects the continuation to be in + continue, so + continue must be + restored before the compiled code is executed. + + + At + 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. + + 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. + + + + + + To enable us to run some compiled code when we start the evaluator + machine, we add a branch instruction at + the beginning of the evaluator machine, which causes the machine to + go to a new entry point if the flag register + is set.Now that the evaluator machine starts + with a branch, we must always initialize the + flag register before starting the evaluator + machine. To start the machine at its ordinary + + + read-eval-print + + + read-evaluate-print + + + loop, we + could use + + start_eceval + start_eceval + compile_and_go + compile_and_go_example + +(define (start-eceval) + (set! the-global-environment (setup-environment)) + (set-register-contents! eceval 'flag false) + (start eceval)) + + +function start_eceval() { + set_register_contents(eceval, "flag", false); + return start(eceval); +} + + + + + + + (branch (label external-entry)) ; branches if $\texttt{flag}$ is set +read-eval-print-loop + (perform (op initialize-stack)) + $\ldots$ + + +$\texttt{ }\texttt{ }$branch(label("external_entry")), // branches if flag is set +"read_evaluate_print_loop", + perform(list(op("initialize_stack"))), + $\ldots$ + + + + External-entry + The code at external_entry + + + assumes that the machine is started with val + containing the location of an instruction sequence that puts a result into + val and ends with + + (goto (reg continue)). + go_to(reg("continue")). + + + Starting at this entry point jumps to the location designated + by val, but first assigns + continue so that execution will return to + + print-result, + print_result, + + + which prints the value in val and then goes to + the beginning of the evaluators + + + read-eval-print + + + read-evaluate-print + + + loop.Since + a compiled + + procedure + function + + is an object that the system may try to print, we also modify the system + print operation + + user-print + user_print + + (from section) so that it will not + attempt to print the components of a compiled + + procedure: + function: + + + user_printmodified for compiled code + user_print_2 + + (define (user-print object) + (cond ((compound-procedure? object) + (display (list 'compound-procedure + (procedure-parameters object) + (procedure-body object) + '<procedure-env>))) + ((compiled-procedure? object) + (display '<compiled-procedure>)) + (else (display object)))) + + +function user_print(string, object) { + function prepare(object) { + return is_compound_function(object) + ? "< compound function >" + : is_primitive_function(object) + ? "< primitive function >" + : is_compiled_function(object) + ? "< compiled function >" + : is_pair(object) + ? pair(prepare(head(object)), + prepare(tail(object))) + : object; + } + display(string + " " + stringify(prepare(object))); +} + + +function user_print(prompt_string, object) { + function to_string(object) { + return is_compound_function(object) + ? "<compound-function>" + : is_compiled_function(object) + ? "<compiled-function>" + : is_primitive_function(object) + ? "<primitive-function>" + : is_pair(object) + ? "[" + to_string(head(object)) + ", " + + to_string(tail(object)) + "]" + : stringify(object); + } + display(prompt_string + "\n" + to_string(object) + "\n----------------------------"); +} + + + + external_entry + +external-entry + (perform (op initialize-stack)) + (assign env (op get-global-environment)) + (assign continue (label print-result)) + (goto (reg val)) + + +"external_entry", + perform(list(op("initialize_stack"))), + assign("env", list(op("get_current_environment"))), + assign("continue", label("print_result")), + go_to(reg("val")), + + + + + explicit-control evaluator for JavaScriptmodified for compiled code + + + Now we can use the following + + procedure + function + + to compile a + + procedure definition, + function declaration, + + execute the compiled code, and run the + + + read-eval-print + + + read-evaluate-print + + + loop so + we can try the + + procedure. + function. + + Because we want the compiled code to + + return + proceed + + to the location in + continue with its result in + val, we compile the + + expression + program + + with a target of val and a + linkage of + + + return. + + + "return". + + + In order to transform the + object code produced by the compiler into executable instructions + for the evaluator register machine, we use the + + procedure + function + + assemble from the + register-machine simulator + (section). + + + For the interpreted program to refer to the names that + are declared at top level in the compiled program, we + scanning out declarationsin compiler + 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. + + +We then initialize + the val register to point to the list + of instructions, set the + flag so that the evaluator will go to + + external-entry, + external_entry, + + + and start the evaluator. + + display_instructions + +function stringify_list(xs) { + return is_null(member(head(xs), + list("assign", "perform", "op", "label", "branch", + "go_to", "save", "restore", "reg", "constant"))) + ? "list(" + comma_separated(xs) + ")" + : head(xs) + "(" + comma_separated(tail(xs)) + ")"; +} +function comma_separated(elements) { + return accumulate((s, acc) => stringify_instruction(s) + + (acc === "" ? "" : ", " + acc), + "", elements); +} +function stringify_instruction(x) { + return is_string(x) || is_number(x) || is_undefined(x) || is_null(x) + ? stringify(x) + : stringify_list(x); +} +function display_instructions(instructions) { + return for_each(i => {display(stringify_instruction(i) + ","); }, + instructions); +} + + + + + eceval_2 + prompt_for_input + the_global + + +const eceval_operations = + list( + // args + list("arg_expressions" , arg_expressions), + list("function_expression" , function_expression), + list("is_null" + , is_null), + list("head" , head), + list("is_last_argument_expression" + , a => is_null(tail(a))), + list("tail" , tail), + + //arg + list("empty_arglist" , () => null), + list("adjoin_arg" , (val, argl) => append(argl, + list(val))), + + // comp (sequence) + list("first_statement" , first_statement), + list("rest_statements" , rest_statements), + list("is_last_statement" , is_last_statement), + list("sequence_statements" , sequence_statements), + + // eval functions from meta-circular evaluator + list("is_literal" , is_literal), + list("literal_value" , literal_value), + list("is_name" , is_name), + list("symbol_of_name" , symbol_of_name), + list("is_assignment" , is_assignment), + list("assignment_symbol" , assignment_symbol), + list("assignment_value_expression" + , assignment_value_expression), + list("assign_symbol_value" , assign_symbol_value), + list("is_declaration" , is_declaration), + list("declaration_symbol" , declaration_symbol), + list("declaration_value_expression" + , declaration_value_expression), + list("assign_symbol_value" , assign_symbol_value), + list("is_lambda_expression", is_lambda_expression), + list("lambda_parameter_symbols" + , lambda_parameter_symbols), + list("lambda_body" , lambda_body), + list("is_return_statement" , is_return_statement), + list("return_expression" , return_expression), + list("is_conditional" + , is_conditional), + list("conditional_predicate" + , conditional_predicate), + list("conditional_consequent" + , conditional_consequent), + list("conditional_alternative" + , conditional_alternative), + list("is_sequence" , is_sequence), + list("is_block" , is_block), + list("block_body" , block_body), + list("scan_out_declarations" + , scan_out_declarations), + list("list_of_unassigned" , list_of_unassigned), + list("is_application" , is_application), + list("is_primitive_function" + , is_primitive_function), + list("apply_primitive_function" + , apply_primitive_function), + list("is_compound_function", is_compound_function), + list("function_parameters" , function_parameters), + list("function_environment", function_environment), + list("function_body" , function_body), + list("extend_environment" , extend_environment), + list("make_function" , make_function), + + list("get_current_environment" + , get_current_environment), + list("set_current_environment" + , set_current_environment), + + // Unsorted + list("is_function_declaration" , is_function_declaration), + list("function_declaration_body" , function_declaration_body), + list("function_declaration_parameters" , function_declaration_parameters), + list("function_declaration_name" , function_declaration_name), + list("function_decl_to_constant_decl", function_decl_to_constant_decl), + list("declaration_symbol" , declaration_symbol), + list("is_operator_combination", is_operator_combination), + list("operator_combination_to_application", operator_combination_to_application), + + // generic helpers + list("is_truthy", is_truthy), + list("is_falsy" , x => ! is_truthy(x)), + list("is_null", is_null), + list("is_pair" , is_pair), + list("is_number" , is_number), + list("append" , append), + list("pair" , pair), + + list( + "lookup_symbol_value" , lookup_symbol_value), + list("get_current_environment" , get_current_environment), + list("set_current_environment" , set_current_environment), + + list("user_read" , prompt), + list("user_print" , user_print), + list("parse" , parse), + list("display" , display), + list("make_compiled_function" , make_compiled_function), + list("is_compiled_function" , is_compiled_function), + list("compiled_function_env" , compiled_function_env), + list("compiled_function_entry" , compiled_function_entry), + list("list" , list) + + ); + +const eceval_controller = +list( + branch(label("external_entry")), // branches if flag is set + + "read_evaluate_print_loop", + perform(list(op("initialize_stack"))), + assign("comp", list(op("user_read"), constant("EC-evaluate input:"))), + test(list(op("is_null"), reg("comp"))), + branch(label("evaluator_done")), + assign("comp", list(op("parse"), reg("comp"))), + assign("env", list(op("get_current_environment"))), + + assign("val", list(op("scan_out_declarations"), reg("comp"))), + save("comp"), // temporarily store to comp + assign("comp", list(op("list_of_unassigned"), reg("val"))), + assign("env", list(op("extend_environment"), + reg("val"), reg("comp"), reg("env"))), + perform(list(op("set_current_environment"), reg("env"))), + restore("comp"), + assign("continue", label("print_result")), + go_to(label("eval_dispatch")), + + "external_entry", + perform(list(op("initialize_stack"))), + assign("env", list(op("get_current_environment"))), + assign("continue", label("print_result")), + go_to(reg("val")), + + "print_result", + perform(list(op("user_print"), + constant("EC-evaluate value:"), reg("val"))), + go_to(label("read_evaluate_print_loop")), + + "eval_dispatch", + test(list(op("is_literal"), reg("comp"))), + branch(label("ev_literal")), + test(list(op("is_name"), reg("comp"))), + branch(label("ev_name")), + test(list(op("is_operator_combination"), reg("comp"))), + branch(label("ev_operator_combination")), + test(list(op("is_function_declaration"), reg("comp"))), + branch(label("ev_function_declaration")), + test(list(op("is_operator_combination"), reg("comp"))), + branch(label("ev_operator_combination")), + test(list(op("is_declaration"), reg("comp"))), + branch(label("ev_declaration")), + test(list(op("is_assignment"), reg("comp"))), + branch(label("ev_assignment")), + test(list(op("is_return_statement"), reg("comp"))), + branch(label("ev_return")), + test(list(op("is_conditional"), reg("comp"))), + branch(label("ev_conditional")), + test(list(op("is_lambda_expression"), reg("comp"))), + branch(label("ev_lambda")), + test(list(op("is_sequence"), reg("comp"))), + branch(label("ev_sequence_start")), + test(list(op("is_block"), reg("comp"))), + branch(label("ev_block")), + test(list(op("is_application"), reg("comp"))), + branch(label("ev_application")), + go_to(label("unknown_component_type")), + + "ev_return", + revert_stack_to_marker(), + restore("continue"), + assign("comp", list(op("return_expression"), reg("comp"))), + go_to(label("eval_dispatch")), + + "ev_literal", + assign("val", list(op("literal_value"), reg("comp"))), + go_to(reg("continue")), + + "ev_name", + assign("comp", list(op("symbol_of_name"), reg("comp"))), + assign("val", list(op("lookup_symbol_value"), reg("comp"), reg("env"))), + go_to(reg("continue")), + + "ev_lambda", + assign("unev", list(op("lambda_parameter_symbols"), reg("comp"))), + assign("comp", list(op("lambda_body"), reg("comp"))), + assign("val", list(op("make_function"), + reg("unev"), reg("comp"), reg("env"))), + go_to(reg("continue")), + + "ev_operator_combination", + assign("comp", list(op("operator_combination_to_application"), + reg("comp"), reg("env"))), + + "ev_application", + save("continue"), + save("env"), + assign("unev", list(op("arg_expressions"), reg("comp"))), + save("unev"), + assign("comp", list(op("function_expression"), reg("comp"))), + assign("continue", label("ev_appl_did_function_expression")), + go_to(label("eval_dispatch")), + + "ev_appl_did_function_expression", + restore("unev"), // the args + restore("env"), + assign("argl", list(op("empty_arglist"))), + assign("fun", reg("val")), // the function_expression + test(list(op("is_null"), + reg("unev"))), + branch(label("apply_dispatch")), + save("fun"), + + "ev_appl_argument_expression_loop", + save("argl"), + assign("comp", list(op("head"), reg("unev"))), + test(list(op("is_last_argument_expression"), + reg("unev"))), + branch(label("ev_appl_last_arg")), + save("env"), + save("unev"), + assign("continue", label("ev_appl_accumulate_arg")), + go_to(label("eval_dispatch")), + + "ev_appl_accumulate_arg", + restore("unev"), + restore("env"), + restore("argl"), + assign("argl", list(op("adjoin_arg"), + reg("val"), reg("argl"))), + assign("unev", list(op("tail"), reg("unev"))), + go_to(label("ev_appl_argument_expression_loop")), + + "ev_appl_last_arg", + assign("continue", label("ev_appl_accum_last_arg")), + go_to(label("eval_dispatch")), + + "ev_appl_accum_last_arg", + restore("argl"), + assign("argl", list(op("adjoin_arg"), + reg("val"), reg("argl"))), + restore("fun"), + go_to(label("apply_dispatch")), + +"compiled_apply", + push_marker_to_stack(), + assign("val", list(op("compiled_function_entry"), reg("fun"))), + go_to(reg("val")), + + "apply_dispatch", + test(list(op("is_compiled_function"), reg("fun"))), + branch(label("compiled_apply")), + test(list(op("is_primitive_function"), + reg("fun"))), + branch(label("primitive_apply")), + test(list(op("is_compound_function"), + reg("fun"))), + branch(label("compound_apply")), + go_to(label("unknown_function_type")), + + "primitive_apply", + assign("val", list(op("apply_primitive_function"), + reg("fun"), + reg("argl"))), + restore("continue"), + go_to(reg("continue")), + + "compound_apply", + assign("unev", list(op("function_parameters"), reg("fun"))), + assign("env", list(op("function_environment"), reg("fun"))), + assign("env", list(op("extend_environment"), + reg("unev"), reg("argl"), reg("env"))), + assign("comp", list(op("function_body"), reg("fun"))), + push_marker_to_stack(), + assign("continue", label("return_undefined")), + go_to(label("eval_dispatch")), + + "return_undefined", + assign("val", constant(undefined)), + revert_stack_to_marker(), + restore("continue"), + go_to(reg("continue")), + + "ev_block", + assign("comp", list(op("block_body"), reg("comp"))), + assign("val", list(op("scan_out_declarations"), reg("comp"))), + + save("comp"), // temporarily store to comp + assign("comp", list(op("list_of_unassigned"), reg("val"))), + assign("env", list(op("extend_environment"), + reg("val"), + reg("comp"), + reg("env"))), + restore("comp"), + go_to(label("eval_dispatch")), + +"ev_sequence_start", + assign("unev", list(op("sequence_statements"), reg("comp"))), + save("continue"), + +"ev_sequence", + assign("comp", list(op("first_statement"), reg("unev"))), + test(list(op("is_last_statement"), reg("unev"))), + branch(label("ev_sequence_last_statement")), + save("unev"), + save("env"), + assign("continue", label("ev_sequence_continue")), + go_to(label("eval_dispatch")), + +"ev_sequence_continue", + restore("env"), + restore("unev"), + assign("unev", list(op("rest_statements"), reg("unev"))), + go_to(label("ev_sequence")), + +"ev_sequence_last_statement", + restore("continue"), + go_to(label("eval_dispatch")), + + "ev_conditional", + save("comp"), // save expression for later + save("env"), + save("continue"), + assign("continue", label("ev_conditional_decide")), + assign("comp", list(op("conditional_predicate"), reg("comp"))), + go_to(label("eval_dispatch")), // evaluate the predicate + + "ev_conditional_decide", + restore("continue"), + restore("env"), + restore("comp"), + test(list(op("is_falsy"), reg("val"))), + branch(label("ev_conditional_alternative")), + + "ev_conditional_consequent", + assign("comp", list(op("conditional_consequent"), reg("comp"))), + go_to(label("eval_dispatch")), + + "ev_conditional_alternative", + assign("comp", list(op("conditional_alternative"), reg("comp"))), + go_to(label("eval_dispatch")), + + "ev_assignment", + assign("unev", list(op("assignment_symbol"), reg("comp"))), + save("unev"), // save variable for later + assign("comp", list(op("assignment_value_expression"), reg("comp"))), + save("env"), + save("continue"), + assign("continue", label("ev_assignment_1")), + go_to(label("eval_dispatch")), // evaluate assignment value + + "ev_assignment_1", + restore("continue"), + restore("env"), + restore("unev"), + perform(list(op("assign_symbol_value"), + reg("unev"), reg("val"), reg("env"))), + go_to(reg("continue")), + + "ev_function_declaration", + assign("comp", list(op("function_decl_to_constant_decl"), reg("comp"))), + + "ev_declaration", + assign("unev", list(op("declaration_symbol"), + reg("comp"))), + save("unev"), // save variable for later + assign("comp", list(op("declaration_value_expression"), + reg("comp"))), + save("env"), + save("continue"), + assign("continue", label("ev_declaration_assign")), + go_to(label("eval_dispatch")), // evaluate declaration value + + "ev_declaration_assign", + restore("continue"), + restore("env"), + restore("unev"), + perform(list(op("assign_symbol_value"), + reg("unev"), reg("val"), reg("env"))), + assign("val", constant(undefined)), + go_to(reg("continue")), + + // Error handling + "unknown_component_type", + assign("val", constant("Unknown expression type")), + go_to(label("signal_error")), + + "unknown_function_type", + restore("continue"), /// clean up stack (from apply_dispatch) + assign("val", constant("Unknown function type")), + go_to(label("signal_error")), + + "signal_error", + perform(list(op("user_print"), + constant("EC-evaluator error:"), reg("comp"))), + go_to(label("read_evaluate_print_loop")), + + "evaluator_done" + ); + +const eceval = + make_machine(list("comp", "env", "val", "fun", + "argl", "continue", "unev"), + eceval_operations, + eceval_controller); + + + + compile_and_go + compile_and_go + compile_and_go_example + headline_4_1_1 + scan_out_declarations + list_of_unassigned + functions_4_1_2 + functions_4_1_2 + functions_4_1_3 + functions_4_1_4 + make_machine + start + compile + make_instruction_sequence + compile_linkage + end_with_linkage + compile_literal + compile_assignment + make_label + compile_conditional + compile_sequence + make_compiled_function + compile_lambda + compile_lambda_body + compile_return + compile_block + compile_application + construct_arglist + compile_function_call + all_regs + compile_fun_appl + registers_needed + needs_register + append_instruction_sequences + list_union + preserving + tack_on_instruction_sequence + parallel_instruction_sequences + user_print_2 + eceval_2 + display_instructions + +(define (compile-and-go expression) + (let ((instructions + (assemble (statements + (compile expression 'val 'return)) + eceval))) + (set! the-global-environment (setup-environment)) + (set-register-contents! eceval 'val instructions) + (set-register-contents! eceval 'flag true) + (start eceval))) + + + +function compile_and_go(program) { + const instrs = assemble(instructions(compile(program, + "val", "return")), + eceval); + const toplevel_names = scan_out_declarations(program); + const unassigneds = list_of_unassigned(toplevel_names); + set_current_environment(extend_environment( + toplevel_names, + unassigneds, + the_global_environment)); + set_register_contents(eceval, "val", instrs); + set_register_contents(eceval, "flag", true); + return start(eceval); +} + + + + + + If we have set up + compiler for JavaScriptmonitoring performance (stack use) of compiled code + stack monitoring, as at the end of + section, we can examine the + stack usage of compiled code: + + compile_and_go_example_2 + compile_and_go + +(compile-and-go + '(define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n)))) + + +(total-pushes = 0 maximum-depth = 0) +;;; EC-Eval value: +ok + + +compile_and_go(parse(` +function factorial(n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + `)); + + +total pushes = 0 +maximum depth = 0 +EC-evaluate value: +undefined + + + + +;;; EC-Eval input: + + +(factorial 5) + + +(total-pushes = 31 maximum-depth = 14) +;;; EC-Eval value: +120 + + +EC-evaluate input: + + +factorial(5); + + +total pushes = 36 +maximum depth = 14 +EC-evaluate value: +120 + + + Compare + compiler for JavaScriptexplicit-control evaluator vs. + this example with the evaluation of + + (factorial 5) + factorial(5) + + + using the interpreted version of the same + + procedure, + function, + + shown at the end of section. + + + The interpreted version required 144 pushes and a maximum stack depth of 28. + + + The interpreted version required 151 pushes and a maximum stack depth of 28. + + + This illustrates the optimization that results from our compilation strategy. + + + + Interpretation and compilation + + + interpretercompiler vs. + compilerinterpreter vs. + + + With the programs in this section, we can now experiment with the + alternative execution strategies of interpretation and + compilation.We can do even better by extending the compiler + to allow compiled code to call interpreted + + procedures. + functions. + + See exercise. + An interpreter raises the machine to the level of the user program; a + compiler lowers the user program to the level of the machine language. + We can regard the + + + Scheme + + + JavaScript + + + language (or any programming language) as a + coherent family of abstractions erected on the machine language. + Interpreters are good for interactive program development and + debugging because the steps of program execution are organized in + terms of these abstractions, and are therefore more intelligible + to the programmer. + Compiled code can execute faster, because the steps of program execution + are organized in terms of the machine language, and the compiler is free + to make optimizations that cut across the higher-level + abstractions.Independent of the strategy of execution, we + incur significant overhead if we insist that + error handlingin compiled code + errors encountered in + execution of a user program be detected and signaled, rather than being + allowed to kill the system or produce wrong answers. For example, an + out-of-bounds array reference can be detected by checking the validity + of the reference before performing it. The overhead of checking, + however, can be many times the cost of the array reference itself, and + a programmer should weigh speed against safety in determining whether + such a check is desirable. A good compiler should be able to produce + code with such checks, should avoid redundant checks, and should allow + programmers to control the extent and type of error checking in the + compiled code. +

+ Compilers for popular languages, such as + Cerror handling + C and C++, + put hardly any error-checking operations into + running code, so as to make things run as fast as possible. As a + result, it falls to programmers to explicitly provide error checking. + Unfortunately, people often neglect to do this, even in + critical applications where speed is not a constraint. Their programs + lead fast and dangerous lives. For example, the notorious + Internet Worm + Worm + that paralyzed the Internet in 1988 exploited the + UNIX + UNIX$^{\textrm{TM}}$ + operating systems failure to check whether the input buffer has + overflowed in the finger daemon. (See + Spafford, Eugene H. + Spafford 1989.) +
+
+ + + The alternatives of interpretation and compilation also lead to + different strategies for + porting a language + porting languages to new computers. Suppose + that we wish to implement + + Lisp + JavaScript + + for a new machine. One strategy is + to begin with the explicit-control evaluator of + section + and translate its instructions to instructions for the + new machine. A different strategy is to begin with the compiler and + change the code generators so that they generate code for the new + machine. The second strategy allows us to run any + Lisp + JavaScript + + program on the new machine by first compiling it with the compiler running + on our + original LispJavaScript system, and linking it with a compiled version of the runtime + library.Of course, with either the interpretation or the + compilation strategy we must also implement for the new machine storage + allocation, input and output, and all the various operations that we took + as primitive in our discussion of + the evaluator and compiler. One strategy for minimizing work here is + to write as many of these operations as possible in + + Lisp + JavaScript + + and then compile them for the new machine. Ultimately, everything reduces + to a small kernel (such as garbage collection and the mechanism for + applying actual machine primitives) that is hand-coded for the new + machine. Better yet, we can compile the compiler itself, and run + this on the new machine to compile other + LispJavaScript programs. + This strategy leads to amusing tests of correctness of + the compiler, such as checking + whether the compilation of a program on the new machine, using the + compiled compiler, is identical with the + compilation of the program on the original + + Lisp + JavaScript + + system. Tracking down the source of differences is fun but often + frustrating, because the results are extremely sensitive to minuscule + details. Or we can compile one of the interpreters of + section to produce an interpreter that + runs on the new machine. + + + + + By + compiler for JavaScriptmonitoring performance (stack use) of compiled code + factorialstack usage, compiled + comparing the stack operations used by compiled code to the stack + operations used by the evaluator for the same computation, we can + determine the extent to which the compiler optimizes use of the stack, + both in speed (reducing the total number of stack operations) and in + space (reducing the maximum stack depth). Comparing this optimized + stack use to the performance of a special-purpose machine for the same + computation gives some indication of the quality of the compiler. +
    +
  1. + Exercise asked you to determine, as a + function of $n$, the number of pushes and + the maximum stack depth needed by the evaluator to compute + $n!$ using the recursive factorial + + procedure + function + + given above. Exercise asked you + to do the same measurements for the special-purpose factorial machine + shown in figure. Now perform the + same analysis using the compiled factorial + + procedure. + function. + + + \quad + Take the ratio of the number of pushes in the compiled version to the + number of pushes in the interpreted version, and do the same for the + maximum stack depth. Since the number of operations and the stack + depth used to compute $n!$ are linear in + $n$, these ratios should + approach constants as $n$ becomes large. + What are these constants? Similarly, find the ratios of the stack usage + in the special-purpose machine to the usage in the interpreted version. + + + \quad + Compare the ratios for special-purpose versus interpreted code to the + ratios for compiled versus interpreted code. You should find that the + special-purpose machine is much more efficient than the compiled code, since + the hand-tailored controller code should be much better than what is + produced by our rudimentary general-purpose compiler. + +
  2. +
  3. + Can you suggest improvements to the compiler that would help it + generate code that would come closer in performance to the + hand-tailored version? +
  4. +
+
+ + + Carry out an analysis like the one in + exercise to determine the + effectiveness of compiling the tree-recursive + compiler for JavaScriptmonitoring performance (stack use) of compiled code + fibstack usage, compiled + Fibonacci + + procedure + function + + + +(define (fib n) + (if (< n 2) + n + (+ (fib (- n 1)) (fib (- n 2))))) + + +function fib(n) { + return n < 2 ? n : fib(n - 1) + fib(n - 2); +} + + + compared to the effectiveness of using the special-purpose Fibonacci machine + of figure. (For measurement of the + interpreted performance, see exercise.) + For Fibonacci, the time resource used is not linear in + $n$; hence the ratios of stack operations will not + approach a limiting value that is independent of + $n$. + + + + + This section described how to modify the explicit-control evaluator so + that interpreted code can call compiled + + procedures. + functions. + Show how to modify the compiler so that compiled + + procedures + functions + + can call not only primitive + + procedures + functions + + and compiled + + procedures, + functions, + + but interpreted + + procedures + functions + + as well. This requires modifying + + compile-procedure-call + compile_function_call + + + to handle the case of compound (interpreted) + + procedures. + functions. + + Be sure to handle all the same target and + linkage combinations + as in + + + compile-proc-appl. + + compile_fun_appl. + + + To do the actual + + procedure + function + + application, + the code needs to jump to the evaluators + + compound-apply + + compound_apply + + + entry point. This label cannot be directly referenced in object code + (since the assembler requires that all labels referenced by the + code it is assembling be defined there), so we will add a register + called compapp to the evaluator machine to + hold this entry point, and add an instruction to initialize it: + + + (assign compapp (label compound-apply)) + (branch (label external-entry)) ; branches if $\texttt{flag}$ is set +read-eval-print-loop + $\ldots$ + + +$\texttt{ }\texttt{ }$assign("compapp", label("compound_apply")), + branch(label("external_entry")), // branches if flag is set +"read_evaluate_print_loop", + $\ldots$ + + + To test your code, start by + + + defining + + + declaring + + + a + + + procedure + + + function + + + f that calls a + + + procedure + + + function + + + g. Use + + + compile-and-go + + + compile_@and_@go + + + to compile the + + + definition + + + declaration + + + of f + and start the evaluator. Now, typing at the evaluator, + + + define + g and try to call + f. + + + declare + g and try to call + f. + + + + + + + The + + compile-and-go + compile_and_go + + + interface implemented in this section is + awkward, since the compiler can be called only once (when the + evaluator machine is started). Augment the compiler\linebreak[2]interpreter + interface by providing a + compile_and_run + + compile-and-run + compile_and_run + + + primitive that can be called from within the explicit-control evaluator + as follows: + + +;;; EC-Eval input: + + +(compile-and-run + '(define (factorial n) + (if (= n 1) + 1 + (* (factorial (- n 1)) n)))) + + +;;; EC-Eval value: +ok + + +EC-evaluate input: + + +compile_and_run(parse(` +function factorial(n) { + return n === 1 + ? 1 + : factorial(n - 1) * n; +} + `)); + + +EC-evaluate value: +undefined + + + + +;;; EC-Eval input: + + +(factorial 5) + + +;;; EC-Eval value: +120 + + +EC-evaluate input: + + +factorial(5) + + +EC-Eval value: +120 + + + + + + + As an alternative to using the explicit-control evaluators + + + read-eval-print + + + read-evaluate-print + + + loop, design a register machine that performs a + read-compile-execute-print loop. That is, the machine should run a + loop that reads + + + an expression, + + + a program, + + + compiles it, assembles and + executes the resulting code, and prints the result. This is easy to + run in our simulated setup, since we can arrange to call the + + procedures + functions + + compile and + assemble as register-machine + operations. + + + + + Use the compiler to compile the + metacircular evaluator for JavaScriptcompilation of + metacircular evaluator of + section and run this program using the + register-machine simulator. + + + (To compile more than one definition at a time, + you can package the definitions in a + begin.) + + + Because the parser takes a string as input, you will need to + convert the program into a string. The simplest way to do this is + to use the back quotes (`), + as we have done for the example inputs to + compile_and_go and + compile_and_run. + + + The resulting interpreter will run very slowly because of the multiple + levels of interpretation, but getting all the details to work is an + instructive exercise. + + + + + Develop a rudimentary implementation of + + Scheme + JavaScript + + in + CJavaScript interpreter written in + C (or some other low-level language of your choice) by translating the + explicit-control evaluator of section + into C. In order to run this code you will need to also + provide appropriate storage-allocation routines and other runtime + support. + + + + + As a counterpoint to exercise, modify + the compiler so that it compiles + + Scheme procedures + JavaScript functions + + into sequences of + Ccompiling JavaScript into + CJavaScript interpreter written in + metacircular evaluator for JavaScriptcompilation of + C instructions. Compile the metacircular evaluator of + section to produce a + + Scheme + JavaScript + + interpreter written in C. + + + + compiler for JavaScriptinterfacing to evaluator + compiler for JavaScriptrunning compiled code + compiler for JavaScript + +
diff --git a/xml/cn/others/02foreword02.xml b/xml/cn/others/02foreword02.xml new file mode 100644 index 000000000..a376d5fe8 --- /dev/null +++ b/xml/cn/others/02foreword02.xml @@ -0,0 +1,210 @@ + + + Foreword + + + + + I had the pleasure of meeting the amazing Alan Perlis and + talking with him a few times, when I was still a student. He and + I had in common a deep love and respect for two very different + programming languages: Lisp and APL. Following in his footsteps + is a daunting task, even though he blazed an excellent + trail. Still, I would like to reexamine one comment he made in + the original foreword to this book (and, please, I suggest that + you read his foreword, which immediately follows this one, + before you finish this one). Is it really true that it is better + to have 100 functions operate on one data structure than to have + 10 functions operate on 10 data structures? + + + To answer that question carefully, we first need to ask whether + that one data structure is universal: can it + conveniently fulfill the roles of those 10 more specialized data + structures? + + + For that matter, we can also ask: do we really need 100 + functions? Is there a single universal function that can fulfill + the roles of all those other functions? + + + The surprising answer to that last question is + yes; it is only slightly tricky to construct a + function that accepts (1) a data structure that serves as a + description of some other function, and (2) a list of arguments, + and behaves exactly as that other function would when applied to + the given arguments. And it is only slightly tricky to design a + data structure capable of describing any computation + whatsoever. One such data structure (the tagged-list + representation of expressions and statements, paired with + environments that associate names with values) and one such + universal function (apply) + are described in Chapter 4 of this book. So maybe we need only + one function and one data structure. + + + That is true in theory. In practice, we find it convenient to + draw distinctions that help us, as human beings constructing + descriptions of computations, to organize the structure of our + code so that we can better understand them. I believe that + Perlis was making a remark not about computational capability, + but about human abilities and human limitations. + + + One thing the human mind seems to do well is to name things; we + have powerful associative memories. Given a name, we can quickly + recall some associated thing to mind. This is why we typically + find it easier to work with the lambda calculus than the + combinatory calculus; it is much easier for most people to + interpret the Lisp expression + (lambda (x) (lambda (y) (+ x y))) + or the JavaScript expression + x => y => x + y + than the combinatory expression +
+ ((S ((S (K S)) ((S ((S (K S)) ((S (K K)) (K +)))) ((S (K K)) I)))) (K I)) +
+ even though there is a direct structural correspondence, easily + expressed in five lines of Lisp code. +
+ + So while in principle we could get by with just one universal + function, we prefer to modularize our code, to give names to the + various pieces, and to mention the names of function descriptions + rather than constantly feeding the descriptions themselves to the + universal function. + + + + In my 1998 talk Growing a Language, I commented + that a good programmer does not just write programs. A good + programmer builds a working vocabulary. As we design and + define more and more parts of our programs, we give names to + those parts, and the result is that we have a richer language in + + which to write the rest. + + + But we also find it natural to draw distinctions among data + structures, and to give them names. + + + It may be that nested lists are a universal data structure (and + it is worth noting that many modern and widely used data + structures, such as HTML and XML and JSON, are also + parenthetically nested representations, only slightly more + elaborate than Lisps bare parentheses). There are also + many functions, such as finding the length of a list or applying + a function to every element of a list and getting back a list of + the results, that are useful in a wide variety of + situations. And yet, when I am thinking about a specific + computation, I often say to myself, This list of two + things I expect to be a personal name and a surname, but that + list of two things I expect to be the real and imaginary parts + of a complex number, and that other list of two things I will + regard as the numerator and denominator of a fraction. + In other words, I draw distinctionsand it may be useful + to represent those distinctions explicitly in the data + structure, in part to prevent mistakes such as accidentally + treating a complex number as a fraction. (Again, this is a + comment about human abilities and human limitations.) + + + Since the first edition of this book was written, almost four decades + ago, a lot more ways of organizing data have become relatively + standard, in particular the object-oriented approach, and many + languages, including JavaScript, support specialized data structures + such as objects and strings and heaps and maps with a variety of + built-in mechanisms and libraries. But in doing so, many languages + abandoned support for more general, universal notions. Java, for + example, originally did not support first-class functions, and has + incorporated them only relatively recently, greatly increasing its + expressive power. + + + APL, likewise, originally did not support first-class functions, and + moreover its original single data structurearrays of any number of + dimensionswas not so conveniently useful as a universal data + structure because arrays could not contain other arrays as + elements. More recent versions of APL do support anonymous function + values and nested arrays, and these have made APL dramatically more + expressive. (The original design of APL did have two very good things + going for it: a comprehensive set of functions applicable to that one + data structure, and moreover an extremely well chosen set of names for + those functions. Im not talking about the funny symbols and Greek + letters, but the spoken words that APL programmers use when mentioning + them, words like + shape, + reshape, + compress, + expand, and + laminate; + these are names not for the + symbols, but for the functions they represent. Ken Iverson had a real + knack for choosing short, memorable, vivid names for functions on + arrays.) + + + + While JavaScript, like Java, was originally designed with objects and + methods in mind, it also incorporated first-class functions from the + beginning, and it is not difficult to use its objects to define a + universal data structure. As a result, JavaScript is not as distant + from Lisp as you would think, and as this edition of + Structure and Interpretation of Computer Programs + demonstrates, it is a good + alternate framework for presenting the key ideas. + SICP + was never about a programming language; it presents powerful, general + ideas for program organization that ought to be useful in any + language. + + + What do Lisp and JavaScript have in common? The ability to abstract a + computation (code plus some associated data) for later execution as a + function; the ability to embed references to such functions within + data structures; the ability to invoke functions on arguments; the + ability to draw a distinction (conditional execution); a convenient + universal data structure; completely automatic storage management for + that data (which seems like a no-brainer, given everything else, until + you realize that many widely used programming languages dont have + it); a large set of useful functions for operating on that universal + data structure; and standard strategies for using the universal data + structure to represent more specialized data structures. + + + So maybe the truth is somewhere in between the extremes that Perlis so + eloquently posited. Maybe the sweet spot is something more like 40 + functions general enough to operate usefully on a universal data + structure such as lists, but also 10 sets of 6 functions each that are + relevant when we take one of 10 specialized views of that universal + data structure. This is manageable if we give good names to these + functions and specialized views. + + + As you read this book, please pay attention not only to the + programming language constructs and how they are used, but also to the + names given to functions and variables and data + structures. They are not all as short and vivid as the names Iverson + chose for his APL functions, but they have been chosen in a deliberate + and systematic way to enhance your understanding of the overall + program structure. + + + Primitives, means of combination, functional abstraction, naming, and + conventions for using a universal data structure in specialized ways + by drawing distinctions: these are the fundamental building blocks of + a good programming language. From there, imagination and good + engineering judgment based on experience can do the rest. + + + + + Guy L. Steele Jr. + Lexington, Massachusetts, 2021 + + +
+
+
diff --git a/xml/cn/others/02foreword84.xml b/xml/cn/others/02foreword84.xml new file mode 100644 index 000000000..c76c399ce --- /dev/null +++ b/xml/cn/others/02foreword84.xml @@ -0,0 +1,198 @@ + + Foreword to Structure and Interpretation of Computer Programs, 1984 + + +Educators, generals, dieticians, psychologists, and parents program. +Armies, students, and some societies are programmed. An assault on +large problems employs a succession of programs, most of which spring +into existence en route. These programs are rife with issues that +appear to be particular to the problem at hand. To appreciate +programming as an intellectual activity in its own right you must turn +to computer programming; you must read and write computer +programsmany of them. It doesn't matter much what the programs are +about or what applications they serve. What does matter is how well +they perform and how smoothly they fit with other programs in the +creation of still greater programs. The programmer must seek both +perfection of part and adequacy of collection. In this book the use +of program is focused on the creation, execution, and study of +programs written in a dialect of Lisp for execution on a digital +computer. Using Lisp we restrict or limit not what we may program, +but only the notation for our program descriptions. + + +Our traffic with the subject matter of this book involves us with +three foci of phenomena: the human mind, collections of computer +programs, and the computer. Every computer program is a model, +hatched in the mind, of a real or mental process. These processes, +arising from human experience and thought, are huge in number, +intricate in detail, and at any time only partially understood. They +are modeled to our permanent satisfaction rarely by our computer +programs. Thus even though our programs are carefully handcrafted +discrete collections of symbols, mosaics of interlocking functions, +they continually evolve: we change them as our perception of the model +deepens, enlarges, generalizes until the model ultimately attains a +metastable place within still another model with which we struggle. +The source of the exhilaration associated with computer programming is +the continual unfolding within the mind and on the computer of +mechanisms expressed as programs and the explosion of perception they +generate. If art interprets our dreams, the computer executes them in +the guise of programs! + + +For all its power, the computer is a harsh taskmaster. Its programs +must be correct, and what we wish to say must be said accurately in +every detail. As in every other symbolic activity, we become +convinced of program truth through argument. Lisp itself can be +assigned a semantics (another model, by the way), and if a program's +function can be specified, say, in the predicate calculus, the proof +methods of logic can be used to make an acceptable correctness +argument. Unfortunately, as programs get large and complicated, as +they almost always do, the adequacy, consistency, and correctness of +the specifications themselves become open to doubt, so that complete +formal arguments of correctness seldom accompany large programs. +Since large programs grow from small ones, it is crucial that we +develop an arsenal of standard program structures of whose correctness +we have become surewe call them idiomsand learn to combine them +into larger structures using organizational techniques of proven +value. These techniques are treated at length in this book, and +understanding them is essential to participation in the Promethean +enterprise called programming. More than anything else, the +uncovering and mastery of powerful organizational techniques +accelerates our ability to create large, significant programs. +Conversely, since writing large programs is very taxing, we are +stimulated to invent new methods of reducing the mass of function and +detail to be fitted into large programs. + + +Unlike programs, computers must obey the laws of physics. If they +wish to perform rapidlya few nanoseconds per state changethey +must transmit electrons only small distances (at most +$1\frac{1}{2}$ +feet). The heat generated by the huge number of devices so +concentrated in space has to be removed. An exquisite engineering art +has been developed balancing between multiplicity of function and +density of devices. In any event, hardware always operates at a level +more primitive than that at which we care to program. The processes +that transform our Lisp programs to machine programs are +themselves abstract models which we program. Their study and creation +give a great deal of insight into the organizational programs +associated with programming arbitrary models. Of course the computer +itself can be so modeled. Think of it: the behavior of the smallest +physical switching element is modeled by quantum mechanics described +by differential equations whose detailed behavior is captured by +numerical approximations represented in computer programs executing on +computers composed of ! + + +It is not merely a matter of tactical convenience to separately +identify the three foci. Even though, as they say, it's all in the +head, this logical separation induces an acceleration of symbolic +traffic between these foci whose richness, vitality, and potential is +exceeded in human experience only by the evolution of life itself. At +best, relationships between the foci are metastable. The computers +are never large enough or fast enough. Each breakthrough in hardware +technology leads to more massive programming enterprises, new +organizational principles, and an enrichment of abstract models. +Every reader should ask himself periodically Toward what end, toward +what end?but do not ask it too often lest you pass up the fun of +programming for the constipation of bittersweet philosophy. + + +Among the programs we write, some (but never enough) perform a precise +mathematical function such as sorting or finding the maximum of a +sequence of numbers, determining primality, or finding the square +root. We call such programs algorithms, and a great deal is known of +their optimal behavior, particularly with respect to the two important +parameters of execution time and data storage requirements. A +programmer should acquire good algorithms and idioms. Even though +some programs resist precise specifications, it is the responsibility +of the programmer to estimate, and always to attempt to improve, their +performance. + + + +Lisp is a survivor, having been in use for about a quarter of a +century. Among the active programming languages only Fortran has had +a longer life. Both languages have supported the programming needs of +important areas of application, Fortran for scientific and engineering +computation and Lisp for artificial intelligence. These two areas +continue to be important, and their programmers are so devoted to +these two languages that Lisp and Fortran may well continue in active +use for at least another quarter-century. + + +Lisp changes. The Scheme dialect used in this text has evolved from +the original Lisp and differs from the latter in several important +ways, including static scoping for variable binding and permitting +functions to yield functions as values. In its semantic structure +Scheme is as closely akin to Algol 60 as to early Lisps. Algol 60, +never to be an active language again, lives on in the genes of Scheme +and Pascal. It would be difficult to find two languages that are the +communicating coin of two more different cultures than those gathered +around these two languages. Pascal is for building +pyramidsimposing, breathtaking, static structures built by armies +pushing heavy blocks into place. Lisp is for building +organismsimposing, breathtaking, dynamic structures built by squads +fitting fluctuating myriads of simpler organisms into place. The +organizing principles used are the same in both cases, except for one +extraordinarily important difference: The discretionary exportable +functionality entrusted to the individual Lisp programmer is more than +an order of magnitude greater than that to be found within Pascal +enterprises. Lisp programs inflate libraries with functions whose +utility transcends the application that produced them. The list, +Lisp's native data structure, is largely responsible for such growth +of utility. The simple structure and natural applicability of lists +are reflected in functions that are amazingly nonidiosyncratic. In +Pascal the plethora of declarable data structures induces a +specialization within functions that inhibits and penalizes casual +cooperation. It is better to have 100 functions operate on one data +structure than to have 10 functions operate on 10 data structures. As +a result the pyramid must stand unchanged for a millennium; the +organism must evolve or perish. + + + +To illustrate this difference, compare the treatment of material and +exercises within this book with that in any first-course text using +Pascal. Do not labor under the illusion that this is a text +digestible at MIT only, peculiar to the breed found there. It is +precisely what a serious book on programming Lisp must be, no matter +who the student is or where it is used. + + +Note that this is a text about programming, unlike most Lisp books, +which are used as a preparation for work in artificial intelligence. +After all, the critical programming concerns of software engineering +and artificial intelligence tend to coalesce as the systems under +investigation become larger. This explains why there is such growing +interest in Lisp outside of artificial intelligence. + + +As one would expect from its goals, artificial intelligence research +generates many significant programming problems. In other +programming cultures this spate of problems spawns new languages. +Indeed, in any very large programming task a useful organizing +principle is to control and isolate traffic within the task modules +via the invention of language. These languages tend to become less +primitive as one approaches the boundaries of the system where we +humans interact most often. As a result, such systems contain complex +language-processing functions replicated many times. Lisp has such a +simple syntax and semantics that parsing can be treated as an +elementary task. Thus parsing technology plays almost no role in Lisp +programs, and the construction of language processors is rarely an +impediment to the rate of growth and change of large Lisp systems. +Finally, it is this very simplicity of syntax and semantics that is +responsible for the burden and freedom borne by all Lisp programmers. +No Lisp program of any size beyond a few lines can be written without +being saturated with discretionary functions. Invent and fit; have +fits and reinvent! We toast the Lisp programmer who pens his thoughts +within nests of parentheses. + + + + Alan J. Perlis + New Haven, Connecticut + + + + diff --git a/xml/cn/others/03prefaces03.xml b/xml/cn/others/03prefaces03.xml new file mode 100644 index 000000000..33c4ce66f --- /dev/null +++ b/xml/cn/others/03prefaces03.xml @@ -0,0 +1,132 @@ + + + + Preface + + + + + + + The book Structure and Interpretation of Computer + Programs + SICP + (SICP) introduces the reader to central ideas of + computation by establishing a series of mental models for computation. + Chapters cover programming concepts that are common to all + modern high-level programming languages. The original first two editions + of SICP use the programming language Scheme in their program examples, + whose minimalist, expression-oriented syntax allows the book to focus + on the underlying ideas rather than the design of the chosen language. + Chapters and use Scheme to formulate language + processors for Scheme, deepening the readers understanding of the + mental models and exploring language extensions and alternatives. + + + + Since its publication in 1984 and its second edition in 1996, SICP has been + adopted as a textbook by universities and colleges around the world, + including the National University of Singapore (NUS), which + introduced the SICP-based introductory course CS1101S in 1997. + In the mid-1990s, the languages Python, JavaScript, + and Ruby emerged, which share central design elements with Scheme, + but which employ a more complex, statement-oriented syntax that uses + familiar algebraic (infix) notation. Their rise in popularity led + instructors to adapt their SICP-based courses, typically by + translating the example programs to their language of choice, by + adding material specific to that language, and by omitting the + material of chapters and. + + + Adapting SICP to JavaScript + + + The work on adapting the second edition of SICP to JavaScript + SICP JS + (SICP JS) + started at NUS in 2008, and CS1101S switched to JavaScript in 2012. The language + standard ECMAScript 2015 introduced lambda expressions, tail recursion, + and block-scoped variables and constants, which enabled the adaptation + to become quite close to the original. We made substantial changes to + SICP only when we felt they were forced by differences + between JavaScript and Scheme. The book covers just a small + fraction of JavaScript, so a reader would be ill-advised to use it + to learn the language. For example, the notion of a JavaScript + objectconsidered one of its fundamental ingredients by any + measureis not even mentioned! + + + + It was straightforward to translate the programs of chapters to JavaScript by adding libraries that mirror Scheme + primitivesincluding support for list structureand adapting the + text accordingly. However, the switch to JavaScript forced us to make + subtle changes in the interpreters and compiler of + chapters and in order to handle return statements. + Scheme's expression-oriented syntax doesn't have return statements, + which are a prominent feature of statement-oriented languages. + + + + By using JavaScript, chapters introduce the reader + to the syntactic style of most mainstream languages today. + However, that same syntactic style + gave rise to significant changes in chapter, because + the direct representation of programs as data structures could + no longer be taken for granted. This provided us with an + opportunity to introduce the reader to the notion of program parsing + in section, + an important component of programming-language processors. + In section, the rigid syntactic structure of JavaScript + complicated the implementation of the presented logic programming system + and exposed the limitations of JavaScript as a tool for programming + language design. + + +Resources for using SICP JS + + \thispagestyle{Preface} + + + + The + MIT + Press web page for SICP JS + links to support for users of this book. This provides all programs from the book + and extensive instructor resources, including + a large collection of additional exercises and + recommendations on selecting a subset of SICP JS that can be covered in a + typical college semester. The JavaScript programs in the book run in the + recommended strict mode in any JavaScript interpreter that complies with + the ECMAScript 2020 specification of JavaScript (ECMA 2020). + The MIT Press web page includes the JavaScript package + sicp, which provides all JavaScript functions + that are considered primitive in the book. + + + To the reader + + + We sincerely hope that if this book is your first + encounter with programming you will use your newly gained understanding + of the structure and interpretation of computer programs to learn more + programming languages, including Scheme and the full JavaScript + language. If you have learned JavaScript prior to picking up SICP + JS, you might gain new insights into the fundamental concepts that underlie + the language and discover how much can be achieved with so little. + If you come to SICP JS with a knowledge of the original SICP, + you might enjoy seeing familiar ideas presented in a new + formatand might appreciate the online comparison edition, + available at the book's web page, where SICP JS and SICP can be viewed + side by side. + + + + + Martin Henz and Tobias Wrigstad + + + + + + + diff --git a/xml/cn/others/03prefaces96.xml b/xml/cn/others/03prefaces96.xml new file mode 100644 index 000000000..6a3d91374 --- /dev/null +++ b/xml/cn/others/03prefaces96.xml @@ -0,0 +1,218 @@ + + + Prefaces + + + to Structure and Interpretation of Computer Programs, 1996\,\&\, & 1984 + + + + +
+ + Preface to the Second Edition of SICP, 1996 + + + Is it possible that software is not like anything else, that it + is meant to be discarded: that the whole point is to + always see it as a soap bubble? + + Perlis, Alan J. + Alan J. Perlis + + + +\noindent +The material in this book has been the basis of MIT's entry-level +computer science subject since 1980. We had been teaching this +material for four years when the first edition was published, and +twelve more years have elapsed until the appearance of this second +edition. We are pleased that our work has been widely adopted and +incorporated into other texts. We have seen our students take the +ideas and programs in this book and build them in as the core of new +computer systems and languages. In literal realization of an ancient +Talmudic pun, our students have become our builders. We are lucky to +have such capable students and such accomplished builders. + + +In preparing this edition, we have incorporated hundreds of +clarifications suggested by our own teaching experience and the +comments of colleagues at MIT and elsewhere. We have redesigned +most of the major programming systems in the book, including +the generic-arithmetic system, the interpreters, the register-machine +simulator, and the compiler; and we have rewritten all the program +examples to ensure that any Scheme implementation conforming to +the IEEE Scheme standard (IEEE 1990) will be able to run the code. + + +This edition emphasizes several new themes. The most important +of these is the central role played by different approaches to +dealing with time in computational models: objects with state, +concurrent programming, functional programming, lazy evaluation, +and nondeterministic programming. We have included new sections on +concurrency and nondeterminism, and we have tried to integrate this +theme throughout the book. + + +The first edition of the book closely followed the syllabus of our MIT +one-semester subject. With all the new material in the second +edition, it will not be possible to cover everything in a single +semester, so the instructor will have to pick and choose. In our own +teaching, we sometimes skip the section on logic programming +(section), +we have students use the +register-machine simulator but we do not cover its implementation +(section), +and we give only a cursory overview of +the compiler +(section). +Even so, this is still +an intense course. Some instructors may wish to cover only the first +three or four chapters, leaving the other material for subsequent +courses. + + +The World Wide Web site of MIT Press +provides support for users of this book. +This includes programs from the book, +sample programming assignments, supplementary materials, +and downloadable implementations of the Scheme dialect of Lisp. + + + + + Harold Abelson and Gerald Jay Sussman + + +
+ +
+ Preface to the First Edition of SICP, 1984 +
+ + + A computer is like a violin. You can imagine a novice trying first a + phonograph and then a violin. The latter, he says, sounds terrible. + That is the argument we have heard from our humanists and most of our + computer scientists. Computer programs are good, they say, for + particular purposes, but they aren't flexible. Neither is a violin, + or a typewriter, until you learn how to use it. + + Marvin Minsky + Minsky, Marvin Lee + <QUOTE>Why Programming Is a Good + Medium for Expressing Poorly-Understood and Sloppily-Formulated + Ideas</QUOTE> + + + \noindent + The Structure and Interpretation of Computer Programs is the + entry-level subject in computer science at the Massachusetts Institute + of Technology. It is required of all students at MIT who major + in electrical engineering or in computer science, as one-fourth of the + common core curriculum, which also includes two subjects on + circuits and linear systems and a subject on the design of digital + systems. We have been involved in the development of this subject + since 1978, and we have taught this material in its present form since + the fall of 1980 to between 600 and 700 students each year. Most of + these students have had little or no prior formal training in + computation, although many have played with computers a bit and a few + have had extensive programming or hardware-design experience. + + + Our design of this introductory computer-science subject reflects two + major concerns. First, we want to establish the idea that a computer + language is not just a way of getting a computer to perform operations + but rather that it is a novel formal medium for expressing ideas about + methodology. Thus, programs must be written for people to read, and + only incidentally for machines to execute. Second, we believe that + the essential material to be addressed by a subject at this level is + not the syntax of particular programming-language constructs, nor + clever algorithms for computing particular functions efficiently, nor + even the mathematical analysis of algorithms and the foundations of + computing, but rather the techniques used to control the intellectual + complexity of large software systems. + + + Our goal is that students who complete this subject should have a good + feel for the elements of style and the aesthetics of programming. + They should have command of the major techniques for controlling + complexity in a large system. They should be capable of reading a + 50-page-long program, if it is written in an exemplary style. They + should know what not to read, and what they need not understand at any + moment. They should feel secure about modifying a program, retaining + the spirit and style of the original author. + + + These skills are by no means unique to computer programming. The + techniques we teach and draw upon are common to all of engineering + design. We control complexity by building abstractions that hide + details when appropriate. We control complexity by establishing + conventional interfaces that enable us to construct systems by + combining standard, well-understood pieces in a mix and match way. + We control complexity by establishing new languages for describing a + design, each of which emphasizes particular aspects of the design and + deemphasizes others. + + + Underlying our approach to this subject is our conviction that + computer science is not a science and that its significance has + little to do with computers. The computer revolution is a revolution + in the way we think and in the way we express what we think. The + essence of this change is the emergence of what might best be called + procedural epistemologythe study of the structure of + knowledge from an imperative point of view, as opposed to the more + declarative point of view taken by classical mathematical subjects. + Mathematics provides a framework for dealing precisely with notions of + what is. Computation provides a framework for dealing precisely + with notions of how to. + + + In teaching our material we use a dialect of the programming language + Lisp. We never formally teach the language, because we don't have to. + We just use it, and students pick it up in a few days. This is one + great advantage of Lisp-like languages: They have very few ways of + forming compound expressions, and almost no syntactic structure. All + of the formal properties can be covered in an hour, like the rules of + chess. After a short time we forget about syntactic details of the + language (because there are none) and get on with the real + issuesfiguring out what we want to compute, how we will decompose + problems into manageable parts, and how we will work on the parts. + Another advantage of Lisp is that it supports (but does not enforce) + more of the large-scale strategies for modular decomposition of + programs than any other language we know. We can make procedural and + data abstractions, we can use higher-order functions to capture common + patterns of usage, we can model local state using assignment and data + mutation, we can link parts of a program with streams and delayed + evaluation, and we can easily implement embedded languages. All of + this is embedded in an interactive environment with excellent support + for incremental program design, construction, testing, and debugging. + We thank all the generations of Lisp wizards, starting with John + McCarthy, who have fashioned a fine tool of unprecedented power and + elegance. + + + Scheme, the dialect of Lisp that we use, is an attempt to bring + together the power and elegance of Lisp and Algol. From Lisp we take + the metalinguistic power that derives from the simple syntax, the + uniform representation of programs as data objects, and the + garbage-collected heap-allocated data. From Algol we take lexical + scoping and block structure, which are gifts from the pioneers of + programming-language design who were on the Algol committee. We wish + to cite John Reynolds and Peter Landin for their insights into the + relationship of Church's lambda calculus to the structure of + programming languages. We also recognize our debt to the + mathematicians who scouted out this territory decades before computers + appeared on the scene. These pioneers include Alonzo Church, Barkley + Rosser, Stephen Kleene, and Haskell Curry. + + + + + Harold Abelson and Gerald Jay Sussman + + + + +
+ diff --git a/xml/cn/others/04acknowledgements04.xml b/xml/cn/others/04acknowledgements04.xml new file mode 100644 index 000000000..050810e46 --- /dev/null +++ b/xml/cn/others/04acknowledgements04.xml @@ -0,0 +1,220 @@ + + Acknowledgments + + + + + The JavaScript adaptation of Structure and Interpretation of Computer + Programs (SICP JS) was developed at the National + University of Singapore (NUS) for the course CS1101S. The course + was co-taught for six years and counting by Low Kok Lim, whose + sound pedagogical judgment was crucial for the success of + the course and this project. The CS1101S teaching team has included many + NUS colleagues and more than 300 + undergraduate student assistants. Their continuous feedback over + the past nine years allowed us to resolve countless JavaScript-specific + issues and remove unnecessary complications and yet retain the + essential features of both SICP and JavaScript. + + + SICP JS is a software project in addition to a book project. We obtained + the book sources from the original authors in 2008. + An early SICP JS tool chain + was developed by Liu Hang and refined by Feng Piaopiao. Chan Ger + Hean developed the first tools for the print edition, based on which Jolyn + Tan developed the tools for the first e-book edition and He Xinyue and + Wang Qian repurposed these tools for the current comparison edition. + Samuel Fang designed and developed the online edition of SICP JS. + + + The online edition of SICP JS and CS1101S rely heavily on a software system + called Source Academy, and the JavaScript sublanguages it + supports are called Source. Many dozens of students have + contributed to the Source Academy during the preparation of SICP JS, and + the system lists them prominently as Contributors. + Since 2020, the students of the NUS course CS4215, Programming + Language Implementation, contributed several programming language + implementations that are used in SICP JS: The concurrent version + of Source used in section 3.4 was developed by Zhengqun Koo and + Jonathan Chan; the lazy implementation used in section 4.2 was developed + by Jellouli Ahmed, Ian Kendall Duncan, Cruz Jomari Evangelista, and + Alden Tan; the nondeterministic implementation used in section + 4.3 was developed by Arsalan Cheema and Anubhav; and Daryl Tan + helped integrate these implementations into the Academy. + + + We are grateful to STINT, The Swedish Foundation for + International Cooperation in Research and Higher Education, + whose sabbatical program connected Martin and Tobias and allowed + Tobias to work as a co-teacher of CS1101S and join the SICP JS + project. + + + We would like to acknowledge the courageous work of the + committee of ECMAScript 2015, led by Allen Wirfs-Brock. SICP JS + relies heavily on constant and let declarations and lambda expressions, + all of which were added to JavaScript with ECMAScript 2015. Those + additions allowed us to stay close to the original in the presentation + of the most important ideas of SICP. Guy Lewis Steele + Jr., who led the first ECMAScript standardization, provided + detailed and useful feedback on some exercises of chapter. + + + + Martin Henz and Tobias Wrigstad + + + +
+ Acknowledgments from the Second Edition of SICP, 1996 +
+
+
+ + +We would like to thank the many people who have helped us develop this +book and this curriculum. + + +Our subject is a clear intellectual descendant of 6.231, a +wonderful subject on programming linguistics and the lambda calculus +taught at MIT in the late 1960s by Jack Wozencraft and Arthur Evans, +Jr. + + +We owe a great debt to Robert Fano, who reorganized MIT's introductory +curriculum in electrical engineering and computer science to emphasize +the principles of engineering design. He led us in starting out on +this enterprise and wrote the first set of subject notes from which +this book evolved. + + +Much of the style and aesthetics of programming that we try to teach +were developed in conjunction with Guy Lewis Steele Jr., who +collaborated with Gerald Jay Sussman in the initial development of the +Scheme language. In addition, David Turner, Peter Henderson, Dan +Friedman, David Wise, and Will Clinger have taught us many of the +techniques of the functional programming community that appear in this +book. + + +Joel Moses taught us about structuring large systems. His experience +with the Macsyma system for symbolic computation provided the insight +that one should avoid complexities of control and concentrate on +organizing the data to reflect the real structure of the world being +modeled. + + +Marvin Minsky and Seymour Papert formed many of our attitudes about +programming and its place in our intellectual lives. To them we owe +the understanding that computation provides a means of expression for +exploring ideas that would otherwise be too complex to deal with +precisely. They emphasize that a student's ability to write and +modify programs provides a powerful medium in which exploring becomes +a natural activity. + + +We also strongly agree with Alan Perlis that programming is lots of +fun and we had better be careful to support the joy of programming. +Part of this joy derives from observing great masters at work. We are +fortunate to have been apprentice programmers at the feet of Bill +Gosper and Richard Greenblatt. + + +It is difficult to identify all the people who have contributed to the +development of our curriculum. We thank all the lecturers, recitation +instructors, and tutors who have worked with us over the past fifteen +years and put in many extra hours on our subject, especially Bill +Siebert, Albert Meyer, Joe Stoy, Randy Davis, Louis Braida, Eric +Grimson, Rod Brooks, Lynn Stein, and Peter Szolovits. +We would like to specially acknowledge the outstanding teaching +contributions of Franklyn Turbak, now at Wellesley; his work +in undergraduate instruction set a standard that we can +all aspire to. +We are grateful to Jerry Saltzer and Jim Miller for +helping us grapple with the mysteries of concurrency, and to +Peter Szolovits and David McAllester for their contributions +to the exposition of nondeterministic evaluation in chapter. + + +Many people have put in significant effort presenting this material at +other universities. Some of the people we have worked closely with +are Jacob Katzenelson at the Technion, Hardy Mayer at the University +of California at Irvine, Joe Stoy at Oxford, Elisha Sacks at Purdue, +and Jan Komorowski at the Norwegian University of Science and +Technology. We are exceptionally proud of our colleagues who have +received major teaching awards for their adaptations of this subject +at other universities, including Kenneth Yip at Yale, Brian Harvey at +the University of California at Berkeley, and Dan Huttenlocher at +Cornell. + + +Al Moy arranged for us to teach this material to engineers at +Hewlett-Packard, and for the production of videotapes of these +lectures. +We would like to thank the talented instructorsin +particular Jim Miller, Bill Siebert, and Mike Eisenbergwho have +designed continuing education courses incorporating these tapes and +taught them at universities and industry all over the world. + + +Many educators in other countries have put in significant +work translating the first edition. +Michel Briand, Pierre Chamard, and Andr Pic produced a French edition; +Susanne Daniels-Herold produced a German +edition; and Fumio Motoyoshi produced a Japanese edition. +We do not know who produced the Chinese edition, +but we consider it an honor to have been selected as the +subject of an unauthorized translation. + + +It is hard to enumerate all the people who have made technical +contributions to the development of the Scheme systems we use for +instructional purposes. In addition to Guy Steele, principal wizards +have included Chris Hanson, Joe Bowbeer, Jim Miller, Guillermo Rozas, +and Stephen Adams. Others who have put in significant time are +Richard Stallman, Alan Bawden, Kent Pitman, Jon Taft, Neil Mayle, John +Lamping, Gwyn Osnos, Tracy Larrabee, George Carrette, Soma +Chaudhuri, Bill Chiarchiaro, Steven Kirsch, Leigh Klotz, Wayne Noss, +Todd Cass, Patrick O'Donnell, Kevin Theobald, Daniel Weise, Kenneth +Sinclair, Anthony Courtemanche, Henry M. Wu, Andrew Berlin, and Ruth +Shyu. + + +Beyond the MIT implementation, we would like to thank the many people +who worked on the IEEE Scheme standard, including William Clinger and +Jonathan Rees, who edited the R$^4$RS, +and Chris Haynes, David Bartley, Chris Hanson, and Jim Miller, +who prepared the IEEE standard. + + +Dan Friedman has been a long-time leader of the Scheme community. +The community's broader work goes beyond issues of language design to +encompass significant educational innovations, such as the high-school +curriculum based on EdScheme by Schemer's Inc., and the wonderful +books by Mike Eisenberg and by Brian Harvey and Matthew Wright. + + +We appreciate the work of those who contributed to making this a real +book, especially Terry Ehling, Larry Cohen, and Paul Bethge at the MIT +Press. Ella Mazel found the wonderful cover image. For the second +edition we are particularly grateful to Bernard and Ella Mazel for +help with the book design, and to David Jones, wizard +extraordinaire. We also are indebted to those readers who made +penetrating comments on the new draft: Jacob Katzenelson, Hardy +Mayer, Jim Miller, and especially Brian Harvey, who did unto this book +as Julie did unto his book Simply Scheme. + + +Finally, we would like to acknowledge the support of the organizations +that have encouraged this work over the years, including support from +Hewlett-Packard, made possible by Ira Goldstein and Joel Birnbaum, and +support from DARPA, made possible by Bob Kahn. + + + + Harold Abelson and Gerald Jay Sussman + + + +
diff --git a/xml/cn/others/06see06.xml b/xml/cn/others/06see06.xml new file mode 100644 index 000000000..e8af13713 --- /dev/null +++ b/xml/cn/others/06see06.xml @@ -0,0 +1,157 @@ +0a22;semicolon +<INDEX><ORDER>0a1</ORDER><JAVASCRIPTINLINE>=></JAVASCRIPTINLINE><SEEALSO>lambda expression</SEEALSO><FRAGILE/></INDEX> +<INDEX><JAVASCRIPTINLINE>?</JAVASCRIPTINLINE><SPACE/><JAVASCRIPTINLINE>:</JAVASCRIPTINLINE><ORDER>;3</ORDER><SEEALSO>conditional expression</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>0p</ORDER>$\pi$<SEE>pi</SEE><FRAGILE/></INDEX> +<INDEX><ORDER>=</ORDER><JAVASCRIPTINLINE>=</JAVASCRIPTINLINE><SEEALSO>assignment</SEEALSO><FRAGILE/></INDEX> +<INDEX>abstract data<SEEALSO>data abstraction</SEEALSO><FRAGILE/></INDEX> +<INDEX>abstraction<SEEALSO>data abstraction; higher-order functions; means of abstraction</SEEALSO><FRAGILE/></INDEX> +<INDEX>agenda<SEE>digital-circuit simulation</SEE><FRAGILE/></INDEX> +<INDEX>algebra, symbolic<SEE>symbolic algebra</SEE><FRAGILE/></INDEX> +<INDEX><ORDER>amb eval</ORDER><JAVASCRIPTINLINE>amb</JAVASCRIPTINLINE> evaluator<SEE>nondeterministic evaluator</SEE><FRAGILE/></INDEX> +<INDEX>argument passing<SEE>call-by-name argument passing; call-by-need argument passing</SEE><FRAGILE/></INDEX> +<INDEX>arithmetic<SUBINDEX>generic</SUBINDEX><SEEALSO>generic arithmetic operations</SEEALSO><FRAGILE/></INDEX> +<INDEX>arithmetic<SUBINDEX>on polynomials</SUBINDEX><SEE>polynomial arithmetic</SEE><FRAGILE/></INDEX> +<INDEX>array<SEE>vector (data structure)</SEE><FRAGILE/></INDEX> +<INDEX>arrow function<SEE>lambda expression</SEE><FRAGILE/></INDEX> +<INDEX>automatic search<SEEALSO>search</SEEALSO><FRAGILE/></INDEX> +<INDEX>backtracking<SEEALSO>nondeterministic computing</SEEALSO><FRAGILE/></INDEX> +<INDEX>balanced binary tree<SEEALSO>binary tree</SEEALSO><FRAGILE/></INDEX> +<INDEX>binary numbers, addition of<SEE>adder</SEE><FRAGILE/></INDEX> +<INDEX><ORDER>break</ORDER><JAVASCRIPTINLINE>break</JAVASCRIPTINLINE> (keyword)<SEEALSO>while loop</SEEALSO><FRAGILE/></INDEX> +<INDEX>case analysis<SEEALSO>conditional expression</SEEALSO><FRAGILE/></INDEX> +<INDEX>changing money<SEE>counting change</SEE><FRAGILE/></INDEX> +<INDEX>circuit<SUBINDEX>digital</SUBINDEX><SEE>digital-circuit simulation</SEE><FRAGILE/></INDEX> +<INDEX>code<SUBINDEX>Huffman</SUBINDEX><SEE>Huffman code</SEE><FRAGILE/></INDEX> +<INDEX>combination<SEEALSO>function application; operator combination</SEEALSO><FRAGILE/></INDEX> +<INDEX>combination, means of<SEEALSO>closure</SEEALSO><FRAGILE/></INDEX> +<INDEX>compound expression<SEEALSO>function application; operator combination; syntactic form</SEEALSO><FRAGILE/></INDEX> +<INDEX>compilation<SEE>compiler</SEE><FRAGILE/></INDEX> +<INDEX>compiler for JavaScript<SUBINDEX>code generators</SUBINDEX><SEE><JAVASCRIPTINLINE>compile_</JAVASCRIPTINLINE><LATEXINLINE>$\ldots$</LATEXINLINE></SEE><FRAGILE/></INDEX> +<INDEX>compiler for JavaScript<SEEALSO>code generator; compile-time environment; instruction sequence; linkage descriptor; target register</SEEALSO><FRAGILE/></INDEX> +<INDEX>compound function<SEEALSO>function</SEEALSO><FRAGILE/></INDEX> +<INDEX>computational process<SEEALSO>process</SEEALSO><FRAGILE/></INDEX> +<INDEX>conditional expression<SEEALSO>case analysis</SEEALSO><FRAGILE/></INDEX> +<INDEX>conjunction<SEEALSO><USE>&&</USE> (logical conjunction)</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>const</ORDER><JAVASCRIPTINLINE>const</JAVASCRIPTINLINE> (keyword)<SEEALSO>constant declaration</SEEALSO><FRAGILE/></INDEX> +<INDEX>continuation<SUBINDEX>in nondeterministic evaluator</SUBINDEX><SEEALSO>failure continuation; success continuation</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>continue</ORDER><LATEXINLINE>\textbf{\texttt{continue}}</LATEXINLINE> (keyword)<SEEALSO>while loop</SEEALSO><FRAGILE/></INDEX> +<INDEX>controller for register machine<SEEALSO>register-machine language</SEEALSO><FRAGILE/></INDEX> +<INDEX>data<SUBINDEX>abstract</SUBINDEX><SEEALSO>data abstraction</SEEALSO><FRAGILE/></INDEX> +<INDEX>data<SUBINDEX>mutable</SUBINDEX><SEE>mutable data objects</SEE><FRAGILE/></INDEX> +<INDEX>data abstraction<SEEALSO>metacircular evaluator</SEEALSO><FRAGILE/></INDEX> +<INDEX>declaration<SEEALSO>internal declaration</SEEALSO><FRAGILE/></INDEX> +<INDEX>differential equation<SEEALSO><JAVASCRIPTINLINE>solve</JAVASCRIPTINLINE></SEEALSO><FRAGILE/></INDEX> +<INDEX>disjunction<SEEALSO>{\tt "|"|} (logical disjunction)</SEEALSO><FRAGILE/></INDEX> +<INDEX>dispatching<SUBINDEX>on type</SUBINDEX><SEEALSO>data-directed programming</SEEALSO><FRAGILE/></INDEX> +<INDEX>efficiency<SEEALSO>order of growth</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>else</ORDER><JAVASCRIPTINLINE>else</JAVASCRIPTINLINE> (keyword)<SEEALSO>conditional statement</SEEALSO><FRAGILE/></INDEX> +<INDEX>empty list<SEEALSO><ORDER>null</ORDER><JAVASCRIPTINLINE>null</JAVASCRIPTINLINE></SEEALSO><FRAGILE/></INDEX> +<INDEX>environment<SUBINDEX>compile-time</SUBINDEX><SEE>compile-time environment</SEE><FRAGILE/></INDEX> +<INDEX>environment<SUBINDEX>global</SUBINDEX><SEE>global environment</SEE><FRAGILE/></INDEX> +<INDEX>environment<SUBINDEX>program</SUBINDEX><SEE>program environment</SEE><FRAGILE/></INDEX> +<INDEX>equation, solving<SEE>half-interval method; Newton's method; <JAVASCRIPTINLINE>solve</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>Euclid's Algorithm<SEEALSO>greatest common divisor</SEEALSO><FRAGILE/></INDEX> +<INDEX>evaluation<SUBINDEX>applicative-order</SUBINDEX><SEE>applicative-order evaluation</SEE><FRAGILE/></INDEX> +<INDEX>evaluation<SUBINDEX>normal-order</SUBINDEX><SEE>normal-order evaluation</SEE><FRAGILE/></INDEX> +<INDEX>evaluation<SUBINDEX>order of subexpression evaluation</SUBINDEX><SEE>order of evaluation</SEE><FRAGILE/></INDEX> +<INDEX>evaluation<SUBINDEX>delayed</SUBINDEX><SEE>delayed evaluation</SEE><FRAGILE/></INDEX> +<INDEX>evaluation<SUBINDEX>environment model of</SUBINDEX><SEE>environment model of evaluation</SEE><FRAGILE/></INDEX> +<INDEX>evaluation<SUBINDEX>substitution model of</SUBINDEX><SEE>substitution model of function application</SEE><FRAGILE/></INDEX> +<INDEX>evaluator<SEEALSO>interpreter</SEEALSO><FRAGILE/></INDEX> +<INDEX>evaluators<SEE>metacircular evaluator; analyzing evaluator; lazy evaluator; nondeterministic evaluator; query interpreter; explicit-control evaluator</SEE><FRAGILE/></INDEX> +<INDEX>expression<SUBINDEX>algebraic</SUBINDEX><SEE>algebraic expressions</SEE><FRAGILE/></INDEX> +<INDEX>expression<SEEALSO>compound expression; primitive expression</SEEALSO><FRAGILE/></INDEX> +<INDEX>expression<SUBINDEX>symbolic</SUBINDEX><SEEALSO>string(s); symbol(s)</SEEALSO><FRAGILE/></INDEX> +<INDEX>factorial<SEEALSO><JAVASCRIPTINLINE>factorial</JAVASCRIPTINLINE></SEEALSO><FRAGILE/></INDEX> +<INDEX>Fibonacci numbers<SEEALSO><JAVASCRIPTINLINE>fib</JAVASCRIPTINLINE></SEEALSO><FRAGILE/></INDEX> +<INDEX>Fibonacci numbers<SUBINDEX>infinite stream of</SUBINDEX><SEE><JAVASCRIPTINLINE>fibs</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>formal parameters<SEE>parameters</SEE><FRAGILE/></INDEX> +<INDEX>fraction<SEE>rational number(s)</SEE><FRAGILE/></INDEX> +<INDEX>frame (query interpreter)<SEEALSO>pattern matching; unification</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>function</ORDER><JAVASCRIPTINLINE>function</JAVASCRIPTINLINE> (keyword)<SEEALSO>function declaration</SEEALSO><FRAGILE/></INDEX> +<INDEX><FUNCTION/><SUBINDEX>higher-order</SUBINDEX><SEE>higher-order function</SEE><FRAGILE/></INDEX> +<INDEX>function application<SUBINDEX>substitution model of</SUBINDEX><SEE>substitution model of function application</SEE><FRAGILE/></INDEX> +<INDEX>GCD<SEE>greatest common divisor</SEE><FRAGILE/></INDEX> +<INDEX>generic types<SEE>polymorphic types</SEE><FRAGILE/></INDEX> +<INDEX>graphics<SEE>picture language</SEE><FRAGILE/></INDEX> +<INDEX>greatest common divisor<SEEALSO><JAVASCRIPTINLINE>gcd</JAVASCRIPTINLINE></SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>how to</ORDER>``how to'' vs.<SPACE/>``what is'' description<SEE>imperative vs.<SPACE/>declarative knowledge</SEE><FRAGILE/></INDEX> +<INDEX><ORDER>if</ORDER><JAVASCRIPTINLINE>if</JAVASCRIPTINLINE> (keyword)<SEEALSO>conditional statement</SEEALSO><FRAGILE/></INDEX> +<INDEX>infinite stream(s)<SUBINDEX>of integers</SUBINDEX><SEE><JAVASCRIPTINLINE>integers</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>infinite stream(s)<SUBINDEX>of Fibonacci numbers</SUBINDEX><SEE><JAVASCRIPTINLINE>fibs</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>infinite stream(s)<SUBINDEX>of prime numbers</SUBINDEX><SEE><JAVASCRIPTINLINE>primes</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>information retrieval<SEE>data base</SEE><FRAGILE/></INDEX> +<INDEX>integral<SEEALSO>definite integral; Monte Carlo integration</SEEALSO><FRAGILE/></INDEX> +<INDEX>interpreter<SEEALSO>evaluator</SEEALSO><FRAGILE/></INDEX> +<INDEX>iteration contructs<SEE>looping constructs</SEE><FRAGILE/></INDEX> +<INDEX>iterative process<SUBINDEX>implemented by function call</SUBINDEX><SEEALSO>tail recursion</SEEALSO><FRAGILE/></INDEX> +<INDEX>language<SEE>parsing natural language; programming language</SEE><FRAGILE/></INDEX> +<INDEX><ORDER>let</ORDER><JAVASCRIPTINLINE>let</JAVASCRIPTINLINE> (keyword)<SEEALSO>variable, declaration</SEEALSO><FRAGILE/></INDEX> +<INDEX>lexical scoping<SEEALSO>scope of a name</SEEALSO><FRAGILE/></INDEX> +<INDEX>LIFO buffer<SEE>stack</SEE><FRAGILE/></INDEX> +<INDEX>Lisp<SEEALSO>Scheme</SEEALSO><FRAGILE/></INDEX> +<INDEX>list(s)<SEEALSO>list structure</SEEALSO><FRAGILE/></INDEX> +<INDEX>list(s)<SUBINDEX>empty</SUBINDEX><SEE>empty list</SEE><FRAGILE/></INDEX> +<INDEX>list structure<SEEALSO>list(s)</SEEALSO><FRAGILE/></INDEX> +<INDEX>logic programming<SEEALSO>query interpreter; query language</SEEALSO><FRAGILE/></INDEX> +<INDEX>logical conjunction<SEEALSO><USE>&&</USE> (logical conjunction)</SEEALSO><FRAGILE/></INDEX> +<INDEX>logical disjunction<SEEALSO>{\tt "|"|} (logical disjunction)</SEEALSO><FRAGILE/></INDEX> +<INDEX>looping constructs<SEEALSO>while loop</SEEALSO><FRAGILE/></INDEX> +<INDEX>magician<SEE>numerical analyst</SEE><FRAGILE/></INDEX> +<INDEX>making change<SEE>counting change</SEE><FRAGILE/></INDEX> +<INDEX>mathematical function<SEE>function (mathematical)</SEE><FRAGILE/></INDEX> +<INDEX>means of combination<SEEALSO>closure</SEEALSO><FRAGILE/></INDEX> +<INDEX>merging infinite streams<SEE>infinite stream(s)</SEE><FRAGILE/></INDEX> +<INDEX>modified registers<SEE>instruction sequence</SEE><FRAGILE/></INDEX> +<INDEX>money, changing<SEE>counting change</SEE><FRAGILE/></INDEX> +<INDEX>mutable data objects<SEEALSO>queue; table</SEEALSO><FRAGILE/></INDEX> +<INDEX>name<SEE>constant; local name; variable</SEE><FRAGILE/></INDEX> +<INDEX>name<SUBINDEX>scope of</SUBINDEX><SEEALSO>scope of a name</SEEALSO><FRAGILE/></INDEX> +<INDEX>natural language parsing<SEE>parsing natural language</SEE><FRAGILE/></INDEX> +<INDEX>needed registers<SEE>instruction sequence</SEE><FRAGILE/></INDEX> +<INDEX>nested declaration(s)<SEE>internal declaration</SEE><FRAGILE/></INDEX> +<INDEX>nested mapping(s)<SEE>mapping</SEE><FRAGILE/></INDEX> +<INDEX>normal-order evaluator<SEE>lazy evaluator</SEE><FRAGILE/></INDEX> +<INDEX>operator precedence<SEE>precedence</SEE><FRAGILE/></INDEX> +<INDEX>order of subexpression evaluation<SEE>order of evaluation</SEE><FRAGILE/></INDEX> +<INDEX>parameter passing<SEE>call-by-name argument passing; call-by-need argument passing</SEE><FRAGILE/></INDEX> +<INDEX><USE>parse</USE><SEEALSO>parsing JavaScript</SEEALSO><FRAGILE/></INDEX> +<INDEX>prime number(s)<SUBINDEX>infinite stream of</SUBINDEX><SEE><JAVASCRIPTINLINE>primes</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>primitive query<SEE>simple query</SEE><FRAGILE/></INDEX> +<INDEX>program<SUBINDEX>structure of</SUBINDEX><SEEALSO>abstraction barriers</SEEALSO><FRAGILE/></INDEX> +<INDEX>programming<SUBINDEX>data-directed</SUBINDEX><SEE>data-directed programming</SEE><FRAGILE/></INDEX> +<INDEX>programming<SUBINDEX>functional</SUBINDEX><SEE>functional programming</SEE><FRAGILE/></INDEX> +<INDEX>query<SEEALSO>compound query; simple query</SEEALSO><FRAGILE/></INDEX> +<INDEX>query language<SUBINDEX>simple query</SUBINDEX><SEE>simple query</SEE><FRAGILE/></INDEX> +<INDEX>query language<SUBINDEX>compound query</SUBINDEX><SEE>compound query</SEE><FRAGILE/></INDEX> +<INDEX>query language<SUBINDEX>rule</SUBINDEX><SEE>rule</SEE><FRAGILE/></INDEX> +<INDEX>query interpreter<SUBINDEX>simple query</SUBINDEX><SEE>simple query</SEE><FRAGILE/></INDEX> +<INDEX>query interpreter<SUBINDEX>compound query</SUBINDEX><SEE>compound query</SEE><FRAGILE/></INDEX> +<INDEX>query interpreter<SUBINDEX>rule</SUBINDEX><SEE>rule</SEE><FRAGILE/></INDEX> +<INDEX>read-evaluate-print loop<SEEALSO>driver loop</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>return</ORDER><JAVASCRIPTINLINE>return</JAVASCRIPTINLINE> (keyword)<SEEALSO>return statement</SEEALSO><FRAGILE/></INDEX> +<!-- + <INDEX>recursion<SUBINDEX>infinite<SEE>infinite recursion</SEE></SUBINDEX></INDEX> + <INDEX>infinite recursion<SEE>recursion, infinite</SEE></INDEX> +--> +<INDEX>roots of equation<SEE>half-interval method; Newton<APOS/>s method</SEE><FRAGILE/></INDEX> +<INDEX>scope of a name<SEEALSO>lexical scoping</SEEALSO><FRAGILE/></INDEX> +<INDEX>simulation<SUBINDEX>Monte Carlo</SUBINDEX><SEE>Monte Carlo simulation</SEE><FRAGILE/></INDEX> +<INDEX>simulation<SUBINDEX><ORDER>digital</ORDER>of digital circuit</SUBINDEX><SEE>digital-circuit simulation</SEE><FRAGILE/></INDEX> +<INDEX>solving equation<SEE>half-interval method; Newton<APOS/>s method; <JAVASCRIPTINLINE>solve</JAVASCRIPTINLINE></SEE><FRAGILE/></INDEX> +<INDEX>square root<SEEALSO><JAVASCRIPTINLINE>sqrt</JAVASCRIPTINLINE></SEEALSO><FRAGILE/></INDEX> +<INDEX>state<SUBINDEX>local</SUBINDEX><SEE>local state</SEE><FRAGILE/></INDEX> +<INDEX>statement sequence<SEE>sequence of statements</SEE><FRAGILE/></INDEX> +<INDEX>stream(s)<SUBINDEX>infinite</SUBINDEX><SEE>infinite streams</SEE><FRAGILE/></INDEX> +<INDEX>string(s)<SUBINDEX>quotation marks for</SUBINDEX><SEE>quotation marks</SEE><FRAGILE/></INDEX> +<INDEX>symbolic expression<SEEALSO>string(s); symbol(s)</SEEALSO><FRAGILE/></INDEX> +<INDEX>synchronization<SEE>concurrency</SEE><FRAGILE/></INDEX> +<INDEX>syntax<SUBINDEX>abstract</SUBINDEX><SEE>abstract syntax</SEE><FRAGILE/></INDEX> +<INDEX>syntax<SEEALSO>syntactic form</SEEALSO><FRAGILE/></INDEX> +<INDEX>table<SUBINDEX>operation-and-type</SUBINDEX><SEE>operation-and-type table</SEE><FRAGILE/></INDEX> +<INDEX>temporal dead zone (TDZ)<SEEALSO>declaration, use of name before</SEEALSO><FRAGILE/></INDEX> +<INDEX>TDZ (temporal dead zone)<SEEALSO>declaration, use of name before</SEEALSO><FRAGILE/></INDEX> +<INDEX>ternary operator<SEE>conditional expression</SEE><FRAGILE/></INDEX> +<INDEX>tree<SUBINDEX>binary</SUBINDEX><SEEALSO>binary tree</SEEALSO><FRAGILE/></INDEX> +<INDEX><ORDER>what is</ORDER>``what is'' vs.<SPACE/>``how to'' description<SEE>declarative vs.<SPACE/>imperative knowledge</SEE><FRAGILE/></INDEX> +<INDEX><ORDER>while</ORDER><JAVASCRIPTINLINE>while</JAVASCRIPTINLINE> (keyword)<SEEALSO>while loop</SEEALSO><FRAGILE/></INDEX> diff --git a/xml/cn/others/97references97.xml b/xml/cn/others/97references97.xml new file mode 100644 index 000000000..e2ab93bb0 --- /dev/null +++ b/xml/cn/others/97references97.xml @@ -0,0 +1,497 @@ + + + References + + +\begin{itemize}[label={},leftmargin=0pt,itemsep=1ex]\small + + + + Abelson, Harold, Andrew Berlin, Jacob Katzenelson, + William McAllister, + Guillermo Rozas, Gerald Jay Sussman, and Jack Wisdom. 1992. The + Supercomputer Toolkit: A general framework for special-purpose + computing. International Journal of High-Speed Electronics + 3(3):337361. + + + Allen, John. 1978. Anatomy of Lisp. New York: McGraw-Hill. + + + + + ANSI X3.226-1994. American National Standard for Information + SystemsProgramming LanguageCommon Lisp. + + + + + Appel, Andrew W. 1987. Garbage collection can be faster than stack + allocation. Information Processing Letters 25(4):275279. + + Backus, John. 1978. Can programming be liberated from the von + Neumann style? Communications of the ACM 21(8):613641. + + Baker, Henry G., Jr. 1978. List processing in real time on a serial + computer. Communications of the ACM 21(4):280293. + + Batali, John, Neil Mayle, Howard Shrobe, Gerald Jay Sussman, and + Daniel Weise. 1982. The Scheme-81 architectureSystem and chip. + In Proceedings of the MIT Conference on Advanced Research in + VLSI, edited by Paul Penfield, Jr. Dedham, MA: Artech House. + + Borning, Alan. 1977. ThingLabAn object-oriented system for + building simulations using constraints. In Proceedings of the 5th + International Joint Conference on Artificial Intelligence. + + Borodin, Alan, and Ian Munro. 1975. The Computational + Complexity of Algebraic and Numeric Problems. New York: American + Elsevier. + + Chaitin, Gregory J. 1975. Randomness and mathematical proof. Scientific American 232(5):\linebreak[0]4752. + + Church, Alonzo. 1941. The Calculi of Lambda-Conversion. + Princeton, N.J.: Princeton University Press. + + Clark, Keith L. 1978. Negation as failure. In Logic and Data + Bases. New York: Plenum Press, pp. 293322. + + Clinger, William. 1982. Nondeterministic call by need is neither + lazy nor by name. In Proceedings of the ACM Symposium on Lisp and + Functional Programming, pp. 226234. + + + + Clinger, William, and Jonathan Rees. 1991. Macros that work. In + Proceedings of the 1991 ACM Conference on Principles of + Programming Languages, pp. 155162. + + + + + Colmerauer A., H. Kanoui, R. Pasero, and P. Roussel. 1973. Un systme + de communication homme-machine en franais. Technical report, + Groupe dIntelligence Artificielle, Universit d'Aix-Marseille II, + Luminy. + + + + Cormen, Thomas, Charles Leiserson, and Ronald Rivest. 1990. Introduction to Algorithms. Cambridge, MA: MIT Press. + + + Cormen, Thomas H., Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. 2022. Introduction to Algorithms. 4th edition. Cambridge, MA: MIT Press. + + + + + + Crockford, Douglas. 2008. JavaScript: The Good Parts. + Sebastopol, CA: O'Reilly Media. + + + + Darlington, John, Peter Henderson, and David Turner. 1982. Functional Programming and Its Applications. New York: Cambridge + University Press. + + Dijkstra, Edsger W. 1968a. The structure of the THE + multiprogramming system. Communications of the ACM + 11(5):341346. + + Dijkstra, Edsger W. 1968b. Cooperating sequential processes. In Programming Languages, edited by F. Genuys. New York: Academic Press, pp. + 43112. + + Dinesman, Howard P. 1968. Superior Mathematical Puzzles. New + York: Simon and Schuster. + + de Kleer, Johan, Jon Doyle, Guy Steele, and Gerald J. Sussman. 1977. + AMORD: Explicit control of reasoning. In Proceedings of the ACM + Symposium on Artificial Intelligence and Programming Languages, pp. + 116125. + + Doyle, Jon. 1979. A truth maintenance system. Artificial + Intelligence 12:231272. + + + + ECMA. 1997. ECMAScript: A general purpose, +cross-platform programming +language. 1st edition, edited by Guy L. Steele Jr. Ecma International. + + ECMA. 2015. ECMAScript: A general purpose, cross-platform programming +language. 6th edition, edited by Allen Wirfs-Brock. Ecma International. + + ECMA. 2020. ECMAScript: A general purpose, cross-platform programming +language. 11th edition, edited by Jordan Harband. Ecma International. + + Edwards, A. W. F. 2019. Pascal's Arithmetical Triangle. Mineola, New York: Dover Publications. + + + + Feeley, Marc. 1986. Deux approches l'implantation du + language Scheme. Masters thesis, Universit de Montral. + + Feeley, Marc and Guy Lapalme. 1987. Using closures for code + generation. Journal of Computer Languages 12(1):4766. + + Feigenbaum, Edward, and Howard Shrobe. 1993. The Japanese National + Fifth Generation Project: Introduction, survey, and evaluation. In Future Generation Computer Systems, vol. 9, pp. 105117. + + Feller, William. 1957. An Introduction to Probability Theory + and Its Applications, volume 1. New York: John Wiley Sons. + + Fenichel, R., and J. Yochelson. 1969. A Lisp garbage collector for + virtual memory computer systems. Communications of the ACM + 12(11):611612. + + Floyd, Robert. 1967. Nondeterministic algorithms. JACM, 14(4):636644. + + Forbus, Kenneth D., and Johan de Kleer. 1993. Building Problem + Solvers. Cambridge, MA: MIT Press. + + Friedman, Daniel P., and David S. Wise. 1976. CONS should not + evaluate its arguments. In Automata, Languages, and Programming: + Third International Colloquium, edited by S. Michaelson and R. + Milner, pp. 257284. + + Friedman, Daniel P., Mitchell Wand, and Christopher T. Haynes. 1992. + Essentials of Programming Languages. Cambridge, MA: MIT + Press/McGraw-Hill. + + Gabriel, Richard P. 1988. The Why of Y. Lisp Pointers + 2(2):1525. + + Goldberg, Adele, and David Robson. 1983. Smalltalk-80: The + Language and Its Implementation. Reading, MA: Addison-Wesley. + + Gordon, Michael, Robin Milner, and Christopher Wadsworth. 1979. Edinburgh LCF. Lecture Notes in Computer Science, volume 78. New + York: Springer-Verlag. + + Gray, Jim, and Andreas Reuter. 1993. Transaction Processing: + Concepts and Models. San Mateo, CA: Morgan-Kaufman. + + Green, Cordell. 1969. Application of theorem proving to problem + solving. In Proceedings of the International Joint Conference on + Artificial Intelligence, pp. 219240. + + Green, Cordell, and Bertram Raphael. 1968. The use of + theorem-proving techniques in question-answering systems. In Proceedings of the ACM National Conference, pp. 169181. + + + + Griss, Martin L. 1981. Portable Standard Lisp, a brief overview. + Utah Symbolic Computation Group Operating Note 58, University of Utah. + + + + Guttag, John V. 1977. Abstract data types and the development of + data structures. Communications of the ACM 20(6):397404. + + Hamming, Richard W. 1980. Coding and Information Theory. + Englewood Cliffs, N.J.: Prentice-Hall. + + Hanson, Christopher P. 1990. Efficient stack allocation for + tail-recursive languages. In Proceedings of ACM Conference on + Lisp and Functional Programming, pp. 106118. + + Hanson, Christopher P. 1991. A syntactic closures macro facility. + Lisp Pointers, 4(4):916. + + Hardy, Godfrey H. 1921. Srinivasa Ramanujan. Proceedings of + the London Mathematical Society XIX(2). + + Hardy, Godfrey H., and E. M. Wright. 1960. An Introduction to + the Theory of Numbers. 4th edition. New York: Oxford University + Press. + + Havender, J. 1968. Avoiding deadlocks in multi-tasking systems. IBM Systems Journal 7(2):7484. + + + + Hearn, Anthony C. 1969. Standard Lisp. Technical report AIM-90, + Artificial Intelligence Project, Stanford University. + + + + Henderson, Peter. 1980. Functional Programming: Application and + Implementation. Englewood Cliffs, N.J.: Prentice-Hall. + + Henderson. Peter. 1982. Functional Geometry. In Conference Record + of the 1982 ACM Symposium on Lisp and Functional Programming, + pp. 179187. + + Hewitt, Carl E. 1969. PLANNER: A language for proving theorems in + robots. In Proceedings of the International Joint Conference on + Artificial Intelligence, pp. 295301. + + Hewitt, Carl E. 1977. Viewing control structures as patterns of + passing messages. Journal of Artificial Intelligence + 8(3):323364. + + Hoare, C. A. R. 1972. Proof of correctness of data representations. + Acta Informatica 1(1):271281. + + Hodges, Andrew. 1983. Alan Turing: The Enigma. New York: Simon + and Schuster. + + Hofstadter, Douglas R. 1979. Gdel, Escher, Bach: An Eternal + Golden Braid. New York: Basic Books. + + Hughes, R. J. M. 1990. Why functional programming matters. In Research Topics in Functional Programming, edited by David Turner. + Reading, MA: Addison-Wesley, pp. 1742. + + IEEE Std 1178-1990. 1990. IEEE Standard for the Scheme + Programming Language. + + Ingerman, Peter, Edgar Irons, Kirk Sattley, and Wallace Feurzeig; + assisted by M. Lind, Herbert Kanner, and Robert Floyd. 1960. THUNKS: + A way of compiling procedure statements, with some comments on + procedure declarations. Unpublished manuscript. (Also, private + communication from Wallace Feurzeig.) + + + + Jaffar, Joxan, and Peter J. Stuckey. 1986. Semantics of infinite tree + logic programming. Theoretical Computer Science 46:141158. + + + + Kaldewaij, Anne. 1990. Programming: The Derivation of + Algorithms. New York: Prentice-Hall. + + + + + Kohlbecker, Eugene Edmund, Jr. 1986. Syntactic extensions in the + programming language Lisp. Ph.D. thesis, Indiana University. + + + + + + Knuth, Donald E. 1973. Fundamental Algorithms. Volume 1 of + The Art of Computer Programming. 2nd edition. Reading, MA: + Addison-Wesley. + + Knuth, Donald E. 1981. Seminumerical Algorithms. Volume 2 of + The Art of Computer Programming. 2nd edition. Reading, MA: + Addison-Wesley. + + + Knuth, Donald E. 1997a. Fundamental Algorithms. Volume 1 of + The Art of Computer Programming. 3rd edition. Reading, MA: + Addison-Wesley. + + Knuth, Donald E. 1997b. Seminumerical Algorithms. Volume 2 of + The Art of Computer Programming. 3rd edition. Reading, MA: + Addison-Wesley. + + + + Konopasek, Milos, and Sundaresan Jayaraman. 1984. The TK!Solver + Book: A Guide to Problem-Solving in Science, Engineering, Business, + and Education. Berkeley, CA: Osborne/McGraw-Hill. + + Kowalski, Robert. 1973. Predicate logic as a programming language. + Technical report 70, Department of Computational Logic, School of + Artificial Intelligence, University of Edinburgh. + + Kowalski, Robert. 1979. Logic for Problem Solving. New York: + North-Holland. + + Lamport, Leslie. 1978. Time, clocks, and the ordering of events in a + distributed system. Communications of the ACM 21(7):558565. + + Lampson, Butler, J. J. Horning, R. London, J. G. Mitchell, and G. K. + Popek. 1981. Report on the programming language Euclid. Technical + report, Computer Systems Research Group, University of Toronto. + + Landin, Peter. 1965. A correspondence between Algol 60 and Church's + lambda notation: Part I. Communications of the ACM + 8(2):89101. + + Lieberman, Henry, and Carl E. Hewitt. 1983. A real-time garbage + collector based on the lifetimes of objects. Communications of + the ACM 26(6):419429. + + Liskov, Barbara H., and Stephen N. Zilles. 1975. Specification + techniques for data abstractions. IEEE Transactions on Software + Engineering 1(1):719. + + McAllester, David Allen. 1978. A three-valued truth-maintenance + system. Memo 473, MIT Artificial Intelligence Laboratory. + + McAllester, David Allen. 1980. An outlook on truth maintenance. + Memo 551, MIT Artificial Intelligence Laboratory. + + + + McCarthy, John. 1960. Recursive functions of symbolic expressions + and their computation by machine. Communications of the ACM + 3(4):184-195. + + + + McCarthy, John. 1967. A basis for a mathematical theory of + computation. In Computer Programing and Formal Systems, edited + by P. Braffort and D. Hirschberg. North-Holland, pp. 3370. + + + + McCarthy, John. 1978. The history of Lisp. In Proceedings of + the ACM SIGPLAN Conference on the History of Programming Languages. + + McCarthy, John, P. W. Abrahams, D. J. Edwards, T. P. Hart, and M. I. + Levin. 1965. Lisp 1.5 Programmer's Manual. 2nd edition. + Cambridge, MA: MIT Press. + + + + McDermott, Drew, and Gerald Jay Sussman. 1972. Conniver reference + manual. Memo 259, MIT Artificial Intelligence Laboratory. + + Miller, Gary L. 1976. Riemann's Hypothesis and tests for primality. + Journal of Computer and System Sciences 13(3):300317. + + Miller, James S., and Guillermo J. Rozas. 1994. Garbage collection is + fast, but a stack is faster. Memo 1462, MIT Artificial Intelligence + Laboratory. + + Moon, David. 1978. MacLisp reference manual, Version 0. Technical + report, MIT Laboratory for Computer Science. + + + + Moon, David, and Daniel Weinreb. 1981. Lisp machine manual. + Technical report, MIT Artificial Intelligence Laboratory. + + + + Morris, J. H., Eric Schmidt, and Philip Wadler. 1980. Experience + with an applicative string processing language. In Proceedings + of the 7th Annual ACM SIGACT/SIGPLAN Symposium on the Principles of + Programming Languages. + + Phillips, Hubert. 1934. The Sphinx Problem Book. London: Faber + and Faber. + + Phillips, Hubert. 1961. My Best Puzzles in Logic and Reasoning. + New York: Dover Publications. + + Rabin, Michael O. 1980. Probabilistic algorithm for testing primality. + Journal of Number Theory 12:128138. + + Raymond, Eric. 1996. The New Hacker's Dictionary. 3rd edition. + Cambridge, MA: MIT Press. + + Raynal, Michel. 1986. Algorithms for Mutual Exclusion. + Cambridge, MA: MIT Press. + + Rees, Jonathan A., and Norman I. Adams IV. 1982. T: A dialect of Lisp + or, lambda: The ultimate software tool. In Conference Record of + the 1982 ACM Symposium on Lisp and Functional Programming, pp. + 114122. + + + + Rees, Jonathan, and William Clinger (eds). 1991. The revised$^4$ + report on the algorithmic language Scheme. Lisp Pointers, 4(3). + + + + + + Rivest, Ronald, Adi Shamir, and Leonard Adleman. 1977. A method for + obtaining digital signatures and public-key cryptosystems. Technical + memo LCS/TM82, MIT Laboratory for Computer Science. + + + Rivest, Ronald L., Adi Shamir, and Leonard M. Adleman. 1978. A method for + obtaining digital signatures and public-key cryptosystems. Communications + of the ACM, 21(2):120126. + + + + Robinson, J. A. 1965. A machine-oriented logic based on the + resolution principle. Journal of the ACM 12(1):23. + + Robinson, J. A. 1983. Logic programmingPast, present, and future. + New Generation Computing 1:107124. + + + + + Sagade, Y. 2015. SICP exercise 1.14 + + + + + + Spafford, Eugene H. 1989. The Internet Worm: Crisis and aftermath. + Communications of the ACM 32(6):678688. + + Steele, Guy Lewis, Jr. 1977. Debunking the expensive procedure + call myth. In Proceedings of the National Conference of the + ACM, pp. 153162. + + Steele, Guy Lewis, Jr., and Gerald Jay Sussman. 1975. Scheme: An + interpreter for the extended lambda calculus. Memo 349, MIT + Artificial Intelligence Laboratory. + + Steele, Guy Lewis, Jr., Donald R. Woods, Raphael A. Finkel, Mark R. + Crispin, Richard M. Stallman, and Geoffrey S. Goodfellow. 1983. The Hacker's Dictionary. New York: Harper + Row. + + Stoy, Joseph E. 1977. Denotational Semantics. Cambridge, MA: + MIT Press. + + Sussman, Gerald Jay, and Richard M. Stallman. 1975. Heuristic + techniques in computer-aided circuit analysis. IEEE Transactions + on Circuits and Systems CAS-22(11):857865. + + Sussman, Gerald Jay, and Guy Lewis Steele Jr. 1980. ConstraintsA + language for expressing almost-hierarchical descriptions. AI + Journal 14:139. + + Sussman, Gerald Jay, and Jack Wisdom. 1992. Chaotic evolution of the + solar system. Science 257:256262. + + Sussman, Gerald Jay, Terry Winograd, and Eugene Charniak. 1971. + Microplanner reference manual. Memo 203A, MIT Artificial Intelligence + Laboratory. + + Sutherland, Ivan E. 1963. SKETCHPAD: A man-machine graphical + communication system. Technical report 296, MIT Lincoln Laboratory. + + Thatcher, James W., Eric G. Wagner, and Jesse B. Wright. 1978. + Data type specification: Parameterization and the power of + specification techniques. In Conference Record of the Tenth Annual ACM + Symposium on Theory of Computing, pp. 119132. + + Turner, David. 1981. The future of applicative languages. In Proceedings of the 3rd European Conference on Informatics, Lecture + Notes in Computer Science, volume 123. New York: Springer-Verlag, pp. + 334348. + + Wand, Mitchell. 1980. Continuation-based program transformation + strategies. Journal of the ACM 27(1):164180. + + Waters, Richard C. 1979. A method for analyzing loop programs. IEEE Transactions on Software Engineering 5(3):237247. + + Winston, Patrick. 1992. Artificial Intelligence. 3rd edition. + Reading, MA: Addison-Wesley. + + Zabih, Ramin, David McAllester, and David Chapman. 1987. + Non-deterministic Lisp with dependency-directed backtracking. + AAAI-87, pp. 5964. + + Zippel, Richard. 1979. Probabilistic algorithms for sparse + polynomials. Ph.D. dissertation, Department of Electrical Engineering + and Computer Science, MIT. + + Zippel, Richard. 1993. Effective Polynomial Computation. + Boston, MA: Kluwer Academic Publishers. + + +\end{itemize} + + + diff --git a/xml/cn/others/98indexpreface98.xml b/xml/cn/others/98indexpreface98.xml new file mode 100644 index 000000000..02257e908 --- /dev/null +++ b/xml/cn/others/98indexpreface98.xml @@ -0,0 +1,23 @@ + + +Index + +<PDF_ONLY> + \addcontentsline{toc}{chapter}{Index} +</PDF_ONLY> + +<TEXT> + <NOINDENT/> +Page numbers for JavaScript declarations are in italics.\\ +Page numbers followed by \textit{n} indicate footnotes. +</TEXT> + diff --git a/xml/cn/others/99making99.xml b/xml/cn/others/99making99.xml new file mode 100644 index 000000000..5f97faf11 --- /dev/null +++ b/xml/cn/others/99making99.xml @@ -0,0 +1,98 @@ + + + About the SICP JS Project + + + + + Background + + + + The JavaScript adaptation of SICP is an open-source community effort. + The software and data required for making these web pages and the PDF edition are contained + in the github repository + source-academy / sicp, + and improvements, extensions and discussions are handled in this repository using git. + + + Martin Henz started translating SICP to JavaScript in 2008. He obtained the original + sources of the second edition from Gerald Jay Sussman, and converted them to + a custom-built XML format. The original sources are retained in the XML format, which + allows for generating the + comparison edition. + A processing system written in XSLT resulted in the + first version of the JavaScript adaptation around 2009, covering the first few sections of SICP. + The content of SICP JS contained in the + XML files are undergoing continuous improvement by the adapters Martin Henz and Tobias + Wrigstad, and by the community of SICP JS readers, using the github repository. + + + + In the book, program fragments often require other program fragments. + In order to collect and execute the necessary programs, the corresponding + SNIPPET tags in the xml files include REQUIRES tags. The + XML processors use these tags in order to assemble the executable programs. + The project thus can be seen as a literate programming system, custom-made + for authoring SICP JS. + + + + Interactive SICP JS + + + + Interactive SICP JS + was designed and implemented by Samuel Fang + in 2021. The XML textbook sources are translated to a JSON format, which + are then read and rendered by a dedicated component of the Source Academy. + + + + Comparison Edition + + + + The precursor of the comparison edition was the + mobile-friendly web edition + of SICP JS, designed and implemented by Liu Hang + in 2017. Feng Piaopiao improved the system in 2018. + He Xinyue and Wang Qian developed the + comparison edition + in 2020. + Formulas are retained in the resulting HTML files + and are typeset by the reader's browser on the fly, + using the MathJax system. + The comparison edition is maintained by Martin Henz. + + + + PDF Edition + + + + The early PDF editions (2010-2018) used XSLT for generating from + the XML sources. + The first Node.js version of the PDF edition + was designed and implemented by Chan Ger Hean in 2019. + Tobias Wrigstad and Martin Henz are the main developers of this system. + + + + Figures + + + + Most figures are adapted from the + HTML5/EPUB3 version of SICP + by Andres Raba. The figures are licensed under Creative Commons + Attribution-ShareAlike 4.0 International + License (cc by-sa). + JavaScript adaptations + of figures were done by Tobias Wrigstad using Inkscape and gratuitous use of + sed. + + + + + diff --git a/xml/en/chapter2/section3/subsection2.xml b/xml/en/chapter2/section3/subsection2.xml index e53229c00..340008166 100644 --- a/xml/en/chapter2/section3/subsection2.xml +++ b/xml/en/chapter2/section3/subsection2.xml @@ -1539,4 +1539,4 @@ deriv(list("x", "*", 4), "x"); differentiationsymbolic symbolic differentiation algebraic expressiondifferentiating - + --> From b9ce30aa9dbe27ad7e6a7a74fd73261c6566e5ef Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 14:56:39 +0800 Subject: [PATCH 24/55] Refactor translation logic to improve handling of SCHEME and SCHEMEINLINE tags in XML parsing --- i18n/controllers/recurTranslate.ts | 34 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 3bf805d75..544b9ae0a 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -288,7 +288,13 @@ async function recursivelyTranslate( currentDepth++; // If we're at depth 2, this is the start of a new segment. - if (node.name == "SCHEME" || node.name == "SCHEMEINLINE") return; + if ( + node.name == "SCHEME" || + node.name == "SCHEMEINLINE" || + parser._parser.tag === "SCHEME" || + parser._parser.tag === "SCHEMEINLINE" + ) + return; if (currentDepth === 2 || isRecording) { isRecording = true; @@ -303,9 +309,13 @@ async function recursivelyTranslate( parser.on("text", text => { text = strongEscapeXML(text); - + // ignore all scheme contents - if(parser._parser.tag == "SCHEME" || parser._parser.tag == "SCHEMEINLINE") return; + if ( + parser._parser.tag == "SCHEME" || + parser._parser.tag == "SCHEMEINLINE" + ) + return; if (isRecording) { currentSegment += text; @@ -321,7 +331,13 @@ async function recursivelyTranslate( }); parser.on("closetag", tagName => { - if (tagName !== "SCHEME" && tagName !== "SCHEMEINLINE" && isRecording) { + if ( + tagName !== "SCHEME" && + tagName !== "SCHEMEINLINE" && + parser._parser.tag !== "SCHEME" && + parser._parser.tag !== "SCHEMEINLINE" && + isRecording + ) { currentSegment += ``; } @@ -464,19 +480,13 @@ async function recursivelyTranslate( clean.on("opentag", node => { currDepth++; - if ( - node.name != "WRAPPER" && - node.name != "TRANSLATE" - ) { + if (node.name != "WRAPPER" && node.name != "TRANSLATE") { translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; } }); clean.on("closetag", tagName => { - if ( - tagName != "WRAPPER" && - tagName != "TRANSLATE" - ) { + if (tagName != "WRAPPER" && tagName != "TRANSLATE") { translatedChunk += ``; } currDepth--; From 5f23def86803019eba81e6425db1cab37bfb1d21 Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 14:58:45 +0800 Subject: [PATCH 25/55] added filename to json error logging --- javascript/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/index.js b/javascript/index.js index cb49f2f87..7d40b8d8c 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -192,7 +192,7 @@ async function translateXml(filepath, filename, option) { stream.end(); }); } } catch (error) { - errors.push(filepath + " " + error); + errors.push(path.join(filepath, filename) + " " + error); } return; } From 429cff5df06a4d323123843e4301ac48a022c607 Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 15:05:25 +0800 Subject: [PATCH 26/55] updated to remove file search annotation generated by api calls --- i18n/controllers/recurTranslate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 544b9ae0a..973fef576 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -562,5 +562,6 @@ function strongEscapeXML(str: string): string { .replace(//g, ">") .replace(/"/g, """) - .replace(/'/g, "'"); + .replace(/'/g, "'") + .replace(/【([\s\S]*?)】/g, ""); } From a28c5f85fe17f1fb66f7e3c74ba9febd5afc1066 Mon Sep 17 00:00:00 2001 From: yihao Date: Sat, 12 Apr 2025 15:13:06 +0800 Subject: [PATCH 27/55] ran prettier --- i18n/controllers/gitComm.ts | 77 ++-- i18n/controllers/translate.ts | 35 +- i18n/index.ts | 638 ++++++++++++++++---------------- i18n/initializers/initialize.ts | 6 +- javascript/index.js | 52 +-- 5 files changed, 406 insertions(+), 402 deletions(-) diff --git a/i18n/controllers/gitComm.ts b/i18n/controllers/gitComm.ts index 73548cf05..ccb4fb5f0 100644 --- a/i18n/controllers/gitComm.ts +++ b/i18n/controllers/gitComm.ts @@ -4,49 +4,52 @@ import fs from "fs"; import path from "path"; dotenv.config(); -if (process.env.GITHUB_OWNER === undefined || process.env.GITHUB_REPO === undefined) { - throw Error("Please specify GITHUB_OWNER, GITHUB_REPO to pull EN XML from!"); +if ( + process.env.GITHUB_OWNER === undefined || + process.env.GITHUB_REPO === undefined +) { + throw Error("Please specify GITHUB_OWNER, GITHUB_REPO to pull EN XML from!"); } // initialize GitHub API const octokit = new Octokit(); async function getSource(filePath: string): Promise { - let toTranslate; - - try { - const result = await octokit.request( - "GET /repos/{owner}/{repo}/contents/{path}", - { - owner: process.env.GITHUB_OWNER!, - repo: process.env.GITHUB_REPO!, - path: filePath, - headers: { - accept: "application/vnd.github.raw+json", - }, - } - ); - - toTranslate = result.data; - const output_dir = path.join(import.meta.dirname, "../../ori"); - - // Ensure directory exists - const dir = path.dirname(path.join(output_dir, filePath)); - fs.mkdirSync(dir, { recursive: true }); - - const fullPath = path.resolve(path.join(output_dir, filePath)); - fs.writeFileSync(fullPath, toTranslate); - - console.log( - `Successfully retrieved ${filePath} from GitHub, retrieval status: ${result.status}` - ); - } catch (error) { - console.log( - `Error retrieving ${filePath} from GitHub.\n Status: ${error.status}.\n Rate limit remaining: ${error.response.headers["x-ratelimit-remaining"]}.\n Message: ${error.response.data.message}` - ); - } - - return toTranslate as string; + let toTranslate; + + try { + const result = await octokit.request( + "GET /repos/{owner}/{repo}/contents/{path}", + { + owner: process.env.GITHUB_OWNER!, + repo: process.env.GITHUB_REPO!, + path: filePath, + headers: { + accept: "application/vnd.github.raw+json" + } + } + ); + + toTranslate = result.data; + const output_dir = path.join(import.meta.dirname, "../../ori"); + + // Ensure directory exists + const dir = path.dirname(path.join(output_dir, filePath)); + fs.mkdirSync(dir, { recursive: true }); + + const fullPath = path.resolve(path.join(output_dir, filePath)); + fs.writeFileSync(fullPath, toTranslate); + + console.log( + `Successfully retrieved ${filePath} from GitHub, retrieval status: ${result.status}` + ); + } catch (error) { + console.log( + `Error retrieving ${filePath} from GitHub.\n Status: ${error.status}.\n Rate limit remaining: ${error.response.headers["x-ratelimit-remaining"]}.\n Message: ${error.response.data.message}` + ); + } + + return toTranslate as string; } async function commitTranslated() {} diff --git a/i18n/controllers/translate.ts b/i18n/controllers/translate.ts index 9298dd08c..375c5b8ab 100644 --- a/i18n/controllers/translate.ts +++ b/i18n/controllers/translate.ts @@ -166,21 +166,22 @@ async function translate(language: string, filePath: string) { translated += ``; }); - clean.on("error", error => { - console.log( - "error encountered when validating XML: " + - error + - "\nvalidating section: " + - chunk.substring(0, 100) + "..." - ); - - // Attempt to recover using the internal parser - try { - clean._parser.resume(); - } catch (e) { - console.log("Failed to resume parser:", e); - } - }); + clean.on("error", error => { + console.log( + "error encountered when validating XML: " + + error + + "\nvalidating section: " + + chunk.substring(0, 100) + + "..." + ); + + // Attempt to recover using the internal parser + try { + clean._parser.resume(); + } catch (e) { + console.log("Failed to resume parser:", e); + } + }); let translated = ""; @@ -239,8 +240,6 @@ function escapeXML(str: string): string { return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); } - - function strongEscapeXML(str: string): string { return str .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") @@ -248,4 +247,4 @@ function strongEscapeXML(str: string): string { .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); -} \ No newline at end of file +} diff --git a/i18n/index.ts b/i18n/index.ts index ce72a159f..408be255f 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,319 +1,319 @@ -import PathGenerator from "./controllers/path.ts"; -import translate, { getFileErrors } from "./controllers/recurTranslate.ts"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { dirname } from "path"; -import OpenAI from "openai"; - -// Get the directory name of the current module -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// Global variables for tracking translation state -// These need to be accessible by signal handlers -let xmlFiles: string[] = []; -let filesToTranslate: string[] = []; -let successCount = 0; -let failureCount = 0; -let processedCount = 0; -let failures: { file: string; error: any }[] = []; - -const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; - -// Function to save summary log - can be called from signal handlers -async function saveSummaryLog() { - try { - const ai = new OpenAI({ - apiKey: process.env.API_KEY, - baseURL: process.env.AI_BASEURL - }); - - // list and delete all assistants - const assistants = await ai.beta.assistants.list({ limit: 100 }); - const failedDel: string[] = []; - await Promise.all( - assistants.data.map(async assistant => { - try { - await ai.beta.assistants.del(assistant.id); - } catch (error) { - failedDel.push(assistant.id); - } - }) - ).then(() => console.log("successfully removed all assistants")); - - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - let summaryLog = ` -Translation Summary (${timestamp}) -================================ -Total files scanned: ${xmlFiles.length} -Files needing translation: ${filesToTranslate.length} -Successfully translated: ${successCount} -Failed translations: ${failureCount} -Success rate: ${filesToTranslate.length > 0 ? ((successCount / filesToTranslate.length) * 100).toFixed(2) : 0}% -`; - - if (failedDel.length > 0) { - summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; - failedDel.forEach( - (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) - ); - } - - // Add failed translations to the log - if (failures.length > 0) { - summaryLog += `\nFailed Translations (High-level errors):\n`; - failures.forEach((failure, index) => { - summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; - }); - } - - // Add detailed errors captured during translation process - const fileErrors = getFileErrors(); - if (Object.keys(fileErrors).length > 0) { - failureCount = Object.keys(fileErrors).length; - summaryLog += `\nDetailed Translation Errors:\n`; - summaryLog += `============================\n`; - - for (const [filePath, errors] of Object.entries(fileErrors)) { - summaryLog += `\nFile: ${filePath}\n`; - errors.forEach((error, index) => { - summaryLog += ` ${index + 1}. ${error.error}\n`; - if (error.error) { - // Format the error object/message for better readability - const errorStr = - typeof error.error === "object" - ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors - : String(error.error); - summaryLog += ` Details: ${errorStr}\n`; - } - }); - } - } - - const logDir = path.resolve(__dirname, "../logs"); - if (!fs.existsSync(logDir)) { - fs.mkdirSync(logDir, { recursive: true }); - } - - const logPath = path.join(logDir, `translation-summary-${timestamp}.log`); - fs.writeFileSync(logPath, summaryLog); - console.log( - `Summary log saved to logs/translation-summary-${timestamp}.log` - ); - } catch (logError) { - console.error("Failed to save log file:", logError); - } -} - -// Register handlers for various termination signals -async function setupCleanupHandlers() { - // Handle normal exit - process.on("exit", () => { - console.log("Process is exiting, saving summary..."); - // Only synchronous operations work in 'exit' handlers - // We can't await here, as exit handlers must be synchronous - try { - // Use synchronous operations for final cleanup if needed - // Note: This won't actually call the async parts of saveSummaryLog properly - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const summaryContent = `Translation interrupted during exit at ${timestamp}`; - const logDir = path.resolve(__dirname, "../logs"); - if (!fs.existsSync(logDir)) { - fs.mkdirSync(logDir, { recursive: true }); - } - fs.writeFileSync( - path.join(logDir, `emergency-log-${timestamp}.log`), - summaryContent - ); - } catch (error) { - console.error("Failed to save emergency log during exit:", error); - } - }); - - // Handle Ctrl+C - process.on("SIGINT", async () => { - console.log("\nReceived SIGINT (Ctrl+C). Saving summary before exit..."); - await saveSummaryLog(); - process.exit(1); - }); - - // Handle SIGTERM (kill command) - process.on("SIGTERM", async () => { - console.log("\nReceived SIGTERM. Saving summary before exit..."); - await saveSummaryLog(); - process.exit(1); - }); - - // Handle uncaught exceptions - process.on("uncaughtException", async error => { - console.error("\nUncaught exception:", error); - console.log("Saving summary before exit..."); - await saveSummaryLog(); - process.exit(1); - }); -} - -// Function to recursively find all XML files in a directory -async function findAllXmlFiles(directory: string): Promise { - const files = await fs.promises.readdir(directory); - const xmlFiles: string[] = []; - - for (const file of files) { - const fullPath = path.join(directory, file); - const stat = await fs.promises.stat(fullPath); - - if (stat.isDirectory()) { - // Recursively search subdirectories - const subDirFiles = await findAllXmlFiles(fullPath); - xmlFiles.push(...subDirFiles); - } else if (path.extname(file).toLowerCase() === ".xml") { - // Add XML files to the list - xmlFiles.push(fullPath); - } - } - - return xmlFiles; -} - -// Function to check if a file needs translation -async function needsTranslation(enFilePath: string): Promise { - // Generate the corresponding cn file path - const cnFilePath = enFilePath.replace( - path.sep + "en" + path.sep, - path.sep + "cn" + path.sep - ); - - try { - // Check if CN file exists - const cnStats = await fs.promises.stat(cnFilePath); - if (!cnStats.isFile()) { - return true; // CN path exists but is not a file (unusual case) - } - - // Compare modification times - const enStats = await fs.promises.stat(enFilePath); - return enStats.mtime > cnStats.mtime; // Return true if EN file is newer - } catch (error) { - // If we get an error, it's likely because the CN file doesn't exist - return true; // Need to translate since CN file doesn't exist - } -} - -export default async function fancyName(path: string) { - const fullPath = PathGenerator(path); - await translate("Chinese", fullPath); -} - -(async () => { - await setupCleanupHandlers(); - - try { - // Get the absolute path to the xml/en directory using proper path resolution - const enDirPath = path.resolve(__dirname, "../xml/en"); - - console.log(`Scanning directory: ${enDirPath}`); - - // Find all XML files - xmlFiles = await findAllXmlFiles(enDirPath); - - console.log(`Found ${xmlFiles.length} XML files to check for translation`); - - // Filter files that need translation - filesToTranslate = []; - for (const file of xmlFiles) { - if (await needsTranslation(file)) { - filesToTranslate.push(file as never); - } - } - - console.log(`${filesToTranslate.length} files need translation`); - - if (filesToTranslate.length === 0) { - console.log("No files need translation. Exiting."); - return; - } - - // Process files in batches to avoid overwhelming the system - const batchSize: number = max_trans_num; - - // Track all failed translations with their errors - failures = []; - - for (let i = 0; i < filesToTranslate.length; i += batchSize) { - const batch = filesToTranslate.slice(i, i + batchSize); - console.log( - `Starting batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` - ); - - // Process each file in the batch, but handle errors individually - const results = await Promise.allSettled( - batch.map(async file => { - try { - console.log(`Translating file: ${file}`); - await translate("Chinese", file); - return { file, success: true }; - } catch (error) { - // Return failure with error but don't log yet - return { file, success: false, error }; - } - }) - ); - - // Count successes and failures - for (const result of results) { - processedCount++; - - if (result.status === "fulfilled") { - if (result.value.success) { - successCount++; - } else { - failureCount++; - failures.push({ - file: result.value.file, - error: result.value.error - }); - console.error( - `Translation failed for ${result.value.file}: ${result.value.error}` - ); - } - } else { - // This is for Promise rejections (should be rare with our error handling) - failureCount++; - failures.push({ - file: "Unknown file in batch", - error: result.reason - }); - console.error(`Promise rejected for file: ${result.reason}`); - } - } - - console.log( - `Completed batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` - ); - console.log( - `Progress: ${successCount} successful, ${failureCount} failed, ${processedCount} processed out of ${filesToTranslate.length} files` - ); - } - - console.log("All translations completed!"); - console.log( - `Final results: ${successCount} successful, ${failureCount} failed out of ${filesToTranslate.length} files` - ); - - // If there are failures, report them all at the end - if (failures.length > 0) { - console.log("\n===== FAILED TRANSLATIONS ====="); - failures.forEach((failure, index) => { - console.log(`${index + 1}. Failed file: ${failure.file}`); - console.log(` Error: ${failure.error}`); - }); - console.log("==============================\n"); - } - - // Save a detailed summary to a log file - await saveSummaryLog(); - } catch (e) { - console.error("Error during translation process:", e); - } -})(); +import PathGenerator from "./controllers/path.ts"; +import translate, { getFileErrors } from "./controllers/recurTranslate.ts"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; +import OpenAI from "openai"; + +// Get the directory name of the current module +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Global variables for tracking translation state +// These need to be accessible by signal handlers +let xmlFiles: string[] = []; +let filesToTranslate: string[] = []; +let successCount = 0; +let failureCount = 0; +let processedCount = 0; +let failures: { file: string; error: any }[] = []; + +const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; + +// Function to save summary log - can be called from signal handlers +async function saveSummaryLog() { + try { + const ai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.AI_BASEURL + }); + + // list and delete all assistants + const assistants = await ai.beta.assistants.list({ limit: 100 }); + const failedDel: string[] = []; + await Promise.all( + assistants.data.map(async assistant => { + try { + await ai.beta.assistants.del(assistant.id); + } catch (error) { + failedDel.push(assistant.id); + } + }) + ).then(() => console.log("successfully removed all assistants")); + + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + let summaryLog = ` +Translation Summary (${timestamp}) +================================ +Total files scanned: ${xmlFiles.length} +Files needing translation: ${filesToTranslate.length} +Successfully translated: ${successCount} +Failed translations: ${failureCount} +Success rate: ${filesToTranslate.length > 0 ? ((successCount / filesToTranslate.length) * 100).toFixed(2) : 0}% +`; + + if (failedDel.length > 0) { + summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; + failedDel.forEach( + (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) + ); + } + + // Add failed translations to the log + if (failures.length > 0) { + summaryLog += `\nFailed Translations (High-level errors):\n`; + failures.forEach((failure, index) => { + summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; + }); + } + + // Add detailed errors captured during translation process + const fileErrors = getFileErrors(); + if (Object.keys(fileErrors).length > 0) { + failureCount = Object.keys(fileErrors).length; + summaryLog += `\nDetailed Translation Errors:\n`; + summaryLog += `============================\n`; + + for (const [filePath, errors] of Object.entries(fileErrors)) { + summaryLog += `\nFile: ${filePath}\n`; + errors.forEach((error, index) => { + summaryLog += ` ${index + 1}. ${error.error}\n`; + if (error.error) { + // Format the error object/message for better readability + const errorStr = + typeof error.error === "object" + ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors + : String(error.error); + summaryLog += ` Details: ${errorStr}\n`; + } + }); + } + } + + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const logPath = path.join(logDir, `translation-summary-${timestamp}.log`); + fs.writeFileSync(logPath, summaryLog); + console.log( + `Summary log saved to logs/translation-summary-${timestamp}.log` + ); + } catch (logError) { + console.error("Failed to save log file:", logError); + } +} + +// Register handlers for various termination signals +async function setupCleanupHandlers() { + // Handle normal exit + process.on("exit", () => { + console.log("Process is exiting, saving summary..."); + // Only synchronous operations work in 'exit' handlers + // We can't await here, as exit handlers must be synchronous + try { + // Use synchronous operations for final cleanup if needed + // Note: This won't actually call the async parts of saveSummaryLog properly + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summaryContent = `Translation interrupted during exit at ${timestamp}`; + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + fs.writeFileSync( + path.join(logDir, `emergency-log-${timestamp}.log`), + summaryContent + ); + } catch (error) { + console.error("Failed to save emergency log during exit:", error); + } + }); + + // Handle Ctrl+C + process.on("SIGINT", async () => { + console.log("\nReceived SIGINT (Ctrl+C). Saving summary before exit..."); + await saveSummaryLog(); + process.exit(1); + }); + + // Handle SIGTERM (kill command) + process.on("SIGTERM", async () => { + console.log("\nReceived SIGTERM. Saving summary before exit..."); + await saveSummaryLog(); + process.exit(1); + }); + + // Handle uncaught exceptions + process.on("uncaughtException", async error => { + console.error("\nUncaught exception:", error); + console.log("Saving summary before exit..."); + await saveSummaryLog(); + process.exit(1); + }); +} + +// Function to recursively find all XML files in a directory +async function findAllXmlFiles(directory: string): Promise { + const files = await fs.promises.readdir(directory); + const xmlFiles: string[] = []; + + for (const file of files) { + const fullPath = path.join(directory, file); + const stat = await fs.promises.stat(fullPath); + + if (stat.isDirectory()) { + // Recursively search subdirectories + const subDirFiles = await findAllXmlFiles(fullPath); + xmlFiles.push(...subDirFiles); + } else if (path.extname(file).toLowerCase() === ".xml") { + // Add XML files to the list + xmlFiles.push(fullPath); + } + } + + return xmlFiles; +} + +// Function to check if a file needs translation +async function needsTranslation(enFilePath: string): Promise { + // Generate the corresponding cn file path + const cnFilePath = enFilePath.replace( + path.sep + "en" + path.sep, + path.sep + "cn" + path.sep + ); + + try { + // Check if CN file exists + const cnStats = await fs.promises.stat(cnFilePath); + if (!cnStats.isFile()) { + return true; // CN path exists but is not a file (unusual case) + } + + // Compare modification times + const enStats = await fs.promises.stat(enFilePath); + return enStats.mtime > cnStats.mtime; // Return true if EN file is newer + } catch (error) { + // If we get an error, it's likely because the CN file doesn't exist + return true; // Need to translate since CN file doesn't exist + } +} + +export default async function fancyName(path: string) { + const fullPath = PathGenerator(path); + await translate("Chinese", fullPath); +} + +(async () => { + await setupCleanupHandlers(); + + try { + // Get the absolute path to the xml/en directory using proper path resolution + const enDirPath = path.resolve(__dirname, "../xml/en"); + + console.log(`Scanning directory: ${enDirPath}`); + + // Find all XML files + xmlFiles = await findAllXmlFiles(enDirPath); + + console.log(`Found ${xmlFiles.length} XML files to check for translation`); + + // Filter files that need translation + filesToTranslate = []; + for (const file of xmlFiles) { + if (await needsTranslation(file)) { + filesToTranslate.push(file as never); + } + } + + console.log(`${filesToTranslate.length} files need translation`); + + if (filesToTranslate.length === 0) { + console.log("No files need translation. Exiting."); + return; + } + + // Process files in batches to avoid overwhelming the system + const batchSize: number = max_trans_num; + + // Track all failed translations with their errors + failures = []; + + for (let i = 0; i < filesToTranslate.length; i += batchSize) { + const batch = filesToTranslate.slice(i, i + batchSize); + console.log( + `Starting batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` + ); + + // Process each file in the batch, but handle errors individually + const results = await Promise.allSettled( + batch.map(async file => { + try { + console.log(`Translating file: ${file}`); + await translate("Chinese", file); + return { file, success: true }; + } catch (error) { + // Return failure with error but don't log yet + return { file, success: false, error }; + } + }) + ); + + // Count successes and failures + for (const result of results) { + processedCount++; + + if (result.status === "fulfilled") { + if (result.value.success) { + successCount++; + } else { + failureCount++; + failures.push({ + file: result.value.file, + error: result.value.error + }); + console.error( + `Translation failed for ${result.value.file}: ${result.value.error}` + ); + } + } else { + // This is for Promise rejections (should be rare with our error handling) + failureCount++; + failures.push({ + file: "Unknown file in batch", + error: result.reason + }); + console.error(`Promise rejected for file: ${result.reason}`); + } + } + + console.log( + `Completed batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` + ); + console.log( + `Progress: ${successCount} successful, ${failureCount} failed, ${processedCount} processed out of ${filesToTranslate.length} files` + ); + } + + console.log("All translations completed!"); + console.log( + `Final results: ${successCount} successful, ${failureCount} failed out of ${filesToTranslate.length} files` + ); + + // If there are failures, report them all at the end + if (failures.length > 0) { + console.log("\n===== FAILED TRANSLATIONS ====="); + failures.forEach((failure, index) => { + console.log(`${index + 1}. Failed file: ${failure.file}`); + console.log(` Error: ${failure.error}`); + }); + console.log("==============================\n"); + } + + // Save a detailed summary to a log file + await saveSummaryLog(); + } catch (e) { + console.error("Error during translation process:", e); + } +})(); diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index 634e8933a..11c412d68 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -22,9 +22,9 @@ export default async function createAssistant(language: string, ai: OpenAI) { tools: [{ type: "file_search" }] }); - const fileStreams = [ - path.resolve(__dirname, "../../dictionary/cn.txt") - ].map(path => fs.createReadStream(path)); + const fileStreams = [path.resolve(__dirname, "../../dictionary/cn.txt")].map( + path => fs.createReadStream(path) + ); // Create a vector store including our two files. const vectorStore = await ai.vectorStores.create({ diff --git a/javascript/index.js b/javascript/index.js index 7d40b8d8c..7a72ca70e 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -166,32 +166,34 @@ async function translateXml(filepath, filename, option) { } if (parseType == "json") { - try {const relativeFilePath = path.join( - filepath, - filename.replace(/\.xml$/, "") + ".html" - ); - - if (option == "generateTOC") { - generateTOC(doc, tableOfContent, relativeFilePath); - return; - } else if (option == "setupSnippet") { - setupSnippetsJson(doc.documentElement); - setupReferencesJson(doc.documentElement, relativeFilePath); - return; - } else if (option == "parseXml") { - const jsonObj = []; - parseXmlJson(doc, jsonObj, relativeFilePath); - - const outputFile = path.join( - outputDir, - tableOfContent[relativeFilePath].index + ".json" + try { + const relativeFilePath = path.join( + filepath, + filename.replace(/\.xml$/, "") + ".html" ); - const stream = fs.createWriteStream(outputFile); - stream.once("open", fd => { - stream.write(JSON.stringify(jsonObj)); - stream.end(); - }); - } } catch (error) { + + if (option == "generateTOC") { + generateTOC(doc, tableOfContent, relativeFilePath); + return; + } else if (option == "setupSnippet") { + setupSnippetsJson(doc.documentElement); + setupReferencesJson(doc.documentElement, relativeFilePath); + return; + } else if (option == "parseXml") { + const jsonObj = []; + parseXmlJson(doc, jsonObj, relativeFilePath); + + const outputFile = path.join( + outputDir, + tableOfContent[relativeFilePath].index + ".json" + ); + const stream = fs.createWriteStream(outputFile); + stream.once("open", fd => { + stream.write(JSON.stringify(jsonObj)); + stream.end(); + }); + } + } catch (error) { errors.push(path.join(filepath, filename) + " " + error); } return; From c3be2c09942297719d2fcfcad8844638d8825240 Mon Sep 17 00:00:00 2001 From: yihao Date: Sun, 13 Apr 2025 12:04:08 +0800 Subject: [PATCH 28/55] refined troubleshoot toggle, temporarily removed problemtic skipping logic --- i18n/controllers/recurTranslate.ts | 30 +- xml/cn/chapter1/section1/subsection4.xml | 681 +-------- xml/cn/chapter2/section3/subsection2.xml | 1373 +++++++++++------- xml/cn/chapter3/section3/subsection5.xml | 1615 ++++++++++++++++++++++ 4 files changed, 2469 insertions(+), 1230 deletions(-) create mode 100644 xml/cn/chapter3/section3/subsection5.xml diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 973fef576..438522aca 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -32,7 +32,7 @@ const MAXLEN = Number(process.env.MAX_LEN) || 3000; // change to true to avoid calling openai api, useful for troubleshooting // chunking logic -const troubleshoot = false; +const troubleshoot = true; // Centralized logging to prevent duplicate messages const errorMessages = new Set(); @@ -80,7 +80,7 @@ async function translate(language: string, filePath: string): Promise { // Use the provided file path directly without modification const input_path = filePath; - assistant = await createAssistant(language, ai as any); + if (!troubleshoot) assistant = await createAssistant(language, ai as any); // Generate output path by replacing "/en/" with "/cn/" in the path const output_path = filePath.replace( @@ -91,7 +91,7 @@ async function translate(language: string, filePath: string): Promise { const translated: string = await recursivelyTranslate( language, input_path, - assistant.id + troubleshoot ? 0 : assistant.id ); // Ensure directory exists @@ -287,15 +287,6 @@ async function recursivelyTranslate( parser.on("opentag", node => { currentDepth++; - // If we're at depth 2, this is the start of a new segment. - if ( - node.name == "SCHEME" || - node.name == "SCHEMEINLINE" || - parser._parser.tag === "SCHEME" || - parser._parser.tag === "SCHEMEINLINE" - ) - return; - if (currentDepth === 2 || isRecording) { isRecording = true; currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; @@ -310,13 +301,6 @@ async function recursivelyTranslate( parser.on("text", text => { text = strongEscapeXML(text); - // ignore all scheme contents - if ( - parser._parser.tag == "SCHEME" || - parser._parser.tag == "SCHEMEINLINE" - ) - return; - if (isRecording) { currentSegment += text; } else { @@ -331,13 +315,7 @@ async function recursivelyTranslate( }); parser.on("closetag", tagName => { - if ( - tagName !== "SCHEME" && - tagName !== "SCHEMEINLINE" && - parser._parser.tag !== "SCHEME" && - parser._parser.tag !== "SCHEMEINLINE" && - isRecording - ) { + if (isRecording) { currentSegment += ``; } diff --git a/xml/cn/chapter1/section1/subsection4.xml b/xml/cn/chapter1/section1/subsection4.xml index 9be1c95bf..4c6c69d69 100644 --- a/xml/cn/chapter1/section1/subsection4.xml +++ b/xml/cn/chapter1/section1/subsection4.xml @@ -1,682 +1,3 @@ - - Compound Procedures - Functions - - - - We have identified in - - Lisp - JavaScript - - some of the elements that must appear in any powerful programming language: -
    -
  • - Numbers and arithmetic operations are primitive data and - - procedures. - functions. - -
  • -
  • - Nesting of combinations provides a means of combining operations. -
  • -
  • - Constant declarations that associate names with values provide a - limited means of abstraction. -
  • -
- Now we will learn about - - - proceduredefinition of - - - declaration of - - - - - procedure definitions, - - - function declarations, - - - a much more powerful abstraction technique by which a compound - operation can be given a name and then referred to as a unit. -
- - We begin by examining how to express the idea of - squaring. - We might say, - - - To square something, multiply it by itself. - - - To square something, take it times itself. - - - - The Scheme and JavaScript phrases differ a bit here, in order to better - match infix notation in JavaScript. - - This is expressed in our language as - - square - function (keyword) - keywordsfunctionfunction - square_definition - square_example - -(define (square x) (* x x)) - - -function square(x) { - return x * x; -} - - - - square_example - - (square 14) - - -square(14); - - - - - - We can understand this in the following way: - - - -(define (square x) (* x x)) -;; ^ ^ ^ ^ ^ ^ -;; To square something, multiply it by itself. - - -function square( x ) { return x * x; } -// ^ ^ ^ ^ ^ ^ ^ -// To square something, take it times itself. - - - - - - - \begin{flushleft}\normalcodesize - \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} - \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ - $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] - \normalsize To & \normalsize square & \normalsize something, & & \normalsize take &\normalsize it & \normalsize times & \normalsize itself. \\ - \end{tabular} - \end{flushleft} - - - We have here a - - - compound procedure - procedurecompound - compound procedure, - - - compound function - compound - compound function, - - - which has been given the name square. The - - procedure - function - - represents the operation of multiplying something by itself. The thing to - be multiplied is given a local name, x, - which plays the same role that a pronoun plays in natural language. - - - namingof procedures - procedurenaming (with define) - procedurecreating with define - - - namingof functions - naming (with function declaration) - creating with function declaration - - - Evaluating the - - - definition - - - declaration - - - creates this compound - - - procedure - - - function - - - and associates it with the name - syntactic formsfunction declaration - - function declaration - declarationfunctionof function (function) - square.Observe that there are two - different operations being combined here: we are creating the - - - procedure, - - - function, - - - and we are giving - it the name square. It is possible, indeed - important, to be able to separate these two notionsto create - - - procedures - - - functions - - - without naming them, and to give names to - - - procedures - - - functions - - - that have already been created. We will see how to do this in - section. - - - - - The general form of a procedure definition - - - The simplest form of a function declaration - - - is - - -(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) - - -function name(parameters) { return expression; } - - - The - - - nameprocedureof a procedure - procedurename of - $\langle \textit{name}\rangle$ - - - namefunctionof a function - name of - name - - - is a symbol to be associated with the - - - procedure - - - function - - - definition in the environment.Throughout this book, we will - notation in this bookitalic symbols in expression syntax - syntaxexpressionsof expressions, describing - describe the general syntax of expressions by using italic - symbols delimited by angle - bracketse.g., - - - $\langle \textit{name}\rangle$to - - - nameto - - - denote the slots in the expression to be filled in - when such an expression is actually used. - The - - - procedureformal parameters of - formal parameters - - - parameters of - parameters - - - - - $\langle \textit{formal parameters}\rangle$ - - - parameters - - - are the names used within the body of the - - - procedure - - - function - - - to refer to the - corresponding arguments of the - - - procedure. - - - function. - - - - - The - procedurebody of - body of a procedure - $\langle \textit{body} \rangle$ - is an expression - that will yield the value of - the procedure - application when the formal parameters are replaced by - the actual arguments to which the - procedure - is applied.More - sequence of expressionsproceduresin procedure body - generally, the body of the procedure can be a sequence of expressions. - In this case, the interpreter evaluates each expression in the - sequence in turn and returns the value of the final expression as the - value of the procedure application. - The $\langle \textit{name} \rangle$ - and the - $\langle \textit{formal parameters} \rangle$ - are grouped within - parenthesesprocedurein procedure definition - proceduredefinition of - parentheses, just as they would be in an actual call to the procedure - being defined. - - - The parameters - are grouped within - parenthesesfunctionin function declaration - parenthesesfunctionin function declaration - parentheses and separated by commas, as they will be in an application - of the function being declared. - return statement - return value - return (keyword) - syntactic formsreturn statement - keywordsreturnreturn - In the simplest form, the - body of - body of a function - body of a function declaration is a single - return statement,More - sequence of statementsfunctionsin function body - generally, the body of the function can be a sequence of statements. - In this case, the interpreter evaluates each statement in the sequence - in turn until a return statement determines the value of the - function application. - which consists of the keyword - return - followed by the return expression - that will yield the value of the function application, when the - - formal - - parameters are replaced by the actual arguments to which the function - is applied. Like constant declarations and expression statements, - return statements - semicolon (;)ending statement - declaration of - end with a semicolon. - - - - - - - Having defined square, - we can now use it: - - - Having declared square, - we can now use it in a - function application expression, which we turn into a statement - using a semicolon: - - - - square_definition - -(square 21) - - -441 - - -square(21); - - -441 - - - - - - - - - Since operator combinations are syntactically distinct from function - applications, the JavaScript version needs to explicitly spell out - the evaluation rules for function application here. This prepares - the ground for the substitution model in the next sub-section. - - Function applications areafter operator - combinationsthe second kind of combination of - expressions into larger expressions that we encounter. - The general form of a function application is - - -function-expression(argument-expressions) - - - where the - function expression - function-expression - of the application specifies - the function to be applied to the comma-separated - argument(s) - argument-expressions. - To evaluate a function application, the interpreter follows - evaluationof function application - function applicationevaluation of - a procedure - quite similar to the procedure for operator combinations described in - section. -
    -
  • To evaluate a function application, do the following: -
      -
    1. - Evaluate the subexpressions of the application, namely - the function expression and the argument expressions. -
    2. -
    3. - Apply the function that is the value of the function expression - to the values of the argument expressions. -
    4. -
    -
  • -
-
-
- - square_definition - -(square (+ 2 5)) - - -49 - - -square(2 + 5); - - -49 - - - - - Here, the argument expression is itself a compound expression, - the operator combination 2 + 5. - - - - square_square - 81 - square_definition - -(square (square 3)) - - -81 - - -square(square(3)); - - -81 - - - - - Of course function application expressions can also serve as argument expressions. - - -
- - - We can also use square - as a building block in defining other - - - procedures. - - - functions. - - - For example, $x^2 +y^2$ can be expressed as - - -(+ (square x) (square y)) - - -square(x) + square(y) - - - We can easily - - define - declare - - a - - - procedure - sum-of-squares - - - function - sum_of_squaresThe - way multi-part names such as - sum_of_squares are written affects - the readability of programs, and programming communities differ - on this. - camel case - According to the common JavaScript convention, called camel case, - the name would be - sumOfSquares. The convention - naming conventionssnake case - snake case - used in this book is called snake case, and was chosen - for its closer resemblance - to the convention used in the Scheme version of this book, where - hyphens play the role of our underscores. - - - that, given any two numbers as arguments, produces the - sum of their squares: - - sum_of_squares - sum_of_squares - 25 - sum_of_squares_example - square_definition - -(define (sum-of-squares x y) - (+ (square x) (square y))) - -(sum-of-squares 3 4) - - -function sum_of_squares(x, y) { - return square(x) + square(y); -} - - - - sum_of_squares_example - 25 - sum_of_squares - -(sum-of-squares 3 4) - - -25 - - -sum_of_squares(3, 4); - - -25 - - - Now we can use - - - sum-of-squares - - - sum_of_squares - - - as a building block in constructing further - - - procedures: - - - functions: - - - - f - f_example - 136 - sum_of_squares - -(define (f a) - (sum-of-squares (+ a 1) (* a 2))) - - -function f(a) { - return sum_of_squares(a + 1, a * 2); -} - - - - f_example - f - -(f 5) - - -136 - - -f(5); - - -136 - - - - - - - Compound - compound procedureused like primitive procedure - procedures are used in exactly the same way as primitive - procedures. Indeed, one could not tell by looking at the definition - of sum-of-squares given above whether - square was built into - the interpreter, like + and - *, - or defined as a compound procedure. - - - In addition to compound functions, any JavaScript environment provides - primitive - primitive function - primitive functions that are built into the interpreter or loaded - from libraries. - JavaScript environment used in this book - Besides the primitive functions provided by the operators, - the JavaScript environment used in this book includes - additional primitive functions - such as the function - math_log (primitive function) - math_logMath.log - math_log, - which computes the natural logarithm of its argument.Our - JavaScript environment includes all functions and constants of - ECMAScript's - Math object, - under the names math_$\ldots$. - ECMAScriptMathMath object - For example, ECMAScript's Math.log - is available as math_log. - The MIT Press - web page for this book includes the JavaScript package - sicp JavaScript package - JavaScript package sicp - sicp that provides these and all other - JavaScript functions that are considered primitive in the book. - - These additional primitive functions are used in exactly the same way as - compound functionused like primitive function - compound functions; evaluating the application - math_log(1) results in the number 0. - Indeed, 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. - - - -
+ \ No newline at end of file diff --git a/xml/cn/chapter2/section3/subsection2.xml b/xml/cn/chapter2/section3/subsection2.xml index 8667008f8..0f495a6cb 100644 --- a/xml/cn/chapter2/section3/subsection2.xml +++ b/xml/cn/chapter2/section3/subsection2.xml @@ -1,183 +1,278 @@ - -示例: 符号微分 - + + Example: Symbolic Differentiation + - 微分符号 - 符号微分 - 代数表达式求导 - - -作为符号操作的一个示例,以及进一步说明数据抽象,考虑一个设计 过程 函数 的情况,它执行代数表达式的符号微分。我们希望 过程 函数 以代数表达式和变量作为参数,并返回表达式关于该变量的导数。例如,如果 过程 函数 的参数是 $ax^2 + bx +c$ 和$x$,则 过程 函数 应返回 $2ax+b$。符号微分在 Lisp. 编程语言 Lisp.本书的原始版本使用编程语言 Scheme,Lisp 的一种方言。 中具有特殊的历史意义。它是开发符号操作计算机语言的推动示例之一。此外,它标志着研究线的开始,这种研究导致了用于符号数学工作的强大系统的发展,这些系统 目前正被越来越多的应用数学家和物理学家使用。 今天被应用数学家和物理学家常规使用。 - -在开发符号微分程序时,我们将遵循与开发部分的有理数系统时相同的数据抽象策略。也就是说,我们将首先定义一个微分算法,该算法作用于诸如和,积,变量等抽象对象,而不必担心这些对象应如何表示。只有在此之后,我们才会解决表示问题。 - - - - 抽象数据的微分程序 - - - - - -为了 - - -为了 - - -简单起见,我们将考虑一个非常简单的符号微分程序,该程序处理仅使用两个参数的加法和乘法运算构建的表达式。任何此类表达式的微分都可以通过应用以下求导规则规则化简规则来进行: - -\[ -\begin{array}{rll} -\dfrac{dc}{dx} & = & 0\quad \text{当 $c$ 为常数或与 $x$ 不同的变量时} \\[12pt] -\dfrac{dx}{dx} & = & 1 \\[12pt] -\dfrac{d(u+v)}{dx} & = & \dfrac{du}{dx}+\dfrac{dv}{dx} \\[12pt] -\dfrac{d(uv)}{dx} & = & u\left( \dfrac{dv}{dx}\right)+v\left( \dfrac{du}{dx}\right) -\end{array} -\] - - - -注意,后两个规则本质上是递归的。也就是说,为了获得和的导数,我们首先找到项的导数并将它们相加。每一项可能又是一个需要分解的表达式。逐渐分解为越来越小的部分,最终将产生一些要么是常数,要么是变量的部分,其导数将是$0$或$1$。 - - -为了在 过程 函数 中体现这些规则,我们像设计有理数实现时那样进行一些wishful thinking一厢情愿的想法。如果我们有一种表示代数表达式的方法,我们应该能够判断一个表达式是和、积、常量还是变量。我们应该能够提取表达式的各个部分。例如,对于和,我们希望能够提取加数(第一项)和被加数(第二项)。我们还应该能够从各个部分构造表达式。让我们假设我们已经有 过程 函数 来实现以下选择器、构造器和谓词: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -(variable? e) -is_variable(e) - - - -e是一个变量吗? -
- -(same-variable? v1 v2) - -is_same_variable(v1, v2) - - - -v1和v2是相同的变量吗? -
- -(sum? e) -is_sum(e) - - - -e是一个和吗? -
- -(addend e) -addend(e) - - - -和的加数e。 -
- -(augend e) -augend(e) - - - -和的被加数e。 -
- -(make-sum a1 a2) -make_sum(a1, a2) - - - -构造a1和a2的和。 -
- -(product? e) -is_product(e) - - - -e是一个积吗? -
- -(multiplier e) -multiplier(e) - - - -乘积e的乘数。 -
- -(multiplicand e) -multiplicand(e) - - - -乘积e的被乘数。 -
- -(make-product m1 m2) - -make_product(m1, m2) - - - -构造m1和m2的乘积。 -
-使用这些,以及原语谓词 - -使用这些,以及原语函数is_number -is_number (\textit{ns}) - -number?, -is_number, - -它识别数字,我们可以将微分规则表达为以下过程:函数: - + differentiationsymbolic + symbolic differentiation + algebraic expressiondifferentiating + + + As an illustration of symbol manipulation and a further illustration + of data abstraction, consider the design of a + + procedure + function + + that performs symbolic differentiation of algebraic expressions. We would + like the + + procedure + function + + to take as arguments an algebraic expression and a variable and to return + the derivative of the expression with respect to the variable. For example, + if the arguments to the + + procedure + function + + are $ax^2 + bx +c$ + and$x$, the + + procedure + function + + should return $2ax+b$. Symbolic differentiation + is of special historical significance in + + + Lisp. + + + the programming language Lisp.The original version of this + book used the programming language Scheme, a dialect of Lisp. + + + It was one of the + motivating examples behind the development of a computer language for + symbol manipulation. Furthermore, it marked the beginning of the line of + research that led to the development of powerful systems for symbolic + mathematical work, which are + + + currently being used by a growing number of + applied mathematicians and physicists. + + + today routinely used by applied mathematicians and physicists. + + + + + In developing the symbolic-differentiation program, we will follow the same + strategy of data abstraction that we followed in developing the + rational-number system of section. That + is, we will first define a differentiation algorithm that operates on + abstract objects such as sums, products, and + variables without worrying about how these are to be + represented. Only afterward will we address the representation problem. + + + + The differentiation program with abstract data + + + + + + In order to + + + To + + + keep things simple, we will consider a very simple + symbolic-differentiation program that handles expressions that are built up + using only the operations of addition and multiplication with two + arguments. Differentiation of any such expression can be carried out by + applying the following + differentiationrules for + reduction rules: + + \[ + \begin{array}{rll} + \dfrac{dc}{dx} & = & + 0\quad \text{for $c$ a constant or a variable different from $x$} \\[12pt] + \dfrac{dx}{dx} & = & 1 \\[12pt] + \dfrac{d(u+v)}{dx} & = & \dfrac{du}{dx}+\dfrac{dv}{dx} \\[12pt] + \dfrac{d(uv)}{dx} & = & u\left( \dfrac{dv}{dx}\right)+v\left( + \dfrac{du}{dx}\right) + \end{array} + \] + + + + Observe that the latter two rules are recursive in nature. That is, to + obtain the derivative of a sum we first find the derivatives of the terms + and add them. Each of the terms may in turn be an expression that needs to + be decomposed. Decomposing into smaller and smaller pieces will eventually + produce pieces that are either constants or variables, whose derivatives + will be either $0$ or + $1$. + + + To embody these rules in a + + procedure + function + + we indulge in a little + wishful thinking + wishful thinking, as we did in designing the rational-number implementation. + If we had a means for representing algebraic expressions, we should be able + to tell whether an expression is a sum, a product, a constant, or a + variable. We should be able to extract the parts of an expression. For a + sum, for example, we want to be able to extract the addend (first term) and + the augend (second term). We should also be able to construct expressions + from parts. Let us assume that we already have + + procedures + functions + + to implement the following selectors, constructors, and predicates: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + (variable? e) + is_variable(e) + + + + Is e a variable? +
+ + (same-variable? v1 v2) + + is_same_variable(v1, v2) + + + + Are v1 and + v2 the same variable? +
+ + (sum? e) + is_sum(e) + + + + Is e a sum? +
+ + (addend e) + addend(e) + + + + Addend of the sum e. +
+ + (augend e) + augend(e) + + + + Augend of the sum e. +
+ + (make-sum a1 a2) + make_sum(a1, a2) + + + + Construct the sum of a1 and + a2. +
+ + (product? e) + is_product(e) + + + + Is e a product? +
+ + (multiplier e) + multiplier(e) + + + + Multiplier of the product e. +
+ + (multiplicand e) + multiplicand(e) + + + + Multiplicand of the product e. +
+ + (make-product m1 m2) + + make_product(m1, m2) + + + + Construct the product of m1 and + m2. +
+ Using these, and the primitive predicate + is_number (primitive function) + is_number (\textit{ns}) + + number?, + is_number, + + which identifies numbers, we can express the differentiation rules as the + following + + procedure: + function: + + deriv (symbolic) deriv is_variable @@ -191,7 +286,7 @@ number?, multiplicand xyx3 [ '*', [ 'x', [ 'y', null ] ] ] - + (define (deriv exp var) (cond ((number? exp) 0) ((variable? exp) @@ -207,7 +302,7 @@ number?, (multiplicand exp)))) (else (error "unknown expression type -- DERIV" exp)))) - + function deriv(exp, variable) { return is_number(exp) @@ -227,208 +322,335 @@ function deriv(exp, variable) { : error(exp, "unknown expression type -- deriv"); } - -这个 -deriv - - 过程 函数 融合了完整的微分算法。由于它是用抽象数据表示的,所以无论我们选择如何表示代数表达式,只要我们设计出合适的选择器和构造器,它就能够起作用。这是我们接下来必须解决的问题。 -
- - -代数表达式的表示 - - - 代数表达式表示 - -我们可以想象许多使用列表结构来表示代数表达式的方法。例如,我们可以使用反映通常代数符号的符号列表来表示 -$ax+b$ -将其表示为 -列表(a * x + b). - -list("a", "*", "x", "+", "b"). - - - - 不过,一个特别简单的选择是使用与 Lisp 用于组合的相同的带括号的前缀表示法;亦即,将 $ax+b$ 表示为 (+ (* a x) b)。然后我们将 - - -然而,如果我们在表示中反映表达式的数学结构将更方便;亦即,将 $ax+b$ 表示为 list("+", list("*", "a", "x"), "b")。在操作数前放置一个二元运算符被称为 前缀表示法 前缀表示法,与 section 中介绍的中缀表示法形成对比。使用前缀表示法,我们的 - - -数据表示法如下: -
    -
  • -变量是 符号。 只是字符串。 它们由原语谓词 is_string (primitive function) is_string (\textit{ns}) symbol?: is_string: - -is_variable_example - (variable? 'xyz) - is_variable("xyz"); - - -is_variablefor algebraic expressions -is_variable -is_variable_example -true - (define (variable? x) (symbol? x)) - function is_variable(x) { return is_string(x); } - -
  • -
  • -两个变量是相同的如果 表示它们的符号是 eq?: 表示它们的字符串相等: - -is_same_variable_example - (same-variable? 'xyz 'xyz) - is_same_variable("xyz", "xyz"); - - -is_same_variable -is_same_variable -true -is_variable -is_same_variable_example - + + This deriv + + procedure + function + + incorporates the complete differentiation algorithm. Since it is expressed + in terms of abstract data, it will work no matter how we choose to + represent algebraic expressions, as long as we design a proper set of + selectors and constructors. This is the issue we must address next. + + + + Representing algebraic expressions + + + algebraic expressionrepresenting + + We can imagine many ways to use list structure to represent algebraic + expressions. For example, we could use lists of symbols that mirror the + usual algebraic notation, representing $ax+b$ as + + the list (a * x + b). + + list("a", "*", "x", "+", "b"). + + + + + However, one especially straightforward choice is to use the same + parenthesized prefix notation that Lisp uses for combinations; that + is, to represent $ax+b$ as + (+ (* a x) b). Then our + + + However, it will be more convenient if we reflect the mathematical + structure of the expression in the JavaScript value representing it; + that is, to represent $ax+b$ as + list("+", list("*", "a", "x"), "b"). + Placing a binary operator in front of its operands is called + prefix notation + prefix notation, + in contrast with the infix notation introduced in + section. With prefix notation, our + + + data representation for the differentiation problem is as follows: +
      +
    • + The variables are + + symbols. + just strings. + + They are identified by the primitive predicate + is_string (primitive function) + is_string (\textit{ns}) + + symbol?: + is_string: + + + + is_variable_example + +(variable? 'xyz) + + +is_variable("xyz"); + + + + is_variablefor algebraic expressions + is_variable + is_variable_example + true + +(define (variable? x) (symbol? x)) + + +function is_variable(x) { return is_string(x); } + + +
    • +
    • + Two variables are the same if the + + symbols representing them are + eq?: + + strings representing them are equal: + + + + is_same_variable_example + +(same-variable? 'xyz 'xyz) + + +is_same_variable("xyz", "xyz"); + + + + is_same_variable + is_same_variable + true + is_variable + is_same_variable_example + (define (same-variable? v1 v2) (and (variable? v1) (variable? v2) (eq? v1 v2))) - - + + function is_same_variable(v1, v2) { return is_variable(v1) && is_variable(v2) && v1 === v2; } - - -
    • -
    • -和与积被构造为列表: - -make_sum_example -make_sum - (make-sum (make-product 'x 3) (make-product 'y 'z)) - make_sum(make_product("x", 3), make_product("y", "z")); - head(tail(make_sum(make_product("x", 3), make_product("y", "z;)))); - - -make_sum -make_product -make_sum -make_sum_example -[ '*', [ 'x', [ 3, null ] ] ] - (define (make-sum a1 a2) (list '+ a1 a2)) (define (make-product m1 m2) (list '* m1 m2)) - function make_sum(a1, a2) { return list("+", a1, a2); } + + +
    • +
    • + Sums and products are constructed as lists: + + make_sum_example + make_sum + +(make-sum (make-product 'x 3) (make-product 'y 'z)) + + +make_sum(make_product("x", 3), make_product("y", "z")); + + +head(tail(make_sum(make_product("x", 3), make_product("y", "z")))); + + + + make_sum + make_product + make_sum + make_sum_example + [ '*', [ 'x', [ 3, null ] ] ] + +(define (make-sum a1 a2) (list '+ a1 a2)) +(define (make-product m1 m2) (list '* m1 m2)) + + +function make_sum(a1, a2) { return list("+", a1, a2); } -function make_product(m1, m2) { return list("*", m1, m2); } - -
    • -
    • -和是一个列表,其第一个元素是 符号+: 字符串"+": - -is_sum_example -make_sum - (sum? (make-sum 'x 3)) - is_sum(make_sum("x", 3)); - - -is_sum -is_sum -is_sum_example -true - (define (sum? x) (and (pair? x) (eq? (car x) '+))) - function is_sum(x) { return is_pair(x) && head(x) === "+"; } - -
    • -
    • -和的加数是和列表的第二项: - -addend_example -make_sum - (addend (make-sum 'x 3)) - addend(make_sum("x", 3)); - - -addend -addend -addend_example -'x' - (define (addend s) (cadr s)) - function addend(s) { return head(tail(s)); } - -
    • -
    • -和的被加数是和列表的第三项: - -augend_example -make_sum - (augend (make-sum 'x 3)) - augend(make_sum("x", 3)); - - -augend -augend -augend_example -3 - (define (augend s) (caddr s)) - function augend(s) { return head(tail(tail(s))); } - -
    • -
    • -积是一个列表,其第一个元素是 符号*: 字符串"*": - -is_product_example -make_sum - (is-product (make-product 'x 3)) - is_product(make_product("x", 3)); - - -is_product -is_product -true -is_product_example - (define (product? x) (and (pair? x) (eq? (car x) '*))) - function is_product(x) { return is_pair(x) && head(x) === "*"; } - -
    • -
    • -积的乘数是积列表的第二项: - -multiplier_example -make_sum - (multiplier (make-product 'x 3)) - multiplier(make_product("x", 3)); - - -multiplierselector -multiplier -multiplier_example -'x' - (define (multiplier p) (cadr p)) - function multiplier(s) { return head(tail(s)); } - -
    • -
    • -积的被乘数是积列表的第三项: - -multiplicand_example -make_sum - (multiplicand (make-product 'x 3)) - multiplicand(make_product("x", 3)); - - -multiplicand -multiplicand -multiplicand_example -3 - (define (multiplicand p) (caddr p)) - function multiplicand(s) { return head(tail(tail(s))); } - -
    • -
    -因此,我们只需将这些与由体现的算法结合起来 -deriv 为了拥有一个工作的符号微分程序。让我们看看它的一些行为示例: +function make_product(m1, m2) { return list("*", m1, m2); } + + +
  • +
  • + A sum is a list whose first element is the + + symbol +: + string "+": + + + + is_sum_example + make_sum + +(sum? (make-sum 'x 3)) + + +is_sum(make_sum("x", 3)); + + + + is_sum + is_sum + is_sum_example + true + +(define (sum? x) + (and (pair? x) (eq? (car x) '+))) + + +function is_sum(x) { + return is_pair(x) && head(x) === "+"; +} + + +
  • +
  • + The addend is the second item of the sum list: + + addend_example + make_sum + +(addend (make-sum 'x 3)) + + +addend(make_sum("x", 3)); + + + + addend + addend + addend_example + 'x' + +(define (addend s) (cadr s)) + + +function addend(s) { return head(tail(s)); } + + +
  • +
  • + The augend is the third item of the sum list: + + augend_example + make_sum + +(augend (make-sum 'x 3)) + + +augend(make_sum("x", 3)); + + + + augend + augend + augend_example + 3 + +(define (augend s) (caddr s)) + + +function augend(s) { return head(tail(tail(s))); } + + +
  • +
  • + A product is a list whose first element is the + + symbol *: + string "*": + + + + is_product_example + make_sum + +(is-product (make-product 'x 3)) + + +is_product(make_product("x", 3)); + + + + is_product + is_product + true + is_product_example + +(define (product? x) + (and (pair? x) (eq? (car x) '*))) + + +function is_product(x) { + return is_pair(x) && head(x) === "*"; +} + + +
  • +
  • + The multiplier is the second item of the product list: + + multiplier_example + make_sum + +(multiplier (make-product 'x 3)) + + +multiplier(make_product("x", 3)); + + + + multiplierselector + multiplier + multiplier_example + 'x' + +(define (multiplier p) (cadr p)) + + +function multiplier(s) { return head(tail(s)); } + + +
  • +
  • + The multiplicand is the third item of the product list: + + multiplicand_example + make_sum + +(multiplicand (make-product 'x 3)) + + +multiplicand(make_product("x", 3)); + + + + multiplicand + multiplicand + multiplicand_example + 3 + +(define (multiplicand p) (caddr p)) + + +function multiplicand(s) { return head(tail(tail(s))); } + + +
  • +
+ Thus, we need only combine these with the algorithm as embodied by + deriv in order to have a working + symbolic-differentiation program. Let us look at some examples of its + behavior: + deriv_example_8 deriv [ '+', [ 1, [ 0, null ] ] ] - + (deriv '(+ x 3) 'x) - + (+ 1 0) @@ -443,9 +665,9 @@ list("+", 1, 0) deriv_example_2 deriv [ '*', [ 'x', [ 0, null ] ] ] - + (deriv '(* x y) 'x) - + (+ (* x 0) (* 1 y)) @@ -463,9 +685,9 @@ list("+", list("*", "x", 0), list("*", 1 xyx3 deriv [ '*', [ 'x', [ 'y', null ] ] ] - + (deriv '(* (* x y) (+ x 3)) 'x) - + (+ (* (* x y) (+ 1 0)) (* (+ (* x 0) (* 1 y)) @@ -482,31 +704,54 @@ list("+", list("*", list("*", "x", " list("*", list("+", list("*", "x", 0), list("*", 1, "y")), list("+", "x", 3))) - -程序生成的答案是正确的;但是,它们是未经化简的。的确 - +
+ The program produces answers that are correct; however, they are + unsimplified. It is true that + \[ \begin{array}{lll} \dfrac{d(xy)}{dx} & = & x\cdot 0+1\cdot y \end{array} \] - -但是我们希望程序能够知道 -$x\cdot 0 = 0$, - $1\cdot y = y$ -以便使程序知道 -$0+y = y$ -第二个例子的答案应该只是 -y 正如第三个例子所示,当表达式变得复杂时,这成为一个严重的问题。
- -我们的问题很像我们在有理数实现中遇到的问题:代数表达式化简 代数表达式的化简 我们还没有将答案化简至最简形式。为了实现有理数的化简,我们只需要更改实现中的构造器和选择器。在这里,我们也可以采用类似的策略。我们不会改变 -deriv -完全不需要。相反,我们将改变 make-sum make_sum 这样如果两个加数都是数字, make-sum make_sum 将它们相加并返回它们的和。此外,如果其中一个加数是 0,那么 make-sum make_sum 将返回另一个加数。 - + + but we would like the program to know that + $x\cdot 0 = 0$, + $1\cdot y = y$, and + $0+y = y$. The answer for the second example + should have been simplyy. As the + third example shows, this becomes a serious issue when the expressions are + complex. + + + Our difficulty is much like the one we encountered with the rational-number + implementation: + algebraic expressionsimplifying + simplification of algebraic expressions + we havent reduced answers to simplest form. To + accomplish the rational-number reduction, we needed to change only the + constructors and the selectors of the implementation. We can adopt a similar strategy here. We wont change deriv at + all. Instead, we will change + + make-sum + make_sum + + so that if both summands are numbers, + + make-sum + make_sum + + will add them and return their sum. Also, if one of the summands is 0, + then + + make-sum + make_sum + + will return the other summand. + make_sum_example_2 - + (make-sum 2 3) - + make_sum(2, 3); @@ -517,13 +762,13 @@ make_sum(2, 3); make_sum_example_2 number_equal 5 - + (define (make-sum a1 a2) (cond ((=number? a1 0) a2) ((=number? a2 0) a1) ((and (number? a1) (number? a2)) (+ a1 a2)) (else (list '+ a1 a2)))) - + function make_sum(a1, a2) { return number_equal(a1, 0) @@ -535,11 +780,22 @@ function make_sum(a1, a2) { : list("+", a1, a2); } - 这使用了 过程 函数 =number?, number_equal, 它检查表达式是否等于给定数字: + + This uses the + + procedure + function + + + =number?, + number_equal, + + which checks whether an expression is equal to a given number: + number_equal_example - + (number_equal 3 3)) - + number_equal(3, 3); @@ -549,22 +805,29 @@ number_equal(3, 3); number_equal number_equal_example true - + (define (=number? exp num) (and (number? exp) (= exp num))) - + function number_equal(exp, num) { return is_number(exp) && exp === num; } - -同样,我们将更改 make-product make_product 来内置规则,即 0 乘以任何数都是 0,而 1 乘以任何数都是其自身: - + + Similarly, we will change + + make-product + make_product + + + to build in the rules that 0 times anything is 0 and 1 times anything is + the thing itself: + make_product_example_2 - + (make-product 2 3)) - + make_product(2, 3); @@ -575,14 +838,14 @@ make_product(2, 3); make_product_example_2 6 number_equal - + (define (make-product m1 m2) (cond ((or (=number? m1 0) (=number? m2 0)) 0) ((=number? m1 1) m2) ((=number? m2 1) m1) ((and (number? m1) (number? m2)) (* m1 m2)) (else (list '* m1 m2)))) - + function make_product(m1, m2) { return number_equal(m1, 0) || number_equal(m2, 0) @@ -609,7 +872,7 @@ function make_product(m1, m2) { is_product multiplier multiplicand - + (define (deriv exp var) (cond ((number? exp) 0) ((variable? exp) @@ -625,7 +888,7 @@ function make_product(m1, m2) { (multiplicand exp)))) (else (error "unknown expression type -- DERIV" exp)))) - + function deriv(exp, variable) { return is_number(exp) @@ -645,15 +908,15 @@ function deriv(exp, variable) { : error(exp, "unknown expression type -- deriv"); } - -以下是此版本在我们的三个示例上的工作方式: - + + Here is how this version works on our three examples: + here_is_how deriv_2 1 - + (deriv '(+ x 3) 'x) - + 1 @@ -668,9 +931,9 @@ deriv(list("+", "x", 3), "x"); why_y deriv_2 'y' - + (deriv '(* x y) 'x) - + y @@ -685,9 +948,9 @@ deriv(list("*", "x", "y"), "x"); deriv_list_times deriv_2 [ '*', [ 'x', [ 'y', null ] ] ] - + (deriv '(* (* x y) (+ x 3)) 'x) - + (+ (* x y) (* y (+ x 3))) @@ -700,25 +963,52 @@ head(tail(deriv(list("*", list("*", "x", "y&q list("+", list("*", "x", "y"), list("*", "y", list("+", "x", 3))) - -尽管这已经有了很大的改进,但第三个例子表明,要让程序将表达式简化成我们可以认为是最简的形式,还有很长的路要走。代数化简的问题很复杂,因为,除了其他原因外,最简形式可能因目的不同而不同。代数表达式化简 - - - -展示如何扩展基本微分器以处理更多种类的表达式。微分规则 例如,实现微分规则 - + + Although this is quite an improvement, the third example shows that there + is still a long way to go before we get a program that puts expressions + into a form that we might agree is simplest. The problem + of algebraic simplification is complex because, among other reasons, a + form that may be simplest for one purpose may not be for another. + algebraic expressionsimplifying + + + + Show how to extend the basic differentiator to handle more kinds of + expressions. + differentiationrules for + For instance, implement the differentiation rule + \[ \begin{array}{lll} \dfrac {d(u^{n})}{dx} & = & nu^{n-1}\left( \dfrac{du}{dx}\right) \end{array} \] - -通过向 -deriv -在程序中添加一个新子句并定义合适的 过程 函数 exponentiation?, is_exp, -base, exponent -并且定义合适的 make-exponentiation. make_exp. (您可以使用 符号** 字符串"**" 来表示幂运算。)内置这样的规则:任何数的 0 次幂为 1,任何数的 1 次幂为其自身。 - + + by adding a new clause to the deriv program + and defining appropriate + + procedures + functions + + + exponentiation?, + is_exp, + + base, exponent, + and + + make-exponentiation. + make_exp. + + (You may use + + the symbol ** + the string "**" + + + to denote exponentiation.) Build in the rules that anything raised to the + power 0 is 1 and anything raised to the power 1 is the thing itself. + deriv_expo @@ -775,8 +1065,8 @@ function deriv(exp, variable) { : error(exp, "unknown expression type -- deriv"); } - - + + @@ -787,14 +1077,16 @@ deriv(list("**", "x", 4), "x"); head(tail(head(tail(deriv(list("**", "x", 4), "x"))))); - - + + - -扩展微分程序以处理任意数量的(两个或更多)项的和与积。然后,上面的最后一个例子可以表示为 - + + Extend the differentiation program to handle sums and products of arbitrary + numbers of (two or more) terms. Then the last example above could be + expressed as + deriv_3 is_variable is_same_variable @@ -804,7 +1096,7 @@ head(tail(head(tail(deriv(list("**", "x", 4), "x") addend is_product multiplier - + ;; change the representation of terms ;; by defining make_sum, make_product, is_sum, is_product, etc @@ -823,7 +1115,7 @@ head(tail(head(tail(deriv(list("**", "x", 4), "x") (multiplicand exp)))) (else (error "unknown expression type -- DERIV" exp)))) - + // change the representation of terms // by defining augend and multiplicand @@ -851,47 +1143,49 @@ function deriv(exp, variable) { deriv_3_example deriv_3 - + (deriv '(* x y (+ x 3)) 'x) - + deriv(list("*", "x", "y", list("+", "x", 3)), "x"); - -尝试仅通过更改和与积的表示来实现这一点,而不改变 -deriv - -尝试仅通过更改和与积的表示来实现这一点,而不改变 过程 函数 完全不需要。例如, -addend -一个和的第一个项会是首项 -augend -的和是其余各项的和。 - - -deriv_3_solution_example - -deriv(list("*", "x", "y", list("+", "x", 3)), "x"); - - - - -deriv_3_solution -deriv_3 -deriv_3_solution_example - + + Try to do this by changing only the + representation for sums and products, without changing the + deriv + + procedure + function + + at all. For example, the addend of a sum would + be the first term, and the augend would be the + sum of the rest of the terms. + + + deriv_3_solution_example + + deriv(list("*", "x", "y", list("+", "x", 3)), "x"); + + + + + deriv_3_solution + deriv_3 + deriv_3_solution_example + (deriv '(* x y (+ x 3)) 'x) - - + + function augend(s) { return accumulate(make_sum, 0, tail(tail(s))); } function multiplicand(s) { return accumulate(make_product, 1, tail(tail(s))); } - - - - - &subsection1.1.1; - - - &subsection1.1.2; - - - &subsection1.1.3; - - - &subsection1.1.4; - - - &subsection1.1.5; - - - &subsection1.1.6; - - - &subsection1.1.7; - - - &subsection1.1.8; - - diff --git a/xml/cn/chapter1/section1/subsection3.xml b/xml/cn/chapter1/section1/subsection3.xml deleted file mode 100644 index 2485e22ed..000000000 --- a/xml/cn/chapter1/section1/subsection3.xml +++ /dev/null @@ -1,415 +0,0 @@ - - - - - combinationevaluation of - evaluationof a combination - - - operator combinationevaluation of - evaluationof operator combination - - - - Evaluating - - Combinations - Operator Combinations - - - - - - One of our goals in this chapter is to isolate issues about thinking - procedurally. As a case in point, let us consider that, in evaluating - - - operator - - - combinations, the interpreter is itself following a procedure. - - Due to the prominence of the keyword - function, we are generally replacing - references to "procedure/procedural" with references to - "function/functional". The above sentences are an exception; - the terms "thinking procedurally" and "procedure" are perhaps still - adequate for the JavaScript edition here. - -
    -
  • - To evaluate - - - a combination, - - - an operator combination, - - - do the following: -
      -
    1. Evaluate the - - - subexpressions - - - operand expressions - - - of the combination.
    2. -
    3. - - - Apply the - procedure - that is the value of the leftmost - subexpression (the operator) to the arguments that are the - values of the other subexpressions (the operands). - - - Apply the function that is denoted by - the operator to the arguments that are the values of - the operands. - - -
    4. -
    -
  • -
- - The Scheme version does not distinguish between operator and - application combinations. However, due to the infix notation, - the JavaScript version needs to describe slightly different - rules for those two. This section contains the rule for operator - combination, and section 1.1.5 introduces a new rule for function - application. - - Even this simple rule illustrates some important points about - processes in general. First, observe that the first step dictates - that in order to accomplish the evaluation process for a - combination we must first perform the evaluation process on each - operand of the combination. Thus, the evaluation rule is - recursion - recursive in nature; - that is, it includes, as one of its steps, the need to invoke the rule - itself.It may seem strange that the evaluation - rule says, as part of the first step, that we should evaluate the leftmost - element of a combination, since at this point that can only be an operator - such as + or * - representing a built-in primitive procedure such as addition or - multiplication. We will see later that it is useful to be able to work with - combinations whose operators are themselves compound expressions. - -
- - Notice how succinctly the idea of recursion can be used to express - recursionexpressing complicated process - what, in the case of a deeply nested combination, would otherwise be - viewed as a rather complicated process. For example, evaluating - - -(* (+ 2 (* 4 6)) - (+ 3 5 7)) - - -(2 + 4 * 6) * (3 + 12); - - - requires that the evaluation rule be applied to four different - combinations. We can obtain a picture of this process by - representing the combination in the form of a - operator combinationtreeas a tree - treecombination viewed as - tree, as shown in - - - figure. - - - figure. - - - Each combination is represented by a - node of a tree - node with - branch of a tree - branches corresponding to the operator and the - operands of the combination stemming from it. - The - terminal node of a tree - terminal nodes (that is, nodes with - no branches stemming from them) represent either operators or numbers. - Viewing evaluation in terms of the tree, we can imagine that the - values of the operands percolate upward, starting from the terminal - nodes and then combining at higher and higher levels. In general, we - shall see that recursion is a very powerful technique for dealing with - hierarchical, treelike objects. In fact, the percolate values - upward form of the evaluation rule is an example of a general kind - of process known as - tree accumulation - tree accumulation. - - - -
- - Tree representation, showing the value of each subcombination. - -
- - -
-
- - Tree representation, showing the value of each subexpression. - -
-
-
-
- - - - Next, observe that the repeated application of the first step brings - us to the point where we need to evaluate, not combinations, but - primitive expressions such as - - - numerals, built-in operators, or other names. - - - numerals or names. - - - We take care of the primitive cases - primitive expressionevaluation of - evaluationof primitive expression - by stipulating that -
    -
  • - the values of numerals are the numbers that they name, - and -
  • - - -
  • - the values of built-in operators are the machine - instruction sequences that carry out the corresponding operations, - and -
  • - -
    - - Operators are not values in JavaScript, so this item does not apply - in the JavaScript version. - -
  • - the values of - - - other - - - names are the objects associated - with those names in the environment. -
  • -
- - - We may regard the second rule as a special case of the third one by - stipulating that symbols such as + - and * are also included - in the global environment, and are associated with the sequences of - machine instructions that are their values. - - - The key point to - notice is the role of the - environmentcontextas context for evaluation - environment in determining the meaning of - the - - - symbols - - - names - - - in expressions. In an interactive language such as - - - Lisp, - - - JavaScript, - - - it is meaningless to speak of the value of an expression such as - - - (+ x 1) - - - x + 1 - - - without specifying any information about the environment - that would provide a meaning for the - - - symbol x (or even for the - symbol +). - - - name x. - - - As we shall see in chapter 3, the general notion of - the environment as providing a context in which evaluation takes place - will play an important role in our understanding of program execution. - - - combinationevaluation of - evaluationof a combination - - - operator combinationevaluation of - evaluationof operator combination - - -
- - Notice that the - evaluation rule given above does not handle - - - definewhy a special form - definitions. - - - constant declarationwhy a syntactic form - declarations. - - - For instance, evaluating - - - (define x 3) - - - const x = 3; - - - does not apply - - - define - - - an equality operator = - - - to two arguments, one - of which is the value of the - - - symbol - - - name - - - x and the other of which is - 3, since the purpose of the - - - define - - - declaration - - - is precisely to associate - x with a value. - (That is, - - - (define x 3) - - - const x = 3; - - - is not - a combination.) - - - - - Such exceptions to the general evaluation rule are called - special form - special forms. - Define - is the only example of a special form that we - have seen so far, but we will meet others shortly. - evaluationof special forms - Each special form - has its own evaluation rule. The various kinds of expressions (each - with its associated evaluation rule) constitute the - syntaxprogrammingof a programming language - syntax of the - programming language. In comparison with most other programming - languages, Lisp has a very simple syntax; that is, the evaluation rule - for expressions can be described by a simple general rule together - with specialized rules for a small number of special - forms. - Special syntactic forms that are simply convenient - alternative surface structures for things that can be written in more - uniform ways are sometimes called syntactic sugar, to use a - Perlis, Alan J.quips - Landin, Peter - syntactic sugar - semicolon - cancer of the semicolon - Pascal - LispPascal vs. - phrase coined by Peter Landin. In comparison with users of other - languages, Lisp programmers, as a rule, are less concerned with - matters of syntax. (By contrast, examine any Pascal manual and notice - how much of it is devoted to descriptions of syntax.) This disdain - for syntax is due partly to the flexibility of Lisp, which makes it - easy to change surface syntax, and partly to the observation that many - convenient syntactic constructs, which make the language - less uniform, end up causing more trouble than they are worth when - programs become large and complex. In the words of Alan Perlis, - Syntactic sugar causes cancer of the semicolon. - - - - - The letters in const are - rendered in bold to indicate that it - - - The word const - - is a - keyword - keyword in JavaScript. Keywords carry a - particular meaning, and thus cannot be used as names. A keyword or a - combination of keywords in a statement instructs the JavaScript - interpreter to treat the statement in a special way. Each such - syntactic form - syntactic form has its own evaluation rule. The - various kinds of statements and expressions (each with its associated - evaluation rule) constitute the - syntaxprogrammingof a programming language - syntax of the programming language. - - - - -
diff --git a/xml/cn/chapter1/section1/subsection4.xml b/xml/cn/chapter1/section1/subsection4.xml deleted file mode 100644 index 4c6c69d69..000000000 --- a/xml/cn/chapter1/section1/subsection4.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/xml/cn/chapter2/section4/section4.xml b/xml/cn/chapter2/section4/section4.xml deleted file mode 100644 index bb3266302..000000000 --- a/xml/cn/chapter2/section4/section4.xml +++ /dev/null @@ -1,177 +0,0 @@ -
- 抽象数据的多重表示 - - - - - - 数据抽象 - - - 我们引入了数据抽象,这是一种用于构建系统的方法,使程序的大部分可以独立于实现程序操作的数据对象的选择之外进行指定。例如,我们在 - section看到如何将设计使用有理数的程序的任务与用计算机语言实现有理数的任务分开 - 的基本机制来构建复合数据。关键思路是构建一个抽象屏障 - ——在这种情况下,有理数的选择器和构造器 - - (make-rat, - (make_rat, - - numer, - denom )将有理数的使用方式与其底层表示的列表结构隔离开来。类似的抽象屏障将执行有理算术的 - - 过程 - 函数 - - 的细节隔离开来 - - (add-rat, - (add_rat, - - - sub-rat, - sub_rat, - - - mul-rat, - mul_rat, - - 和 - - div-rat) - div_rat) - - 与使用有理数的高层 - - 过程 - 函数 - - 隔离开。生成的程序具有在figure中显示的结构。 - - - 这些数据抽象屏障是控制复杂性的强大工具。通过隔离数据对象的底层表示,我们可以将设计大型程序的任务划分为可以单独执行的较小任务。但这种数据抽象还不够强大,因为并非总是有意义地谈论数据对象的底层表示。 - - - 一方面,一个数据对象可能有不止一种有用的表示,我们可能希望设计能够处理多种表示形式的系统。举个简单的例子,复数可以用两种几乎等效的方式表示:直角坐标形式(实部和虚部)和极坐标形式(模和角)。有时直角坐标形式更合适,有时极坐标形式更合适。事实上,完全可以想象一个系统,其中复数可以以这两种方式表示,并且用于操作复数的 - - 过程 - 函数 - - 可以处理任一表示形式。 - - - 更重要的是,编程系统通常是由多人在长时间内设计的,并且受到随时间变化的需求的影响。在这样的环境中,不可能所有人事先就数据表示的选择达成一致。因此,除了将表示与使用隔离的数据抽象屏障外,我们还需要将不同设计选择彼此隔离并允许不同选择在单个程序中共存的抽象屏障。此外,由于大型程序通常是通过组合 - - 预先存在的 - 预先存在的 - - 模块而创建,这些模块是在隔离中设计的,因此我们需要允许程序员将模块 - 加性 - 加性地合并到更大系统中的约定,也就是说,不需要重新设计或重新实现这些模块。 - - - 在本节中,我们将学习如何处理程序的不同部分可能采用不同表示的数据。这需要构建通用 - 过程函数 - 通用 - 通用 - 过程函数过程函数 - ,这些过程或函数可以对可能以多种方式表示的数据进行操作。我们构建通用 - - 过程 - 函数 - - 的主要技术是使用带有类型标签类型标签的数据对象,即包含有关如何处理信息的数据对象。我们还将讨论 - 数据导向编程 - 数据导向编程,这是一种强大且便捷的实现策略,用于通过通用操作在构建系统时进行加性组合。 - - - 我们从简单的复数示例开始。我们将看到类型标签和数据导向风格如何使我们能够为复数设计独立的直角坐标和极坐标表示,同时保持抽象的概念【38:18†cn.txt】。 -复数算术 - 算术在复数上 - 复数 - 数据对象。 - 我们将通过定义算术【50:3†cn.txt】 - - procedures - 函数 - - 对于复数 - - (add-complex, - (add_complex, - - - - sub-complex, - sub_complex, - - - - mul-complex, - mul_complex, - - - 和 - - div-complex) - div_complex) - - - 根据通用选择器访问复数的部分,这与数字的表示方式无关。生成的复数系统,如图中所示【30:15†cn.txt】。 - - - figure, - - - figure, - - - 包含两种不同类型的 - - 在复数系统中的抽象屏障【80:0†cn.txt】 - - 抽象屏障。 - 水平 - 抽象屏障 - 起到与 - figure 中相同的作用 - 。它们将高层操作与低层表示隔离开。此外,还有一个垂直屏障,使我们能够单独设计和安装替代表示。 - - -
-
- - 复数系统中的数据抽象屏障 。 - - -
- - -
-
- - 复数系统中的数据抽象屏障 。 - - -
-
-
-
- - 在section中,我们将展示如何使用类型标签和数据导向风格来开发一个通用算术包。这提供了 - - 过程 - 函数 - - (add,mul,等等)可以用来操作各种数字,并且当需要数字的新类型时可以轻松扩展。在section中,我们将展示如何在执行符号代数的系统中使用通用算术【96:2†cn.txt】。 - - - - &subsection2.4.1; - - - &subsection2.4.2; - - - &subsection2.4.3; - -
diff --git a/xml/cn/chapter2/section4/subsection1.xml b/xml/cn/chapter2/section4/subsection1.xml deleted file mode 100644 index 4668b8046..000000000 --- a/xml/cn/chapter2/section4/subsection1.xml +++ /dev/null @@ -1,362 +0,0 @@ - - - - 复数的表示 - - - - 复数直角坐标 vs. 极坐标形式 - - - 我们将开发一个系统,用于对复数 - 执行算术运算,作为一个简单但不现实的程序示例,该程序使用通用运算。我们首先讨论复数作为有序对的两种可能表示形式:直角坐标(实部和虚部)和极坐标(模长和角度)。在实际计算系统中,由于直角坐标和极坐标之间转换的舍入误差,直角坐标形式大多数时候比极坐标形式更可取。这就是为什么复数示例是不现实的。然而,它为使用通用运算设计系统提供了清楚的说明,并为本章后面开发的更重要的系统提供了良好的介绍。 Section - 将展示如何通过使用类型标签和通用运算,使两种表示形式可以在一个系统中共存。 - - - 类似有理数,复数自然表示为有序对。复数集可视为一个具有两个正交轴的二维空间,一个是实数轴,另一个是虚数轴。(见 - figure。)从这一视角来看,复数$z=x+iy$(其中 - $i^{2} =-1$)可以看作平面中一个点,其实部坐标为$x$,虚部坐标为$y$。在这种表示中,复数的加法归结为坐标的加法: - - \[ - \begin{array}{lll} - \mbox{实部}(z_{1}+z_{2}) & = & - \mbox{实部}(z_{1})+\mbox{实部}(z_{2}) \\[1ex] - \mbox{虚部}(z_{1} +z_{2}) & = & - \mbox{虚部}(z_1)+\mbox{虚部}(z_2) - \end{array} - \] - - - - 当乘复数时,更自然的是考虑将复数表示为极坐标形式,作为幅度和角度($r$ 和 $A$,参见 - figure)。两个复数的乘积是一个通过一个复数的长度来拉伸另一个复数并通过另一个的角度旋转得到的向量: - - \[ - \begin{array}{lll} - \mbox{模长}(z_{1}\cdot z_{2}) & = & - \mbox{模长}(z_{1})\cdot\mbox{模长}(z_{2})\\[1ex] - \mbox{角度}(z_{1}\cdot z_{2}) & = & - \mbox{角度}(z_{1})+\mbox{角度}(z_{2}) - \end{array} - \] - - - - 因此,复数有两种不同的表示形式,适用于不同的运算。然而,从使用复数写程序的人的角度来看,数据抽象原则建议无论计算机使用哪种表示形式,所有用于操作复数的运算都应可以使用。例如,能够找到用直角坐标指定的复数的模长通常是有用的。同样,能够确定用极坐标指定的复数的实部通常是有用的。 -
-
- 复数作为平面中的点。 - -
-
- - - 为了设计这样的系统,我们可以遵循与在 有理数包 - 中设计有理数包相同的数据抽象策略。假设复数操作是通过四个选择器实现的: - - 实部, - 实部, - - - 虚部, - 虚部, - - - 模长, - 模长, - - 和 -angle . Also assume that we have two - - 过程 - 函数 - - for constructing complex numbers: - - make-from-real-imag - - make_from_real_imag - - - 返回具有指定实部和虚部的复数,以及 - - - make-from-mag-ang - - - make_from_mag_ang - - - 返回具有指定幅度和角度的复数。这些 - - 过程 - 函数 - - 具有这样一个属性,对于任何复数 - z , both - - -(make-from-real-imag (real-part z) (imag-part z)) - - -make_from_real_imag(real_part(z), imag_part(z)); - - - 和 - - -(make-from-mag-ang (magnitude z) (angle z)) - - -make_from_mag_ang(magnitude(z), angle(z)); - - - 生成等于的复数 -z. - - - - 使用这些构造函数和选择器,我们可以像在节 中为有理数所做的那样,使用构造函数和选择器指定的抽象数据来实现复数的算术运算。如上面的公式所示,我们可以在实部和虚部的基础上对复数进行加减法运算,而在模长和角度的基础上进行复数的乘除法运算: - - add_complex - sub_complex - mul_complex - div_complex - complex_number_calculation - -(define (add-complex z1 z2) - (make-from-real-imag (+ (real-part z1) (real-part z2)) - (+ (imag-part z1) (imag-part z2)))) - -(define (sub-complex z1 z2) - (make-from-real-imag (- (real-part z1) (real-part z2)) - (- (imag-part z1) (imag-part z2)))) - -(define (mul-complex z1 z2) - (make-from-mag-ang (* (magnitude z1) (magnitude z2)) - (+ (angle z1) (angle z2)))) - -(define (div-complex z1 z2) - (make-from-mag-ang (/ (magnitude z1) (magnitude z2)) - (- (angle z1) (angle z2)))) - - -function add_complex(z1, z2) { - return make_from_real_imag(real_part(z1) + real_part(z2), - imag_part(z1) + imag_part(z2)); -} -function sub_complex(z1, z2) { - return make_from_real_imag(real_part(z1) - real_part(z2), - imag_part(z1) - imag_part(z2)); -} -function mul_complex(z1, z2) { - return make_from_mag_ang(magnitude(z1) * magnitude(z2), - angle(z1) + angle(z2)); -} -function div_complex(z1, z2) { - return make_from_mag_ang(magnitude(z1) / magnitude(z2), - angle(z1) - angle(z2)); -} - - - - - 为了完成复数包,我们必须选择一种表示方式,并且必须用原语数和原语列表结构实现构造函数和选择器。有两种明显的方法可以做到这一点:我们可以将复数以直角坐标形式表示为一个对(实部,虚部),或者以极坐标形式表示为一个对(模长,角度)。我们该选择哪一种? - - - - 为了使不同的选择具体化,假设有两个程序员,Ben Bitdiddle 和 Alyssa P. Hacker,他们正在独立设计复数系统的表示。 - Ben 选择用复数直角坐标表示 - 将复数表示为直角坐标形式。选择这种方式后,选择复数的实部和虚部变得很简单,构造具有给定实部和虚部的复数也是如此。 - 为了找到模长和角度,或者构造一个具有给定模长和角度的复数,他使用三角关系 - - \[ - \begin{array}{lllllll} - x & = & r\ \cos A & \quad \quad \quad & r & = & \sqrt{x^2 +y^2} \\ - y & = & r\ \sin A & & A &= & \arctan (y,x) - \end{array} - \] - - 将实部和虚部关联起来的 ( -$x$, - $y$ ) 与模长和角度 - $(r, A)$. 反正切函数 - 在这里 - - - 通过 Schemes - 反正切 - math_atan2 (原语函数) - math_atan2Math.atan2 - atan 过程计算, - - - 通过 JavaScripts - 反正切 - math_atan2 (原语函数) - math_atan2Math.atan2 - math_atan2 函数计算, - - - 被定义为接受两个参数 - $y$和 $x$ - 并返回其正切为 $y/x$ 的角度。 - 参数的符号决定了角度的象限。 - 因此,Ben的表示方式是通过以下选择器 - 和构造函数给出的: - - real_partrectangular representation - imag_partrectangular representation - magnituderectangular representation - anglerectangular representation - make_from_real_imagrectangular representation - make_from_mag_angrectangular representation - make_complex_number1 - complex_number_calculation - square_definition - make_complex_number_example - -3 - -(define (real-part z) (car z)) - -(define (imag-part z) (cdr z)) - -(define (magnitude z) - (sqrt (+ (square (real-part z)) (square (imag-part z))))) - -(define (angle z) - (atan (imag-part z) (real-part z))) - -(define (make-from-real-imag x y) (cons x y)) - -(define (make-from-mag-ang r a) - (cons (* r (cos a)) (* r (sin a)))) - - -function real_part(z) { return head(z); } - -function imag_part(z) { return tail(z); } - -function magnitude(z) { - return math_sqrt(square(real_part(z)) + square(imag_part(z))); -} -function angle(z) { - return math_atan2(imag_part(z), real_part(z)); -} -function make_from_real_imag(x, y) { return pair(x, y); } - -function make_from_mag_ang(r, a) { - return pair(r * math_cos(a), r * math_sin(a)); -} - - - - - make_complex_number_example - -const my_co_num_1 = make_from_real_imag(2.5, -0.5); -const my_co_num_2 = make_from_real_imag(2.5, -0.5); - -const result = add_complex(my_co_num_1, - mul_complex(my_co_num_2, - my_co_num_2)); - -imag_part(result); - - - - - - - 相比之下,Alyssa 选择将复数表示为 - 复数极坐标表示 极坐标形式。 - - 对她来说,选择模长和角度是直接的,但她必须使用 - 三角关系 三角关系来获取实部和虚部。 - Alyssa的表示是: - - real_partpolar representation - imag_partpolar representation - magnitudepolar representation - anglepolar representation - make_from_real_imagpolar representation - make_from_mag_angpolar representation - make_complex_number2 - complex_number_calculation - square_definition - make_complex_number_example - -3 - -(define (real-part z) - (* (magnitude z) (cos (angle z)))) - -(define (imag-part z) - (* (magnitude z) (sin (angle z)))) - -(define (magnitude z) (car z)) - -(define (angle z) (cdr z)) - -(define (make-from-real-imag x y) - (cons (sqrt (+ (square x) (square y))) - (atan y x))) - -(define (make-from-mag-ang r a) (cons r a)) - - -function real_part(z) { - return magnitude(z) * math_cos(angle(z)); -} -function imag_part(z) { - return magnitude(z) * math_sin(angle(z)); -} -function magnitude(z) { return head(z); } - -function angle(z) { return tail(z); } - -function make_from_real_imag(x, y) { - return pair(math_sqrt(square(x) + square(y)), - math_atan2(y, x)); -} -function make_from_mag_ang(r, a) { return pair(r, a); } - - - - - 数据抽象的规范确保相同的 - - - add-complex, - - - add_@complex, - - - - - sub-complex, - - - sub_complex, - - - - - mul-complex, - - - mul_complex, - - - 和 - - - div-complex - - - div_complex - - - 实现能与 Ben 以及 Alyssa 的表示方式一起工作。 - -
diff --git a/xml/cn/chapter2/section5/section5.xml b/xml/cn/chapter2/section5/section5.xml deleted file mode 100644 index 221cf842d..000000000 --- a/xml/cn/chapter2/section5/section5.xml +++ /dev/null @@ -1,103 +0,0 @@ -
- 通用操作的系统 - - - - - - 上一节中,我们看到如何设计可以以多种方式表示数据对象的系统。关键是通过通用接口将指定数据操作的代码链接到多个表示 - - 过程。 - 函数。 - 现在我们将看到如何使用相同的思路,不仅定义在不同表示之间通用的操作,还定义在不同类型参数上的 - 算术通用 - 通用操作。我们已经看到几种不同的算术运算包:原始算术 ( 【12:10†cn.txt】) 【4:0†source】+, - -, *, - / ) 内建于我们的语言中, 有理数运算 - - (add_rat, - (add_rat, - - - sub_rat, - sub_rat, - - - mul_rat, - mul_rat, - - - div_rat) - div_rat) - - 章节,以及在我们实现的复数算术中 - 章节。现在我们将使用数据导向技术来构建一个包含我们已构建的所有算术包的算术运算包。 - - - - - 图 - - - 图 - - - 显示了我们将要构建的系统结构。注意 - 抽象屏障在通用算术系统中 - 抽象屏障。从使用数字的人的角度来看,这是一个单一的 - - 过程 - 函数 - - add 运行在提供的任何数值上。 - - add - 函数 add - - - 是一个通用接口的一部分,该接口允许通过通用程序访问独立的普通算术、有理算术和复数算术包, -任何单独的算术包(例如复数包)本身可以通过通用 - - 过程 - 函数 - - (例如 - - add-complex) - add_complex) - - - 访问,这些操作结合了为不同表示(如直角和极坐标)设计的包。此外,系统结构是可加的,因此可以单独设计各个算术包并将它们组合以生成通用算术系统。 - 消息传递 - - -
-
- 通用算术系统。 - -
- - -
-
- - 通用 - 通用算术操作系统结构 - 算术系统。 - - -
-
-
-
- - - &subsection2.5.1; - - - &subsection2.5.2; - - - &subsection2.5.3; - -
diff --git a/xml/cn/chapter2/section5/subsection2.xml b/xml/cn/chapter2/section5/subsection2.xml deleted file mode 100644 index feab20db6..000000000 --- a/xml/cn/chapter2/section5/subsection2.xml +++ /dev/null @@ -1,899 +0,0 @@ - - 不同类型数据的组合 - - - - 我们已经看到如何定义一个统一的算术系统,该系统包含普通数、复数、有理数和我们可能决定发明的任何其他类型的数字,但我们忽略了一个重要问题。到目前为止,我们定义的操作将不同的数据类型视为完全独立的。因此,添加两个普通数字或两个复数等是有单独的软件包。我们尚未考虑的是定义跨类型边界的运算的意义,如将复数与普通数相加。我们非常费尽心思地在程序的各个部分之间设置屏障,以便它们可以独立开发和理解。我们希望以某种精心控制的方式引入跨类型运算,以便在不严重违反模块边界的情况下支持它们。 - 一种处理 操作跨类型 跨类型操作 类型跨类型操作 跨类型操作的方法是为每种可能的、操作有效的数据类型组合设计不同的 过程 函数。例如,我们可以扩展复数包,以便它提供一个 过程 函数 用于将复数加到普通数,并使用标签 (complex scheme-number)list("complex", "javascript_number"):我们还需要提供一个几乎相同的 过程 函数 来处理类型

(scheme_number complex) list("javascript_number", "complex")
- add_complex_to_javascript_number_example - -const c = make_complex_from_real_imag(4, 3); -const n = make_javascript_number(7); - -add(c, n); - - - - add_complex_to_javascript_num - add_complex_to_javascript_number - install_javascript_number_package_usage - install_complex_package_usage - add_complex_to_javascript_number_example - [ 'complex', [ 'rectangular', [ 11, 3 ] ] ] - -;; to be included in the complex package -(define (add-complex-to-schemenum z x) - (make-from-real-imag (+ (real-part z) x) - (imag-part z))) - -(put 'add '(complex scheme-number) - (lambda (z x) (tag (add-complex-to-schemenum z x)))) - - -// to be included in the complex package -function add_complex_to_javascript_num(z, x) { - return make_complex_from_real_imag(real_part(z) + x, imag_part(z)); -} -put("add", list("complex", "javascript_number"), - (z, x) => tag(add_complex_to_javascript_num(z, x))); - - -
- 这种技术有效,但非常繁琐。在这样的系统中,引入新类型的成本不仅仅是为该类型构建 过程 函数 的软件包,还包括实现跨类型操作的 过程 函数 的构建和安装。这可能会比定义该类型自身操作所需的代码量更多。该方法还影响了我们以加法方式组合独立包的能力,或至少限制了单个包的实现者需要考虑其他包的程度。例如,在上面的例子中,处理复数和普通数的混合操作似乎合理地应由复数包负责。然而,将有理数和复数组合可能由复数包、有理数包或某个使用这两个包提取操作的第三方包完成。在许多包和许多跨类型操作的系统设计中,对包之间责任划分制定一致的策略可能是一个艰巨的任务。 - - 强制类型转换 - - 强制类型转换 - 在完全不相关的操作作用于完全不相关的类型的情况下,实现显式的跨类型操作虽然繁琐,但已是最佳方案。幸运的是,通常我们可以通过利用类型系统中潜在的附加结构做得更好。通常,不同的数据类型并不是完全独立的,可能存在某种方法可以将一种类型的对象视为另一种类型的对象。这个过程被称为强制类型转换。例如,如果要求我们将一个普通数与一个复数进行算术组合,我们可以将普通数视为虚部为零的复数。这将问题转换为组合两个复数的问题,可以通过复数算术包以常规方式处理。 - 一般来说,我们可以通过设计 强制类型转换过程函数 强制类型转换 过程 函数 来实现该想法,将一种类型的对象转换为另一种类型的等效对象。以下是一个典型的强制类型转换 过程, 函数, 将给定的普通数字转换为具有该实部和零虚部的复数: - javascript_number_to_complex - javascript_number_to_complex - -(define (scheme-number->complex n) - (make-complex-from-real-imag (contents n) 0)) - - -function javascript_number_to_complex(n) { - return make_complex_from_real_imag(contents(n), 0); -} - - - 强制类型转换用于强制转换 强制类型转换 我们将在一个特殊的强制类型转换表中安装这些强制类型转换 过程 函数 ,按这两种类型的名称进行索引: - put_get_coercion - -(define coercion-list '()) - -(define (clear-coercion-list) - (set! coercion-list '())) - -(define (put-coercion type1 type2 item) - (if (get-coercion type1 type2) coercion-list - (set! coercion-list - (cons (list type1 type2 item) - coercion-list)))) - -(define (get-coercion type1 type2) - (define (get-type1 listItem) - (car listItem)) - (define (get-type2 listItem) - (cadr listItem)) - (define (get-item listItem) - (caddr listItem)) - (define (get-coercion-iter list type1 type2) - (if (null? list) #f - (let ((top (car list))) - (if (and (equal? type1 (get-type1 top)) - (equal? type2 (get-type2 top))) (get-item top) - (get-coercion-iter (cdr list) type1 type2))))) - (get-coercion-iter coercion-list type1 type2)) - - -let coercion_list = null; - -function clear_coercion_list() { - coercion_list = null; -} - -function put_coercion(type1, type2, item) { - if (is_null(get_coercion(type1, type2))) { - coercion_list = pair(list(type1, type2, item), - coercion_list); - } else { - return coercion_list; - } -} - -function get_coercion(type1, type2) { - function get_type1(list_item) { - return head(list_item); - } - function get_type2(list_item) { - return head(tail(list_item)); - } - function get_item(list_item) { - return head(tail(tail(list_item))); - } - function get_coercion_iter(items) { - if (is_null(items)) { - return undefined; - } else { - const top = head(items); - return equal(type1, get_type1(top)) && - equal(type2, get_type2(top)) - ? get_item(top) - : get_coercion_iter(tail(items)); - } - } - return get_coercion_iter(coercion_list); -} - - - - put_coercion_usage - put_get_coercion - javascript_number_to_complex - install_complex_package_usage - put_coercion_usage_example - put_get_coercion - - -(put-coercion 'scheme-number 'complex scheme-number->complex) - - -put_coercion("javascript_number", "complex", - javascript_number_to_complex); - - - - put_coercion_usage_example - -get_coercion("javascript_number", "complex"); - - (我们假设有 put-coercion put_coercion get-coercion get_coercion 过程 函数 可用于操作此表。) 通常,表中的某些槽位将为空,因为通常不能将每种类型的任意数据对象强制转换为所有其他类型。例如,无法将任意复数转换为普通数,因此不会包含一般的 complex->scheme-number complex_to_javascript_number 过程 函数 在表中。 - 一旦强制类型转换表建立,我们可以通过修改第节的 apply-generic apply_generic 过程 函数 以一种统一的方式处理强制类型转换。当要求应用一个操作时,我们首先检查操作是否对参数的类型定义,就像以前一样。如果是,我们将调度到操作与类型表中找到的 过程 函数 。否则,我们尝试强制类型转换。为简单起见,我们仅考虑存在两个参数的情况。请参见练习了解一般情况。 我们检查强制类型转换表,以查看是否可以将第一种类型的对象强制转换为第二种类型。如果可以,我们进行强制类型转换后再尝试操作。如果第一种类型的对象通常不能被转换为第二种类型,则我们反过来尝试看看是否可以将第二个参数转换为第一个参数的类型。最后,如果没有已知的方法能将任一类型转换为另一种类型,我们就放弃。以下是 过程: 函数: apply_generic带强制转换 - base_operation_table - -// operation_table, put and get -// from chapter 3 (section 3.3.3) -function assoc(key, records) { - return is_null(records) - ? undefined - : equal(key, head(head(records))) - ? head(records) - : assoc(key, tail(records)); -} -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)); - if (is_undefined(record)) { - return undefined; - } else { - return 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 - : "undefined operation -- table"; - } - return dispatch; -} -const operation_table = make_table(); -const get = operation_table("lookup"); -const put = operation_table("insert"); - -// In Source, most functions have a fixed number of arguments. -// (The function list is the only exception, to this so far.) -// The function apply_in_underlying_javascript allows us to -// apply any given function fun to all elements of the argument -// list args, as if they were separate arguments -function apply(fun, args) { - return apply_in_underlying_javascript(fun, args); -} -function add(x, y) { - return apply_generic("add", list(x, y)); -} -function sub(x, y) { - return apply_generic("sub", list(x, y)); -} -function mul(x, y) { - return apply_generic("mul", list(x, y)); -} -function div(x, y) { - return apply_generic("div", list(x, y)); -} - -function attach_tag(type_tag, contents) { - return pair(type_tag, contents); -} -function type_tag(datum) { - return is_pair(datum) - ? head(datum) - : error(datum, "bad tagged datum -- type_tag"); -} -function contents(datum) { - return is_pair(datum) - ? tail(datum) - : error(datum, "bad tagged datum -- contents"); -} - - - - javascript_number_package - base_operation_table - -function install_javascript_number_package() { - function tag(x) { - return attach_tag("javascript_number", x); - } - put("add", list("javascript_number", "javascript_number"), - (x, y) => tag(x + y)); - put("sub", list("javascript_number", "javascript_number"), - (x, y) => tag(x - y)); - put("mul", list("javascript_number", "javascript_number"), - (x, y) => tag(x * y)); - put("div", list("javascript_number", "javascript_number"), - (x, y) => tag(x / y)); - put("make", "javascript_number", - x => tag(x)); - return "done"; -} -install_javascript_number_package(); - -function make_javascript_number(n) { - return get("make", "javascript_number")(n); -} - - - - complex_number_package - base_operation_table - -// generic selector functions for complex numbers - -function real_part(z) { - return apply_generic("real_part", list(z)); -} -function imag_part(z) { - return apply_generic("imag_part", list(z)); -} -function magnitude(z) { - return apply_generic("magnitude", list(z)); -} -function angle(z) { - return apply_generic("angle", list(z)); -} -function square(x) { - return x * x; -} - -function install_rectangular_package() { - function real_part(z) { return head(z); } - function imag_part(z) { return tail(z); } - function make_from_real_imag(x, y) { return pair(x, y); } - function magnitude(z) { - return math_sqrt(square(real_part(z)) + - square(imag_part(z))); - } - function angle(z) { - return math_atan2(imag_part(z), real_part(z)); - } - function make_from_mag_ang(r, a) { - return pair(r * math_cos(a), r * math_sin(a)); - } - // interface to the rest of the system - function tag(x) { - return attach_tag("rectangular", x); - } - put("real_part", list("rectangular"), real_part); - put("imag_part", list("rectangular"), imag_part); - put("magnitude", list("rectangular"), magnitude); - put("angle", list("rectangular"), angle); - put("make_from_real_imag", "rectangular", - (x, y) => tag(make_from_real_imag(x, y))); - put("make_from_mag_ang", "rectangular", - (r, a) => tag(make_from_mag_ang(r, a))); - return "done"; -} -install_rectangular_package(); - -function install_polar_package() { - // internal functions - function magnitude(z) { return head(z); } - function angle(z) { return tail(z); } - function make_from_mag_ang(r, a) { return pair(r, a); } - function real_part(z) { - return magnitude(z) * math_cos(angle(z)); - } - function imag_part(z) { - return magnitude(z) * math_sin(angle(z)); - } - function make_from_real_imag(x, y) { - return pair(math_sqrt(square(x) + square(y)), - math_atan2(y, x)); - } - - // interface to the rest of the system - function tag(x) { return attach_tag("polar", x); } - put("real_part", list("polar"), real_part); - put("imag_part", list("polar"), imag_part); - put("magnitude", list("polar"), magnitude); - put("angle", list("polar"), angle); - put("make_from_real_imag", "polar", - (x, y) => tag(make_from_real_imag(x, y))); - put("make_from_mag_ang", "polar", - (r, a) => tag(make_from_mag_ang(r, a))); - return "done"; -} -install_polar_package(); - -function install_complex_package() { - // imported functions from rectangular and polar packages - function make_from_real_imag(x, y) { - return get("make_from_real_imag", "rectangular")(x, y); - } - function make_from_mag_ang(r, a) { - return get("make_from_mag_ang", "polar")(r, a); - } - - // internal functions - function add_complex(z1, z2) { - return make_from_real_imag(real_part(z1) + - real_part(z2), - imag_part(z1) + - imag_part(z2)); - } - function sub_complex(z1, z2) { - return make_from_real_imag(real_part(z1) - - real_part(z2), - imag_part(z1) - - imag_part(z2)); - } - function mul_complex(z1, z2) { - return make_from_mag_ang(magnitude(z1) * - magnitude(z2), - angle(z1) + - angle(z2)); - } - function div_complex(z1, z2) { - return make_from_mag_ang(magnitude(z1) / - magnitude(z2), - angle(z1) - - angle(z2)); - } - - // interface to rest of the system - function tag(z) { - return attach_tag("complex", z); - } - put("add", list("complex", "complex"), - (z1, z2) => tag(add_complex(z1, z2))); - put("sub", list("complex", "complex"), - (z1, z2) => tag(sub_complex(z1, z2))); - put("mul", list("complex", "complex"), - (z1, z2) => tag(mul_complex(z1, z2))); - put("div", list("complex", "complex"), - (z1, z2) => tag(div_complex(z1, z2))); - put("make_from_real_imag", "complex", - (x, y) => tag(make_from_real_imag(x, y))); - put("make_from_mag_ang", "complex", - (r, a) => tag(make_from_mag_ang(r, a))); - return "done"; -} -install_complex_package(); - -function make_complex_from_real_imag(x, y){ - return get("make_from_real_imag", "complex")(x, y); -} -function make_complex_from_mag_ang(r, a){ - return get("make_from_mag_ang", "complex")(r, a); -} - - - - coercion_support - -// coercion support - -let coercion_list = null; - -function clear_coercion_list() { - coercion_list = null; -} - -function put_coercion(type1, type2, item) { - if (is_undefined(get_coercion(type1, type2))) { - coercion_list = pair(list(type1, type2, item), - coercion_list); - } else { - return coercion_list; - } -} - -function get_coercion(type1, type2) { - function get_type1(list_item) { - return head(list_item); - } - function get_type2(list_item) { - return head(tail(list_item)); - } - function get_item(list_item) { - return head(tail(tail(list_item))); - } - function get_coercion_iter(items) { - if (is_null(items)) { - return undefined; - } else { - const top = head(items); - return equal(type1, get_type1(top)) && - equal(type2, get_type2(top)) - ? get_item(top) - : get_coercion_iter(tail(items)); - } - } - return get_coercion_iter(coercion_list); -} - - - - apply_generic_with_coercion_example - base_operation_table - javascript_number_package - complex_number_package - coercion_support - -function javascript_number_to_complex(n) { - return make_complex_from_real_imag(contents(n), 0); -} - -put_coercion("javascript_number", "complex", - javascript_number_to_complex); - -const c = make_complex_from_real_imag(4, 3); -const n = make_javascript_number(7); - -add(c, n); - - - - apply_generic_with_coercion - apply_generic_with_coercion_example - [ 'complex', [ 'rectangular', [ 11, 3 ] ] ] - -(define (apply-generic op . args) - (let ((type-tags (map type-tag args))) - (let ((proc (get op type-tags))) - (if proc - (apply proc (map contents args)) - (if (= (length args) 2) - (let ((type1 (car type-tags)) - (type2 (cadr type-tags)) - (a1 (car args)) - (a2 (cadr args))) - (let ((t1->t2 (get-coercion type1 type2)) - (t2->t1 (get-coercion type2 type1))) - (cond (t1->t2 - (apply-generic op (t1->t2 a1) a2)) - (t2->t1 - (apply-generic op a1 (t2->t1 a2))) - (else - (error "No method for these types" - (list op type-tags)))))) - (error "No method for these types" - (list op type-tags))))))) - - -function apply_generic(op, args) { - const type_tags = map(type_tag, args); - const fun = get(op, type_tags); - if (! is_undefined(fun)) { - return apply(fun, map(contents, args)); - } else { - if (length(args) === 2) { - const type1 = head(type_tags); - const type2 = head(tail(type_tags)); - const a1 = head(args); - const a2 = head(tail(args)); - const t1_to_t2 = get_coercion(type1, type2); - const t2_to_t1 = get_coercion(type2, type1); - return ! is_undefined(t1_to_t2) - ? apply_generic(op, list(t1_to_t2(a1), a2)) - : ! is_undefined(t2_to_t1) - ? apply_generic(op, list(a1, t2_to_t1(a2))) - : error(list(op, type_tags), - "no method for these types"); - } else { - return error(list(op, type_tags), - "no method for these types"); - } - } -} - - - - 这种强制类型转换方案比定义显式跨类型操作的方法有许多优点,如上所述。虽然我们仍然需要编写强制类型转换 过程 函数 来关联这些类型(可能 【46:0†cn.txt】 $n^2$ - 过程 函数 用于一个系统 $n$ 类型),我们只需要为每对类型编写一个 过程 函数 ,而不是为每个类型集合和每个通用操作编写一个不同的 过程 函数 如果我们足够聪明,通常可以用少于 $n^2$ 的强制类型转换 过程。 函数。 例如,如果我们知道如何从类型1转换到类型2,再从类型2转换到类型3,那么我们可以利用这些知识从类型1转换到类型3。这可以极大减少我们在系统中新增类型时需要显式提供的强制类型转换 过程 函数 的数量。如果我们愿意在系统中构建所需的复杂性,我们可以让系统搜索类型之间关系的,并自动生成那些能从显式提供的强制类型转换 过程 函数 推导出来的过程。我们依赖的是类型之间适当的变换仅依赖于类型本身,而不依赖于将要应用的操作。 - - 另一方面,对于某些应用,我们的强制类型转换方案可能不够通用。即使要组合的对象都无法转换为对方的类型,通过将这两个对象转换为第三种类型,仍然可能进行该操作。为了处理这种复杂性,同时保持程序的模块性,通常有必要构建利用类型间关系中更深层结构的系统,正如我们接下来将讨论的那样。 - - 类型层次结构 - - 类型层次结构 - 类型层次结构 - 上述强制类型转换方案依赖于类型对之间自然关系的存在。通常,不同类型之间的关系具有更“全局”的结构。例如,假设我们正在构建一个通用算术系统,以处理整数、有理数、实数和复数。在这样的系统中,将整数视为一种特殊的有理数,再将其视为一种特殊的实数,最终视为一种特殊的复数是非常自然的。实际上,我们拥有一个所谓的类型层次结构,例如,整数是有理数的 子类型 类型子类型 子类型(即任何可以应用于有理数的操作都可以自动应用于整数)。反过来,我们说有理数形成整数的 超类型 类型超类型 超类型。我们这里的特定层次结构是非常简单的,其中每种类型最多只有一个超类型和一个子类型。这种结构称为,在图中有所说明。
类型塔。 类型塔 类型
- 如果我们有一个塔结构,那么我们可以极大地简化向层次结构中添加新类型的问题,因为我们只需要指定新类型如何嵌入在其上方的下一个超类型中,以及它如何成为其下方类型的超类型。例如,如果我们想将一个整数添加到一个复数中,我们不需要显式定义一个特殊的强制类型转换 过程 函数 integer->complex. integer_to_complex. 相反,我们定义一个整数可以如何转换为有理数,有理数如何转换为实数,以及实数如何转换为复数。然后我们允许系统通过这几个步骤将整数转换为复数并然后将两个复数相加。 - 类型提升 apply_generic带类型塔 我们可以通过以下方式重新设计我们的 apply-generic apply_generic 过程 函数 :对于每种类型,我们需要提供一个 raise 过程, 函数, 该函数将该类型的对象“提升”一个层次到塔中。然后,当系统需要对不同类型的对象进行操作时,它可以逐步提升较低的类型,直到所有对象都位于塔中的相同层次。(练习 涉及实现这种策略的详细信息。) - 另一个塔结构的优点是我们可以轻松实现每种类型都“继承”定义在其超类型上的所有操作的概念。例如,如果我们没有提供一个用于查找整数实部的特殊 过程 函数 ,我们仍然可以期望 real-part real_part 会因为整数是复数的子类型而定义在整数上。在塔中,我们可以通过修改 apply-generic. apply_generic. 来以统一的方式实现这一点。如果所需的操作未直接针对给定对象的类型定义,我们会将对象提升到其超类型并再次尝试。因此,我们沿着塔不断向上爬,随着前进而转换我们的参数,直到我们找到可以执行所需操作的层次,或到达顶层(在这种情况下,我们放弃)。 - 类型降低 底层结构的另一个优点是,它为我们提供了一种简单的方法来将数据对象“降为”最简单的表示。例如,如果我们将 $2+3i$ 加到 $4-3i$,得出结果为整数6而不是复数 $6+0i$会很好。练习讨论了实现这种降低操作的方法。(诀窍在于我们需要一种通用的方法来区分那些可以被降低的对象,比如 $6+0i$,与那些不能被降低的对象,比如 $6+2i$。) - - 层次结构的不足 - - 类型层次结构的不足 - 如果我们系统中的数据类型可以自然地安排成一个塔结构,那么如我们所见,这将极大地简化处理不同类型上的通用操作的问题。不幸的是,通常情况并非如此。图展示了一种更复杂的混合类型排列,显示了不同几何图形类型之间的关系。我们看到,通常来说, 类型多重子类型和超类型 超类型多个 子类型多个 一个类型可以有多个子类型。例如,三角形和四边形都是多边形的子类型。此外,一个类型可以有多个超类型。例如,一个等腰直角三角形可以被视为等腰三角形或直角三角形。这种多重超类型的问题尤其棘手,因为这意味着在层次结构中没有唯一的方法将一个类型“提升”。为对象找到应用操作的“正确”超类型可能需要大量的搜索, 过程 函数 例如 apply-generic. apply_generic. 由于通常一个类型有多个子类型,因此在将一个值“降低”到类型层次结构时也存在类似的问题。处理大量相互关联的类型同时在大型系统的设计中保持模块性是非常困难的,并且这是当前研究的一个热点领域。 这段话出现在该书的第一版中,现在仍然适用,就像我们十二年前编写时一样。开发一个用于表达不同实体类型之间关系的有用且通用的框架(哲学家称之为“本体论”)似乎难以解决。当前与十年前的混乱之间的主要区别在于,现在各种不完善的本体论理论已经被植入相应不完善的编程语言中。例如,面向对象编程语言的复杂性——以及当代面向对象语言之间微妙且混乱的差异——主要集中在处理相互关联类型上的通用操作。我们在第章中对计算对象的讨论完全避免了这些问题。熟悉面向对象编程的读者会注意到我们在第章中对局部状态有很多讨论,但我们甚至没有提到“类”或“继承”。事实上,我们怀疑这些问题不能仅通过计算机语言设计来充分解决,而不借助知识表示和自动推理方面的工作。 -
几何图形类型之间的关系。
- - - apply_generic带强制转换 Louis Reasoner 注意到 apply-generic apply_generic 可能尝试将参数强制转换为彼此的类型,即使它们已经具有相同的类型。因此,他推测,我们需要在强制转换表中放置 过程 函数 以将每种类型的参数“强制转换”到其自身的类型。例如,除了上面展示的 scheme-number->complex javascript_number_to_complex 强制转换外,他还会这样做: - javascript_number_to_javascript_ number - complex_to_complex - -(define (scheme-number->scheme-number n) n) -(define (complex->complex z) z) -(put-coercion 'scheme-number 'scheme-number - scheme-number->scheme-number) -(put-coercion 'complex 'complex complex->complex) - - -function javascript_number_to_javascript_number(n) { return n; } - -function complex_to_complex(n) { return n; } - -put_coercion("javascript_number", "javascript_number", - javascript_number_to_javascript_number); -put_coercion("complex", "complex", complex_to_complex); - - - -
    -
  1. 随着 Louis 的强制类型转换 过程 函数 安装,如果 apply-generic apply_generic 被调用且带有两个类型为 scheme-number "complex" 或两个参数为类型的 complex "javascript_@number" 且在这些类型的表中找不到操作,会发生什么?例如,假设我们已经定义了一个通用的指数运算: - apply_generic - -(define (exp x y) (apply-generic 'exp x y)) - - -function exp(x, y) { - return apply_generic("exp", list(x, y)); -} - - 并在 Scheme-number JavaScript-number 包中但不在任何其他包中放入一个用于求幂的 过程 函数 - -;; following added to Scheme-number package -(put 'exp '(scheme-number scheme-number) - (lambda (x y) (tag (expt x y)))) ; using primitive expt - - -// following added to JavaScript-number package -put("exp", list("javascript_number", "javascript_number"), - (x, y) => tag(math_exp(x, y))); // using primitive $\texttt{math\char`_exp}$ - - 如果我们调用会发生什么 exp 以两个复数作为参数会怎样?
  2. -
  3. Louis 是否正确地认为针对相同类型参数的强制转换需要进行处理,或者 apply-generic apply_generic 本身是否工作正常?
  4. 修改 apply-generic apply_generic 使得在两个参数具有相同类型时不尝试进行强制转换。
- -
    -
  1. 如果 Louis 将强制类型转换函数放入操作表中, apply_generic 将进入一个无限循环
  2. Louis 的代码不起作用。 apply_generic 按原样正确工作。但我们可以修改它,使其在尝试任何相同类型的强制转换之前退出并报错。
  3. - - apply_generic_with_unavailable_type_example - base_operation_table - complex_number_package - coercion_support - - -function install_javascript_number_package() { - function tag(x) { - return attach_tag("javascript_number", x); - } - put("add", list("javascript_number", "javascript_number"), - (x, y) => tag(x + y)); - put("sub", list("javascript_number", "javascript_number"), - (x, y) => tag(x - y)); - put("mul", list("javascript_number", "javascript_number"), - (x, y) => tag(x * y)); - put("div", list("javascript_number", "javascript_number"), - (x, y) => tag(x / y)); - put("exp", list("javascript_number", "javascript_number"), - (x, y) => tag(math_exp(x, y))); - put("make", "javascript_number", - x => tag(x)); - return "done"; -} -install_javascript_number_package(); - -function make_javascript_number(n) { - return get("make", "javascript_number")(n); -} - -function javascript_number_to_javascript_number(n) { - return n; -} -function complex_to_complex(n) { - return n; -} -put_coercion("javascript_number", "javascript_number", - javascript_number_to_javascript_number); -put_coercion("complex", "complex", - complex_to_complex); -function exp(x, y) { - return apply_generic("exp", list(x, y)); -} - -const c = make_javascript_number(4); -const d = make_javascript_number(2); -exp(c, d); - - - - - - apply_generic_with_unavailable_type - apply_generic_with_unavailable_type_example - - - -function apply_generic(op, args) { - const type_tags = map(type_tag, args); - const fun = get(op, type_tags); - if (! is_undefined(fun)) { - return apply(fun, map(contents, args)); - } else { - if (length(args) === 2) { - const type1 = head(type_tags); - const type2 = head(tail(type_tags)); - const a1 = head(args); - const a2 = head(tail(args)); - const t1_to_t2 = get_coercion(type1, type2); - const t2_to_t1 = get_coercion(type2, type1); - return type1 === type2 - ? error(list(op, type_tags), - "no method for these types") - : ! is_undefined(t1_to_t2) - ? apply_generic(op, list(t1_to_t2(a1), a2)) - : ! is_undefined(t2_to_t1) - ? apply_generic(op, list(a1, t2_to_t1(a2))) - : error(list(op, type_tags), - "no method for these types"); - } else { - return error(list(op, type_tags), - "no method for these types"); - } - } -} - - -
  4. -
-
- -
- - - apply_generic对多参数进行强制转换 展示如何将 apply-generic apply_generic 泛化为处理多参数的强制转换。一种策略是尝试将所有参数强制转换为第一个参数的类型,然后是第二个参数的类型,以此类推。举一个此策略(以及上述的双参数版本)不够通用的例子。(提示:考虑在表中存在一些适合的混合类型操作但不会被尝试的情况。) - - - multi_coercion_example - base_operation_table - javascript_number_package - complex_number_package - coercion_support - -function javascript_number_to_complex(n) { - return make_complex_from_real_imag(contents(n), 0); -} - -put_coercion("javascript_number", "complex", - javascript_number_to_complex); - -put("add", list("complex", "complex", "complex"), - (x, y, z) => attach_tag("complex", make_complex_from_real_imag( - real_part(x) + real_part(y) + real_part(z), - imag_part(x) + imag_part(y) + imag_part(z)))); - -function add_three(x, y, z) { - return apply_generic("add", list(x, y, z)); -} - -const c = make_complex_from_real_imag(4, 3); -const n = make_javascript_number(7); -add_three(c, c, n); -// add_three(c, n, n); - - - - - - multi_coercion - multi_coercion_example - -function can_coerce_to(type_tags, target_type) { - return accumulate((type_tag, result) => - result && - (type_tag === target_type || - ! is_undefined(get_coercion(type_tag, target_type))), - true, - type_tags); -} - -function find_coerced_type(type_tags) { - return is_null(type_tags) - ? undefined - : can_coerce_to(type_tags, head(type_tags)) - ? head(type_tags) - : find_coerced_type(tail(type_tags)); -} - -function coerce_all(args, target_type) { - return map(arg => type_tag(arg) === target_type - ? arg - : get_coercion(type_tag(arg), target_type)(arg), - args); -} - -function apply_generic(op, args) { - const type_tags = map(type_tag, args); - const fun = get(op, type_tags); - if (! is_undefined(fun)) { - return apply(fun, map(contents, args)); - } else { - const target_type = find_coerced_type(type_tags); - if (! is_undefined(target_type)) { - return apply_generic(op, coerce_all(args, target_type)); - } else { - return error(list(op, type_tags), - "no method for these types"); - } - } -} - - 一种情况下这种方法是不够的:如果你有三种类型,A、B、C,其中A可以强制转换为B并且C可以强制转换为B,并且有一个注册的操作为(A, B, B)。对于(A, B, C)的运算只会尝试(A, B, C)和(B, B, B),但你可以简单地将C强制转换为B,并使用(A, B, B)的注册操作。 - - - - 类型提升 假设你正在设计一个处理图中显示的类型塔的通用算术系统:整数、有理数、实数、复数。对于每种类型(除了复数),设计一个 过程 函数 将该类型的对象提升一个层次到塔中。展示如何安装一个通用【136:3†cn.txt】 raise 操作,该操作将适用于每一种类型(除了复数)。 - - - rational_number_package - -function gcd(a, b) { - return b === 0 ? a : gcd(b, a % b); -} - -function install_rational_package() { - // internal functions - function numer(x) { - return head(x); - } - function denom(x) { - return tail(x); - } - function make_rat(n, d) { - let g = gcd(n, d); - return pair(n / g, d / g); - } - function add_rat(x, y) { - return make_rat(numer(x) * denom(y) + - numer(y) * denom(x), - denom(x) * denom(y)); - } - function sub_rat(x, y) { - return make_rat(numer(x) * denom(y) - - numer(y) * denom(x), - denom(x) * denom(y)); - } - function mul_rat(x, y) { - return make_rat(numer(x) * numer(y), - denom(x) * denom(y)); - } - function div_rat(x, y) { - return make_rat(numer(x) * denom(y), - denom(x) * numer(y)); - } - // interface to rest of the system - function tag(x) { - return attach_tag("rational", x); - } - put("add", list("rational", "rational"), - (x, y) => tag(add_rat(x, y))); - put("sub", list("rational", "rational"), - (x, y) => tag(sub_rat(x, y))); - put("mul", list("rational", "rational"), - (x, y) => tag(mul_rat(x, y))); - put("div", list("rational", "rational"), - (x, y) => tag(div_rat(x, y))); - put("make", "rational", - (n, d) => tag(make_rat(n, d))); -} -install_rational_package(); - -function make_rational(n, d) { - return get("make", "rational")(n, d); -} - - - - - javascript_number_package - rational_number_package - complex_number_package - coercion_support - apply_generic_with_coercion - raise_example - -const a = make_javascript_number(2); -const b = make_rational(2, 3); -raise(a); -// raise(b); - - - - - raise - raise_example - -function raise(x) { - return apply_generic("raise", list(x)); -} - -// add to Javascript-number package -put("raise", list("javascript_number"), - x => make_rational(x, 1)); -//// add to rational package -// put("raise", list("rational"), -// x => make_real(div, numer(x), denom(x))); -// put("raise", list("rational"), -// x => make_complex_from_real_imag(x, 0)); -//// add to real package -// put("raise", list("real"), -// x => make_complex_from_real_imag(x, 0)); - - - - - - apply_generic通过提升进行强制转换 使用练习的raise操作,修改 apply-generic apply_generic 过程 函数 以便通过本节讨论的逐层提升方法,将其参数强制转换为相同类型。您需要设计一种方法来测试两个类型中哪个在塔中更高。按照与系统其他部分“兼容”的方式做到这一点,并不会导致向塔中添加新层级时出现问题。 - - - apply_generic通过下降进行简化 类型降低 本节提到了通过尽可能低地降低数据对象到类型塔中以进行“简化”的方法。设计一个 过程 函数 drop 实现练习中描述的塔的功能。关键是要以某种通用方式判断一个对象是否可以降低。例如,复数 $1.5+0i$ 可以被降低到 real, "real", 复数 $1+0i$ 可以被降低到 integer, "integer", 和复数 $2+3i$ 无法降低。以下是确定对象是否可以降低的计划:首先定义一个通用操作 project 通过降低塔中的对象来“推”它。例如,投影一个复数将涉及丢弃虚部。然后,如果当我们 【158:10†cn.txt】 project 它和 raise 结果返回到我们开始的类型,我们最终得到的等于我们开始的。通过编写一个详细地展示如何实现这个想法的 drop - 过程 函数 将一个对象尽可能地降低。您将需要设计各种投影操作可以使用 math_round (原语函数) math_roundMath.round round math_round 原语将实数投影到整数,该函数返回最接近其参数的整数。 并安装 project 作为系统中的一种通用操作。您还需使用通用等式谓词,例如在练习中描述的。最后,使用 drop 重写练习中的 apply-generic apply_generic ,使其“简化”其答案。 - - - 假设我们希望处理复数,它们的实部、虚部、幅度和角度可以是普通数、有理数或我们可能希望添加到系统中的其他数字。描述并实现为适应此情况系统所需的更改。您将不得不定义一些操作如 sine 和 cosine,这些操作是普通数和有理数的通用操作。 - - 强制类型转换 - 类型层次结构 - 类型层次结构 - -
diff --git a/xml/cn/chapter3/section2/section2.xml b/xml/cn/chapter3/section2/section2.xml deleted file mode 100644 index 35ce5a5d6..000000000 --- a/xml/cn/chapter3/section2/section2.xml +++ /dev/null @@ -1,195 +0,0 @@ -
- 求值的环境模型 - - - - - 求值的环境模型 - - - 当我们在一章中介绍复合 - 过程 - 函数 - 时,我们使用求值的代换模型 - (在节)来定义应用过程函数到参数的意义: -
    -
  • 要应用一个复合 - 过程 - 函数 - 到参数,需将 - 过程体 - 函数的返回表达式(更广泛地说,函数体) - 中的每个 - 形式化 - - 参数替换为对应的实参。 -
  • -
-
- - - 一旦我们将赋值引入我们的编程语言,这样的定义就不再足够。特别是,节指出,在存在赋值的情况下, - - - 变量不能仅仅被视为一个值的名字。相反,变量必须以某种方式指示一个可以存储值的位置。 - - - 名字不能仅仅被视为表示一个值。相反,名字必须以某种方式指示一个可以存储值的位置。 - - - 在我们新的求值模型中,这些位置将维持在称为环境 - 环境的结构中。 - - - - 环境是帧(环境模型)的序列。每个帧是绑定绑定的表(可能为空),它将 - - 变量名 - - - 名称 - - 与其对应的值关联起来。 - - - (单个帧中最多只能包含对任何变量的一个绑定。) - - - (单个帧中最多只能包含对任何名称的一个绑定。) - - 每个帧还有一个指针指向其封闭环境环境封闭封闭环境,除非为了讨论的目的,帧被认为是全局帧帧(环境模型)全局全局。相对于一个环境的 - - 变量的值 - - - 名称名称的值 - - 是由环境中第一个包含该 - - 变量 - - - 名称 - - 绑定的帧所给出的。如果序列中的没有任何帧为该 - - 变量, - - - 名称, - - 指定绑定,那么就说该 - - 变量 - - - 名称 - - 是未绑定的。 未绑定名称 - 名称未绑定 - 未绑定 在环境中。 - - -
-
- - 一个简单的环境结构。 -
- - -
-
- - - 一个简单的 - 求值的环境模型环境结构 - 环境结构。 - -
-
-
-
- - - - - 图 - - - 图 - - - 展示了一个简单的环境结构,由三个帧组成,标记为 I、II 和 III。在图中,A、B、C 和 D 是指向环境的指针。C和 D 指向相同的环境。 - - 变量 - - - 名称 - - z 和 x 被绑定在帧 II,而 y 和 x 被绑定在帧 I。在环境 D 中,x 的值是 3。相对于环境 B,x 的值也是 3。其确定方式如下:检查序列中的第一个帧(帧 III),未找到x的绑定,因此继续到封闭环境 D,并在帧 I 找到绑定。另一方面,在环境 A 中,x 的值是 7,因为序列中的第一个帧(帧 II)包含将x绑定到 7 的绑定。相对于环境 A,将x绑定到帧 II 的 7 被认为遮蔽一个绑定遮蔽了在帧 I 中将x绑定到 3 的绑定。 - - - - 环境对求值过程至关重要,因为它决定了表达式应该在何种上下文中进行求值。实际上,可以说编程语言中的表达式本身没有任何意义。相反,表达式仅在其被求值的某个环境中才能获得意义。 - - - 即使是像(+11)这样简单的表达式的解释也依赖于理解+是加法的符号这一上下文。 - - - 即使是像display(1)这样简单的表达式的解释也依赖于理解名称display指的是显示一个值的原语函数的上下文。 - - - 因此,在我们的求值模型中,我们总是会提到在某个环境中求值表达式。为了描述与解释器的交互,我们假设存在一个 - 全局环境 - 全局环境,由一个包含原语 - - 过程 - - - 函数 - - 符号值的单一帧(无封闭环境)组成。 - 例如,理念是 - - - - + 是加法符号是通过说符号+ - - - display 是指代显示的原语函数的名称是通过说名称display - - - 在全局环境中绑定到原语 - - 加法过程。 - 显示函数。 - - - - - - - 在我们对程序进行求值之前,我们通过一个新的帧程序帧扩展全局环境,从而形成 - 程序环境 - 程序环境。我们将在程序顶层声明的名称,即在任何块之外的名称,添加到这个帧中。然后,给定程序相对于程序环境进行求值。 - - - - - - - &subsection3.2.1; - - - &subsection3.2.2; - - - &subsection3.2.3; - - - &subsection3.2.4; - - - &subsection3.2.5; - -
diff --git a/xml/cn/chapter3/section2/subsection1.xml b/xml/cn/chapter3/section2/subsection1.xml deleted file mode 100644 index deddaf6a0..000000000 --- a/xml/cn/chapter3/section2/subsection1.xml +++ /dev/null @@ -1,568 +0,0 @@ - - - 求值规则 - - - - 求值的环境模型求值规则 - - - - 关于解释器如何 - 函数应用环境模型的 - 求值的环境模型函数应用 - 对 - - - 组合式 - - - 函数应用 - - - 的求值的总体规范保持不变,如我们在
中首次介绍的那样: -
    -
  • - 对 - - - 组合式进行求值: - - - 函数应用进行求值: - - -
      -
    1. - 对 - - - 组合式的子表达式求值。赋值在求值规则的第1步中引入了微妙之处。如练习所示,赋值的存在使我们能够编写根据子表达式在组合式中求值的求值顺序与实现相关不同而产生不同值的表达式。因此,为了精确起见,我们应该在第1步中指定一个求值顺序(例如,从左到右或从右到左)。然而,这种顺序应该始终被视为一种实现细节,绝不应编写依赖某种特定顺序的程序。例如,一个复杂的编译器可能通过改变子表达式的求值顺序来优化程序。ECMAScript标准指定从左到右对子表达式进行求值。 - - - 应用的子表达式求值。赋值在求值规则的第1步中引入了一种微妙之处。如练习所示,赋值的存在使我们能够编写根据组合式中子表达式的求值顺序产生不同值的表达式。为了消除这种歧义,求值顺序JavaScript在JavaScript中 JavaScript指定组合式的子表达式和应用的参数表达式的从左到右求值。 - - -
    2. -
    3. - 将 - - - 运算符 - - - 函数 - - - 子表达式的值应用到 - - - 操作数 - - - 参数 - - - 子表达式的值上。 -
    4. -
    -
  • -
- 求值的环境模型替代了代换模型,用于指定应用复合函数的含义 - - 过程 - 函数 - - 到参数。 -
- - - 在求值的环境模型中, - - 过程 - 函数 - - 始终是由一些代码和一个指向环境的指针组成的组合体。 - - 过程 - 函数 - - 的创建只有一种方式:通过对 - - lambda - lambda - - 表达式求值。 - lambdalambda 表达式一起创建 - 这产生了一个 - - 过程 - 函数 - - 其代码是从 - - lambda - lambda - - 表达式的文本中获得的,其环境是对 - - lambda - lambda - - 表达式进行求值以生成 - - 过程。 - 函数。 - - 例如,考虑以下 - - 过程定义。 - 函数声明。 - - square环境在环境模型中 - - square_example - 196 - -(define (square x) - (* x x)) - - -function square(x) { - return x * x; -} - - - 在 - - - 全局 - - - 程序 - - - 环境中求值。 - - 过程定义 - 函数声明 - - 语法是 - - 只是语法糖 - - 等同于 - - - 底层隐式 - - lambda - lambda - - 表达式的语法形式。 - 这将等同于使用 - 脚注在第 1 章中 - 提到在完整的 JavaScript 中二者之间的微妙差别,我们将在本书中忽略这些差别。 - - square_example - 196 - -(define square - (lambda (x) (* x x))) - - -const square = x => x * x; - - - 对 - - (lambda (x) (* x x)) - - - x => x * x - - - 求值并绑定 - square 绑定到结果值,全部在 - - - 全局 - - - 程序 - - - 环境中。 - - - - - - 图 - - - 图 - - - 显示了评估此 - 声明环境模型的 - - define 表达式。 - 声明语句。 - - - - - - 全局环境包围程序环境。为了减少混乱,在这幅图之后我们将不再显示全局环境(因为它始终相同),但我们通过程序环境向上的指针提醒其存在。 - - - 这个 - - 过程 - 函数 - - 对象是一个组合体,其代码指定 - - 过程 - 函数 - - 具有一个 - 形式 - 参数,即 -x , 和 - - 过程 - 函数 - - 体 - - (* x x). - return x * x;. - - - 这个 - - 过程 - 函数 - - 的环境部分是指向程序环境的指针,因为这是求值 - - lambda - lambda - - 表达式以产生 - - 过程 - 函数 - - 的环境。一个新的绑定,它将 - - 过程 - 函数 - - 对象与一个 - - - 符号 - - - 名称 - - - square ,已添加到程序帧中。 - - - 一般而言,define 通过 - 将绑定添加到帧来创建定义。 - - - - - - - -
-
- - - 在全局环境中评估 - (define (square x) (* x x)) 产生的 - 环境结构。 - -
- - -
-
- - - 在程序环境中评估 - function square(x) { return x * x; } - 产生的环境结构。 - -
-
-
-
- - - - - - - 通常,const, - function和 - let - 会将绑定添加到帧中。 - 对常量的赋值是被禁止的,因此我们的环境模型需要将指向常量的名称与指向变量的名称区分开来。我们通过在冒号后面添加一个等号来表示名称是一个常量。 - 我们将函数声明视为与常量声明等效;我们在第 1 章的脚注中提到,在完整的 JavaScript 语言中允许对函数声明的名称进行赋值。请注意图中冒号后的等号。 - - - - - 现在我们已经看到了 - - 过程 - 函数 - - 是如何创建的,我们可以描述 - - 过程 - 函数 - - 是如何应用的。环境模型规定:要将 - - 过程 - 函数 - - 应用于参数,创建一个新的环境,其中包含一个绑定参数到参数值的帧。这个帧的外围环境是由 - - 过程。 - 函数。 - 指定的环境。现在,在这个新环境中,评估 - - 过程 - 函数 - - 体。 - - - -要显示如何遵循这一规则, - - - 图 - - - 图 - - - 说明了通过求值表达式 - - (square 5) - square(5) - - - 在 - - 全局 - 程序 - - 环境中创建的环境结构,在哪里 -square 是在 - - 过程 - 函数 - - 中生成的 - - - 图. - - - 图. - - - 应用 - - 过程 - 函数 - - 结果是产生一个新环境,图中标记为 E1,其始于一个框架,其中 -x , - - 形式 - - 参数在 - - 过程中, - 函数中, - - 被绑定到参数5。 - - - 请注意,环境 E1 中的名称 x 后面跟有一个冒号,但没有等号,指示参数 x 被视为变量。该例子并未利用该参数是变量这一事实,但请回忆在第节中的函数 make_withdraw 依赖于其参数是变量。 - - - - 从该框架向上的指针表明该框架的外围环境是 - - 全局 - 程序 - - 环境。选择 - - 全局 - 程序 - - 环境是因为这是指示为一部分的环境 -square - - 过程 - 函数 - - 对象。在 E1 中,我们求值 - - 过程, - 函数, - - - (* x x). - return x * x;. - - - 体。由于 -x 在 E1 中是 5,结果是 - - (*55), - 5 * 5, - - 或25。 - square环境在环境模型中 - - -
-
- - - 在全局环境中求值 - (square 5)所创建的环境。 - -
- - -
-
- - - 在程序环境中求值 - square(5) - 所创建的环境。 - -
-
-
-
- - - 求值的环境模型 - - 过程 - 函数 - - 应用可归纳为两个规则: -
    -
  • - A - - 过程 - 函数 - - 对象是通过构建一个帧来应用于一组参数的, - - 将该过程的形式参数绑定到调用的参数 - 将函数的参数绑定到调用的参数 - - ,然后在新构造的环境中评估 - - 过程 - 函数 - - 体。新框架的外围环境是所应用的 - - 过程 - 函数 - - 对象的环境部分。 - 应用的结果是在评估函数体时遇到的第一个返回语句的返回表达式的评估结果。 -
  • -
  • - A - - 过程 - 函数 - - 是通过在给定环境中评估 - lambdalambda 表达式一起创建 - - lambda - lambda - - 表达式创建的。结果的 - lambda 表达式 - - 过程 - 函数 - - 对象是由 - - lambda - lambda - - 表达式的文本和一个指向创建该 - - 过程 - 函数 - - 环境的指针组成的组合体。 -
  • -
-
- - - - - define环境模型的 - 我们也指定使用define定义一个符号在当前环境帧中创建一个绑定,并将指示的值赋给该符号。 - - - - 我们在 JavaScript 版本中省略这一点,因为在第节中有更详细的讨论。 - - - - - - define环境模型的 - 对表达式(set! - variable value) - 在某个环境中进行求值会定位到环境中变量的绑定。为此,需要找到环境中包含该变量绑定的第一个帧并对其进行修改。如果变量在环境中没有绑定,则set!会发出错误信号。 - - - 赋值求值的 - 最后,我们规定了赋值的行为,这个操作最初迫使我们引入了环境模型。对表达式 - name=value - 在某个环境中的求值会定位到环境中名称的绑定。也就是说,需要找到环境中包含该名称绑定的第一个帧。 - 如果绑定是变量绑定——在帧中用名称后仅有:表示——该绑定会被改变以反映变量的新值。 - 否则,如果帧中的绑定是常量绑定——在帧中用名称后紧跟:=表示——赋值会发出"assignment to constant"错误信号。 - 如果名称在环境中没有绑定,则赋值会发出"variable undeclared"错误信号。 - - - - - - 尽管这些求值规则比代换模型复杂得多,但仍然相当简单。此外,尽管求值模型是抽象的,但它正确描述了解释器如何对表达式求值。在第章中,我们将看到这个模型如何可以作为实现有效解释器的蓝图。以下各节通过分析一些示例程序来详细说明该模型的细节。 - 求值的环境模型求值规则 - -
diff --git a/xml/cn/chapter3/section2/subsection2.xml b/xml/cn/chapter3/section2/subsection2.xml deleted file mode 100644 index ab933ed26..000000000 --- a/xml/cn/chapter3/section2/subsection2.xml +++ /dev/null @@ -1,103 +0,0 @@ - - 应用简单 过程 函数 - - - 求值的环境模型过程函数-应用示例 - 函数应用环境模型 - - - sum_of_squares环境在环境模型 当我们在部分引入代换模型时,我们展示了 组合 (f 5) 函数应用 f(5) 在给定以下 过程定义: 函数声明: 的情况下计算结果为136【16:0†source】 - f_example - 136 - -(define (square x) - (* x x)) - -(define (sum-of-squares x y) - (+ (square x) (square y))) - -(define (f a) - (sum-of-squares (+ a 1) (* a 2))) - - -function square(x) { - return x * x; -} -function sum_of_squares(x, y) { - return square(x) + square(y); -} -function f(a) { - return sum_of_squares(a + 1, a * 2); -} - - 我们可以使用环境模型分析相同的示例。 图显示了通过求值定义创建的三个 过程 函数 对象【16:2†source】f, square ,和 sum-of-squares sum_of_squares 全局 程序 环境中。每个 过程 函数 对象由一些代码以及指向 全局 程序 环境的指针组成。
全局框架中的过程对象。
程序框架中的函数对象。
- - - <!-- 图像稍后为 SICP JS 分页而更改 --> <!-- 图像代码在本文件的 PDF_ONLY 中重复 -->
通过图中的过程计算 (f 5) 所创建的环境。
通过图中的函数计算 f(5) 所创建的环境。
中,我们看到通过求值表达式 (f 5). f(5). 所创建的环境结构。对 f 创建了一个新环境,E1,始于一个帧 a形式参数 f ,被绑定到参数 5。在 E1 中,我们 评估的主体【40:1†source】 f - -(sum-of-squares (+ a 1) (* a 2)) - - -return sum_of_squares(a + 1, a * 2); - - - 为了求值 这个组合,我们首先对子表达式进行求值。 返回语句,我们首先对返回表达式的子表达式进行求值。 第一个子表达式, sum-of-squares, sum_of_squares, 的值是一个 过程 函数 对象。(注意如何找到这个值:我们先在 E1 的第一个帧中查找,其中不包含 sum-of-squares 的绑定。 sum_of_squares的绑定。 然后我们转到封闭环境,即 全局 程序 环境,并找到在 中显示的绑定。) 中的绑定。) 另外两个子表达式通过应用原始运算进行求值【16:0†source】 +* 为了对两个组合进行求值 (+ a 1) a + 1 (* a 2) a * 2 分别得到6和10。
- - 现在我们应用 过程 函数 对象 sum-of-squares sum_of_squares 到参数6和10。这会产生一个新环境,E2,其中 形式参数 xy 被绑定到参数。在 E2 中我们求值 组合式 (+ (square x) (square y))。 语句 return square(x) + square(y); 这导致我们求值 (square x), square(x), 其中 【16:5†source】 square 全局 程序 帧中找到和 x 是 6。再一次,我们设置一个新环境,E3,其中 【70:0†source】 x 被绑定到 6,并在其中我们评估的函数体 【74:12†cn.txt】 square ,它是 (* x x). return x * x;. 同时在应用 sum-of-squares, sum_of_squares, 时,我们必须对子表达式求值 (square y), square(y), 其中 【16:0†source】 y 是 10。第二次调用 【16:1†source】square 创建了另一个环境,E4,其中 x形式参数 被绑定到 参数 6,并在我们评估的函数体中 【90:15†source】 square ,被绑定到 10。并且在 E4 中,我们必须 求值 (* x x). return x * x;. 【74:12†cn.txt】 - - 需要注意的重要一点是,每次调用 square 都会创建一个包含 x 绑定的新环境。我们可以在这里看到,不同的帧如何用来分开所有命名为 x 的不同局部变量。注意,由 square 创建的每个帧都指向 全局 程序 环境,因为这是由 square 过程 函数 对象指示的环境。 - - 在子表达式求值后,结果会被返回。通过两次调用square生成的值被 sum-of-squares, sum_of_squares, 相加并返回给 f。因为我们这里的重点是环境结构,所以我们不会详细讨论这些返回值是如何从一次调用传递到另一次调用的;然而,这也是求值过程中的一个重要方面,我们将在章中详细讨论。 sum_of_squares环境在环境模型 - - - 在章节中,我们使用代换模型来分析两个 递归过程与迭代过程比较 迭代过程与递归过程比较 过程 函数 用于计算 factorial评估中的环境结构 阶乘,一个递归版本 【106:3†cn.txt】 - factorial_example - 120 - -(define (factorial n) - (if (= n 1) - 1 - (* n (factorial (- n 1))))) - - -function factorial(n) { - return n === 1 - ? 1 - : n * factorial(n - 1); -} - - 和一个迭代版本【110:6†cn.txt】 - factorial_example - 120 - -(define (factorial n) - (fact-iter 1 1 n)) - -(define (fact-iter product counter max-count) - (if (> counter max-count) - product - (fact-iter (* counter product) - (+ counter 1) - max-count))) - - -function factorial(n) { - return fact_iter(1, 1, n); -} -function fact_iter(product, counter, max_count) { - return counter > max_count - ? product - : fact_iter(counter * product, - counter + 1, - max_count); -} - - 显示由评估 (factorial 6) factorial(6) 使用每个版本 【114:0†cn.txt】 factorial - 过程函数 使用环境模型无法明确我们在章节中关于解释器可以在有限空间内通过尾递归执行类似 过程 函数 fact-iter fact_iter 的说法。我们将在章节中处理解释器的控制结构时讨论 求值的环境模型尾递归与 尾递归求值的环境模型与 尾递归。 - - 求值的环境模型过程函数-应用示例 - 函数应用环境模型 - - <!-- 图像从之前的位置移到这里以便于 SICP JS 排版 --> <!-- 图像代码是本文件中 WEB_ONLY 之前代码的复制 -->
通过求值 (f 5) 以及图中的过程创建的环境。
通过求值 f(5) 以及图中的函数创建的环境。
- -
diff --git a/xml/cn/chapter3/section2/subsection3.xml b/xml/cn/chapter3/section2/subsection3.xml deleted file mode 100644 index 8bcd66011..000000000 --- a/xml/cn/chapter3/section2/subsection3.xml +++ /dev/null @@ -1,655 +0,0 @@ - - 帧(作为局部状态的存储库) - - - 帧 (环境模型)作为局部状态的存储库 - 局部状态在帧中维护 - 求值的环境模型局部状态 - - - 我们可以通过环境模型来查看 - - 过程 - 函数 - - 和赋值如何用于表示具有局部状态的对象。例如,考虑通过调用make_withdrawenvironment在环境模型中创建的section中的取款处理器 - - 过程 - 函数 - - - make_withdraw2 - -(define (make-withdraw balance) - (lambda (amount) - (if (>= balance amount) - (begin (set! balance (- balance amount)) - balance) - "Insufficient funds"))) - - -function make_withdraw(balance) { - return amount => { - if (balance >= amount) { - balance = balance - amount; - return balance; - } else { - return "insufficient funds"; - } - }; -} - - - 让我们描述一下求值 - - make_withdraw2 - make_withdraw2_w1_declare - -(define W1 (make-withdraw 100)) - - -const W1 = make_withdraw(100); - - - 接着是 - - make_withdraw2_w1_example - make_withdraw2_w1_declare - 50 - -(W1 50) - - - 50 - - -W1(50); - - -50 - - - - - 图 - - - 图 - - - 显示了 - - 定义 - make-withdraw - 声明 - make_withdraw - - - - 过程 - 函数 - - 在 - - 全局 - 程序 - - 环境中的结果。这产生了一个包含对 - - 全局 - 程序 - - 环境的指针的 - - 过程 - 函数 - - 对象。到目前为止,这与我们已经看到的例子没有什么不同,除了 - - - 过程体本身是一个 - lambda - 表达式。 - - - 函数体中的返回表达式本身是一个 - lambda 表达式。 - - - - -
-
- - 在全局环境中定义make-withdraw的结果。 - - -
- - -
-
- - 在程序环境中定义make_withdraw的结果。 - - -
-
-
-
- - - 计算的有趣部分发生在我们应用 - 过程 - 函数 - - make-withdraw - make_withdraw - - 到一个参数: - - -(define W1 (make-withdraw 100)) - - -const W1 = make_withdraw(100); - - - 我们像往常一样开始,通过设置一个环境 E1,其中的 - 形式 参数 -balance - 绑定到参数 100。在这个环境中,我们对 - - make-withdraw, - - make_withdraw, - - - 的函数体进行求值,主要是对 - - lambda 表达式。这 - 返回语句,其中返回表达式是 - 一个 lambda 表达式。对这个 lambda 表达式的求值 - - 构造一个新的 - - 过程 - 函数 - - 对象,其代码如 - - lambda - lambda 表达式 - - 中所指定,其环境是 E1,也是在这个环境中 - - lambda - lambda 表达式 - - 被求值以生成 - - 过程。 - 函数。 - - 由调用 - make-withdraw - - make_withdraw - - 返回的值就是这个结果 -W1 - 在 - 全局 - 程序 - - 环境中,因为 - define - 常量声明 - - 本身正在 - 全局 - 程序 - - 环境中被求值。 - - - 图 - - - 图 - - - 显示了最终的环境结构。 - - -
-
- - 在 - (define W1 (make-withdraw 100)) - 上求值的结果。 - - -
- - -
-
- - 在 - const W1 = make_withdraw(100); - 上求值的结果。 - - -
-
-
-
- - - 现在我们可以分析当 -W1 - 被应用于一个参数: - - -(W1 50) - - - 50 - - -W1(50); - - -50 - - - 我们开始构造一个帧,其中 -amount , - 形式参数 -W1 ,被绑定到参数 50。需要注意的关键点是,这个帧的外围环境不是 - 全局 - 程序 - - 环境,而是环境 E1,因为这是由 -W1 - - - 过程 - 函数 - - 对象。在这个新环境中,我们对 - - 过程: - 函数: - - 的主体进行求值。 - - -(if (>= balance amount) - (begin (set! balance (- balance amount)) - balance) - "Insufficient funds") - - -if (balance >= amount) { - balance = balance - amount; - return balance; -} else { - return "insufficient funds"; -} - - - 生成的环境结构如 - - - 图. - - - 图. - - - 所示。正在求值的表达式引用了两个 -amount - 和 -balance. - - - Amount - 变量 amount - - - 将在该环境中的第一个帧中找到,并且 -balance -将在 E1 中通过跟随外层环境指针找到。 - - -
-
- - 通过应用过程对象 W1 创建的环境。 - - -
- - -
-
- - 通过应用函数对象 W1 创建的环境。 - - -
-
-
-
- - - 当 - - set! - 赋值 - - 被执行时,绑定 -balance 在 E1 中被更改。 在调用结束时 -W1, balance 在 E1 中,50,包含的帧 balance -仍然由 - - 过程 - 函数 - -对象指向 -W1 -仍然由包含的帧绑定。 -amount ,绑定的帧(我们执行更改的代码) -balance -) 不再相关,因为构造它的 - 过程 - 函数 - -调用已经结束,并且没有来自环境其他部分的指向该帧的指针。下一次 -W1 -被调用时,这将构建一个新的帧,该帧绑定 -amount -并且其外层环境是 E1。我们看到 E1 作为持有 - - 过程 - 函数 - -对象的局部状态变量的位置 -W1. - - - - 图 - - - 图 - - -显示了调用后的情况 -W1. - - - - 图 - - - 图 - - -显示了调用后的情况 - - -
-
- - 调用 - W1 - W1 - - 后的环境。 - - -
- - -
-
- - 调用后产生的环境。 - - -
-
-
-
- - \pagebreak - - -观察创建第二个取款对象时会发生什么,通过再次调用 - - make_withdraw: - make_withdraw: - - - - make_withdraw2 - make_withdraw2_w2_declare - 20 - -(define W2 (make-withdraw 100)) - - -const W2 = make_withdraw(100); - - -const W1 = make_withdraw(100); -W1(50); -const W2 = make_withdraw(100); -W2(80); - - -这产生了 - - - 图, - - - 图, - - -的环境结构,显示了 -W2 是一个过程函数对象,即,一个包含一些代码和一个环境的对。环境 E2【152:8†cn.txt】W2 是由调用 make-withdraw. make_withdraw. 创建的。它包含一个帧,其中具有自己的局部绑定 【156:0†cn.txt】。balance -这产生了 - - - 图, - - - 图, - - -的环境结构,显示了它是一个包含一些代码和环境的 - 过程 - 函数 -对象。环境 E2 是由调用 make-withdraw. make_withdraw. 创建的。它包含一个帧,其中具有自己的局部绑定。另一方面, 【152:8†cn.txt】 【156:0†cn.txt】 【160:0†cn.txt】W1W2 拥有相同代码:由 lambda lambda make-withdraw</SCHEMEINLINE>make_withdraw 的主体中指定的代码。这是一个实现细节,无论 W1 和 W2 是否共享计算机中相同的物理代码, 还是每个保留一份代码的副本。在我们在章节 中实现的解释器中,代码实际上是共享的。在这里我们看到为什么【168:0†cn.txt】。W1 具有相同的代码:即 lambda lambda make-withdraw</SCHEMEINLINE>make_withdraw 主体中指定的代码。在章节 中实现说明性知识通过共享方式来实现。这是一个实现细节,无论 W1 和 W2 是否共享代码 【172:0†cn.txt】。W2 -作为独立对象行为。调用 -W1 引用状态变量balance 存储在 E1,而引用 【184:0†cn.txt】。W2 引用 balance 存储在 E2。因此,一个对象的局部状态的变化不会影响另一个对象。 - - -
-
- - 使用(define W2 (make-withdraw 100))来创建第二个对象。 - - -
- - -
-
- - 使用const W2 = make_withdraw(100);来创建第二个对象。 - - -
-
-
-
- - - - 存储在 E2。因此,一个对象的局部状态的变化不会影响另一个对象。 - - -
-
- - 使用(define W2 (make-withdraw 100))来创建第二个对象。 - - -
- - -
-
- - 使用const W2 = make_withdraw(100);来创建第二个对象。 - - -
-
-
-balance -作为 - make-withdraw的参数创建。 - make_withdraw的参数创建。 - - -我们也可以使用 - 显式地 - 分别地, - -使用 - let, - 我们称为lambda 表达式立即调用立即调用 lambda 表达式立即调用 lambda 表达式 - -创建局部状态变量,如下所示: - - make_withdrawusing immediately invoked lambda expression - make_withdraw3 - make_withdraw3_example - 20 - -(define (make-withdraw initial-amount) - (let ((balance initial-amount)) - (lambda (amount) - (if (>= balance amount) - (begin (set! balance (- balance amount)) - balance) - "Insufficient funds")))) - - -function make_withdraw(initial_amount) { - return (balance => - amount => { - if (balance >= amount) { - balance = balance - amount; - return balance; - } else { - return "insufficient funds"; - } - })(initial_amount); -} - - - - make_withdraw3_example - -const W1 = make_withdraw(100); -W1(80); - - - - - - 回忆在章节中,let语法糖作为语法糖let求值模型let只是语法糖,类似于一个 过程函数 的调用: - - -(let ((var exp)) body) - - - 被诠释为 - -((lambda (var) body) exp) - - 的替代语法。 - - - 外部 lambda 表达式在评估后立即调用。其唯一目的是创建一个局部变量balance并将其初始化为initial_amount。 - - -使用环境模型来分析这个替代版本 【204:0†source】【204:5†source】【204:6†source】。make_withdraw ,绘制如上图像以说明交互 - make_withdraw3_example_2 - make_withdraw3 - 20 - -(define W1 (make-withdraw 100)) - -(W1 50) - -(define W2 (make-withdraw 100)) - - -const W1 = make_withdraw(100); - -W1(50); - -const W2 = make_withdraw(100); - - -const W1 = make_withdraw(100); -W1(50); -const W2 = make_withdraw(100); -W2(80); - - -显示两个版本的 - - make-withdraw - make_withdraw - -创建具有相同行为的对象。这两个版本的环境结构有何不同? - -
    -
  • 黑色显示练习 3.10 中函数的环境结构
  • -
  • 绿色显示原始环境结构的差异(其中make_withdraw被替换为图 3.9 版本)
  • -
-
- -
-
-
- - 帧 (环境模型)作为局部状态的存储库 - 作为 - make-withdraw - make_withdraw - -的参数创建。我们也可以创建局部状态变量为 - 显式地 - 分别地 - -使用 - let - - ,即立即调用 lambda 表达式 - - -如下: - - 求值的环境模型局部状态 - make_withdrawenvironment在环境模型中 -
diff --git a/xml/cn/chapter3/section2/subsection4.xml b/xml/cn/chapter3/section2/subsection4.xml deleted file mode 100644 index 4de37a099..000000000 --- a/xml/cn/chapter3/section2/subsection4.xml +++ /dev/null @@ -1,619 +0,0 @@ - - - - 内部 - - 定义 - 声明 - - - - - 块结构环境在环境模型中 - 求值的环境模型内部声明 - 内部声明环境在环境模型中 - - - - - 在本节中,我们处理包含声明的函数体或其他块(例如条件语句的分支)的求值。每个块为块中声明的名称开启一个新的作用域。为了在给定的环境中求值一个块,我们通过一个新的框架扩展该环境,该框架包含在块体中直接声明的所有名称(即,不在嵌套块内),然后在新构建的环境中求值该块体。 - - - - - 第 节介绍了 - - 过程 - 函数 - - 可以有内部 - - - 定义 - - - 声明 - - - ,从而形成一个块结构,如在 - sqrt环境在环境模型中 - 计算平方根的下面的 - - 过程 - 函数 - - : - - another_sqrt - abs_definition - square_definition - average_definition - sqrt_example7 - 2.2360688956433634 - -(define (sqrt x) - (define (good-enough? guess) - (< (abs (- (square guess) x)) 0.001)) - (define (improve guess) - (average guess (/ x guess))) - (define (sqrt-iter guess) - (if (good-enough? guess) - guess - (sqrt-iter (improve guess)))) - (sqrt-iter 1.0)) - - -function sqrt(x) { - function is_good_enough(guess) { - return abs(square(guess) - x) < 0.001; - } - function improve(guess) { - return average(guess, x / guess); - } - function sqrt_iter(guess){ - return is_good_enough(guess) - ? guess - : sqrt_iter(improve(guess)); - } - return sqrt_iter(1); -} - - - 现在我们可以使用环境模型来理解这些内部 - - - 定义 - - - 声明 - - - 是如何按预期行为的。 - - - 图 - - - 图 - - - 显示在求值表达式 - - (sqrt 2) - sqrt(2) - - 时,内部 - - 过程 - 函数 - - - good-enough? - is_good_enough - - 首次被调用的时刻。 -guess 等于1. - - -
-
- - sqrt 过程含有内部定义。 - - -
- - -
-
- The - sqrt 函数含有内部声明。 - - -
-
-
-
- - - 观察环境的结构。 - - - Sqrt 是全局环境中的一个符号,被绑定为 - - - 名称 - sqrt 已在程序环境中绑定 - - - 到一个 - - 过程 - 函数 - - 对象,其关联的环境是 - - 全局 - 程序 - - 环境。当 -sqrt 被调用时,一个新的环境 E1 被形成,隶属于 - - 全局 - 程序 - - 环境,其中参数 x 被绑定到2。身体的 sqrt 然后在E1 中求值。 - - - 由于sqrt体中的第一个表达式是 - - - 该段是一个包含局部函数声明的块,因此 E1 被扩展为一个新的帧用于这些声明,结果形成了一个新环境 E2。块体随后在 E2 中求值。因为体中的第一条语句是 - - - - abs_definition - square_definition - -(define (good-enough? guess) - (< (abs (- (square guess) x)) 0.001)) - - -function is_good_enough(guess) { - return abs(square(guess) - x) < 0.001; -} - - - - - 求值这个表达式定义了在环境E1 中的过程 - good-enough?。 - - - 求值这个声明在环境E2 中创建了函数 - is_good_enough。 - - - - - 更精确地说,符号 - good-enough? 被添加到 E1 的第一个帧, - 与一个过程对象绑定,其关联的环境是 E1。 - - - 更精确地说, - 名称 is_good_enough - 在 E2 的第一个帧中被绑定到一个函数 - 对象,其关联的环境是 E2。 - - - 类似地, - improve and - - sqrt-iter - sqrt_iter - - were defined as - - procedures in E1. - functions in E2. - - For conciseness, - - - figure - - - figure - - - shows only the - - procedure - function - - object for - - good-enough?. - is_good_enough. - - - - - 在本地 - - 过程 - 函数 - - 被定义之后,表达式 - - (sqrt-iter 1.0) - sqrt_@iter(1) - - - 仍在环境 - - E1 - E2 - - 中求值。因此在 - - E1 中绑定到的过程对象 - E2 中绑定到的函数对象 - - - sqrt-iter - 以 1 作为参数被调用。 这创建了一个新的环境 E2,其中 - - sqrt_@iter - 以 1 作为参数被调用。这创建了一个新的环境 E3,其中 - - - guesssqrt-iter,sqrt_@iter,的参数被绑定到 1。sqrt-iter函数sqrt_@iter反过来调用了good-enough?is_@good_@enough,并使用这个值。 guess - - - (来自 E2) 作为 good-enough? 的参数。 - - - (来自 E3) 作为 is_@good_@enough 的参数。 - - - 这设置了另一个环境, - - - E3,其中guess (good-enough? 的参数) - - - E4,其中guess (is_@good_@enough 的参数) - - - 被绑定到 1。虽然 - - sqrt-iter - sqrt_@iter - - 和 - - good-enough? - is_@good_@enough - - 都有一个名称为 -guess ,这些是位于不同帧中的两个独立局部变量。 - - - 此外,E2 和 E3 都以 E1 作为它们的外层环境,因为 - sqrt-iter 和 - good-enough? 过程 - 都以 E1 作为它们的环境部分。 - - - 此外,E3 和 E4 都以 E2 作为它们的外层环境,因为 - sqrt_@iter - 和 - is_@good_@enough 函数 - 都以 E2 作为它们的环境部分。 - - - 这一结果的一个后果是, - - - 符号 - - - 名称 - - - x ,出现在 - - good-enough? - is_@good_@enough - - 函数体中的符号将引用 -x ,出现在 E1 中,即值为 x ,其原始的 sqrt - - 过程 - 函数 - - 被调用。 - sqrt环境在环境模型中 - - - - 因此,环境模型解释了使得本地 - - 过程定义 - 函数声明 - - 成为模块化程序的一种有用技术的两个关键属性: -
    -
  • - 本地 - - 过程 - 函数 - - 的名称不会干扰 - 外部于封闭 - - 过程, - 函数, - - 的名称,因为本地 - - 过程 - 函数 - - 的名称将被绑定在 - - 过程运行时创建的帧中, - 块求值时创建的帧中, - - 而不是在 - - 全局 - 程序 - - 环境中。 -
  • -
  • - 本地 - - 过程 - 函数 - - 可以访问封闭 - - 过程, - 函数, - - 的参数,只需使用参数名称作为自由 - - 变量。 - 名称。 - - 这是因为本地 - - 过程 - 函数 - - 的体是在一个从属于封闭 - - 过程 - 函数。 - - 的求值环境中进行求值的。 -
  • -
-
- - 在第节中,我们看到环境模型如何描述具有局部状态的 - - 过程 - 函数 - - 的行为。现在我们了解了内部 - - - 定义 - - - 声明 - - - 的工作原理。 - 求值的环境模型消息传递 - 消息传递环境模型与 - 一个典型的消息传递 - - 过程 - 函数 - - 包含这两个方面。考虑第节中的 - 银行账户 - 银行账户 - - 过程 - 函数 - - : - - - make_accountenvironmentin environment model - another_make_account - another_make_account_example - 30 - -(define (make-account balance) - (define (withdraw amount) - (if (>= balance amount) - (begin (set! balance (- balance amount)) - balance) - "Insufficient funds")) - (define (deposit amount) - (set! balance (+ balance amount)) - balance) - (define (dispatch m) - (cond ((eq? m 'withdraw) withdraw) - ((eq? m 'deposit) deposit) - (else (error "Unknown request - - MAKE-ACCOUNT" - m)))) - dispatch) - - -function make_account(balance) { - function withdraw(amount) { - if (balance >= amount) { - balance = balance - amount; - return balance; - } else { - return "Insufficient funds"; - } - } - function deposit(amount) { - balance = balance + amount; - return balance; - } - function dispatch(m) { - return m === "withdraw" - ? withdraw - : m === "deposit" - ? deposit - : error(m, "Unknown request: make_account"); - } - return dispatch; -} - - - 显示由一系列互动生成的环境结构 - - another_make_account_example - - (define acc (make-account 50)) - - -const acc = make_account(50); - - -const acc = make_account(50); -acc("withdraw")(20); - - - - another_make_account_example_2 - - ((acc 'deposit) 40) - - - 90 - - -acc("deposit")(40); - - -90 - - - - another_make_account_example_3 - - ((acc 'withdraw) 60) - - - 30 - - -acc("withdraw")(60); - - -30 - - - 局部状态在哪里 -acc 保留?假设我们定义了另一个账户 - - (define acc2 (make-account 100)) - - -const acc2 = make_account(100); - - - 两个账户的局部状态如何保持独立?环境结构中哪些部分是共享的? -accacc2 ? - - - 更多关于块 - - - 如我们所见,在 - sqrt 中声明的名称的作用域是整个 - sqrt 的主体。这解释了为什么 - 相互递归 - 递归相互 - 相互递归能够工作,如这种(相当浪费的)检查非负整数是否为偶数的方法。 - - f_is_even_is_odd - -function f(x) { - function is_even(n) { - return n === 0 - ? true - : is_odd(n - 1); - } - function is_odd(n) { - return n === 0 - ? false - : is_even(n - 1); - } - return is_even(x); -} - - - 当 - is_even 在调用 - f 时被调用,环境图看起来像 - sqrt_iter 被调用时图中的那样。函数 - is_even 和 - is_odd 在 E2 中被绑定到指向 E2 的函数对象作为评估这些函数调用的环境。因此 - is_odd 在 - is_even 的体内指的是正确的函数。虽然 - is_odd - 是在 - is_even 之后定义的, - 这与 - sqrt_iter 的体内的名称 - improve - 和名字 - sqrt_iter - 本身指的是正确的函数没有区别。 - - - 有了一种处理块内声明的方法,我们可以 - 重新审视顶层的名称声明。在第节中,我们看到 - 顶层声明的名称被添加到程序 - 帧中。一个更好的解释是整个程序被放置在一个 - 隐含块中,该块在全局环境中求值。 - 上述块的处理方式然后处理了顶层: - 全局环境通过一个包含所有在隐含块中声明的名称绑定的帧来扩展。该帧是 - 程序帧,产生的 - 环境是 - 程序环境 - 程序环境。 - - - 我们说过,一个块的主体是在包含块体内直接声明的所有名称的环境中求值的。 - 在进入块时,局部声明的名称被放入环境中,但没有关联的值。在块主体求值期间对其声明的求值,然后将表达式右侧的求值结果赋值给该名称,就像声明是一个赋值一样。由于名称添加到环境中与声明的求值是分开的,并且整个块都在名称的作用域中,一个错误的程序可能会尝试 - 声明在使用名称之前 - 在其声明求值之前访问名称的值; - 对未赋值名称的求值会引发错误。 - - 这解释了为什么在第 1 章中的脚注中程序会出现问题。 - 从为名称创建绑定到对名称的声明进行求值之间的时间称为 - 时间死区(TDZ) - TDZ(时间死区) - 时间死区(TDZ)。 - - 求值的环境模型 - 块结构环境在环境模型中 - 求值的环境模型内部声明 - 内部声明环境在环境模型中 -
diff --git a/xml/cn/chapter3/section3/subsection1.xml b/xml/cn/chapter3/section3/subsection1.xml deleted file mode 100644 index d081d557c..000000000 --- a/xml/cn/chapter3/section3/subsection1.xml +++ /dev/null @@ -1,1917 +0,0 @@ - - - 可变数据对象 (列表结构) - - - - 可变数据对象列表结构 - 列表结构可变 - 可变数据对象序对流生成函数 - 序对可变 - - - 基本操作在 - - - 序对cons, - - - 序对pair, - - - - car, - head, - - 和 - - cdr可以 - tail可以 - - - 用于构建列表结构并从列表结构中选择部分,但它们无法修改列表结构。同样地,我们迄今所用的列表操作,例如append和 - list,因为这些可以用 - - cons, - pair, - - - car, - head, - - 和 - - cdr. - tail. - - 要修改列表结构,我们需要新的操作。 - - - - 序对的基本修改器是 - set_head (原语函数) - set_head (\textit{ns}) - - set-car! - set_head - - 和 - set_tail (原语函数) - set_tail (\textit{ns}) - - set-cdr!. - set_tail. - - - Set-car! - 函数 set_head - - - 接受两个参数,第一个必须是序对。它修改这个序对,将 - - car - head - - 指针替换为指向 - set-car!set_head的第二个参数的指针。 - - - Set-car! 和 - set-cdr! 返回的是 - 特定于实现的 - set-car! (原语函数)的值 - 未指定值set-carset-car! - set-cdr! (原语函数)的值 - 未指定值set-cdrset-cdr! - 的值。像set!一样,它们应该仅用于其效果。 - - - 函数 set_head 和 - set_tail 返回值 - undefined。 - set_head (原语函数)的值 - set_tail (原语函数)的值 - 它们应该仅用于其效果。 - - - - - - 举例来说,假设 -x 被绑定为 - - - 列表为 - ((a b) c d) - - - list(list("a", "b"), "c", "d") - - - 和 y 绑定到 - - - 列表 - (e f) - - list("e", "f") - - - 如图所示 - - - 图. - - - 图. - - - 计算表达式 - - (set-car! x y) - - set_head(x, y) - - 修改绑定到的序对 x 被绑定, - 将其 - - car - head - - 替换为 y 。操作的结果显示在 - - - 图. - - - 图. - - - 结构 x 已被修改,现在 - - - 会被打印为 - ((ef)cd)。 - - - 现在等同于 - list(list("e", "f"), "c", "d")。 - - 表示列表的序对 - - (a b), - list("a", "b"), - - - 由被替换的指针表示,现在已从原始结构中分离出来。从中我们可以看到,对列表的修改操作可能会产生不属于任何可访问结构的垃圾。我们将在部分看到, - - Lisp - JavaScript - - 内存管理系统包含一个 - 垃圾回收突变与 - 垃圾回收器,用于识别并回收不需要的序对所占用的内存空间。 - - - - -
-
- - 列表 x: - ((a b) c d) 和 - y: (e f). - - -
- - -
-
- - 列表 x: - list(list("a", "b"), "c", "d") - 和 y: - list("e", "f"). - - -
-
-
- - -
-
- - (set-car! x y) 对图中列表的影响。 - - -
- - -
-
- - set_head(x, y) 对图中列表的影响。 - - -
-
-
- - -
-
- - (define z (cons y (cdr x))) 对图中列表的影响。 - - -
- - -
-
- - const z = pair(y, tail(x)); 对图中列表的影响。 - - -
-
-
- - -
-
- - (set-cdr! x y) 对图中列表的影响。 - - -
- - -
-
- - set_tail(x, y) 对图中列表的影响。 - - -
-
-
- - - - 比较 - - - 图 - - - 图 - - - 和 - - - 图, - - - 图, - - - 哪个说明了执行的结果 - - mutable_list_example - -(define x '((a b) c d)) -(define y '(e f)) - - -const x = list(list("a", "b"), "c"); -const y = list("e", "f"); - - - - mutable_list_example - -(define z (cons y (cdr x))) - - -const z = pair(y, tail(x)); - - - 其中x和y绑定到了原始列表 - - - 图。 - - - 图。 - - - 这个 - - - 变量 - - - 名称 - - - z 现在绑定到了一个 - 由 - cons - pair - - 操作创建的新序对;x绑定的列表未被改变。 - - - - - set-cdr! - set_tail - - 操作与 - - set-car!. - set_head. - - 类似。唯一的区别是 - - cdr - tail - - 指针而不是 - - car - head - - 指针被替换。执行 - - (set-cdr! x y) - set_tail(x, y) - - - 在 - - - 图 - - - 图 - - - 列表上的效果显示在 - - - 图。 - - - 图。 - - - 这里 - - cdr - tail - - 指针 - x 被替换为指向 - - (e f)。 - list("e", "f")。 - - - 同时,列表 - - (c d), - list("c", "d"), - - - 曾是 - - cdr - tail - - 的 x ,现在从结构中分离出来。 - - - - - Cons - 函数 pair - - - 通过创建新的序对来构建新的列表结构, - - 而 set-car! - set_@head - - 和 - - set-cdr! - set_tail - - 修改现有的序对。 - 实际上,我们可以 - pair (原语函数)用修改器实现 - 用两个修改器和一个 - 过程函数 - - get-new-pair, - get_new_pair, - - 实现 - - cons - pair - - ,它返回一个不属于任何现有列表结构的新序对。 - 我们获得这个新序对,设置其 - - car - head - - 和 - - cdr - tail - - 指针指向指定的对象,并返回作为 - conspair - 的结果的新序对。 - - - Get-new-pair - 是Lisp实现所需内存管理的一部分操作。 - 我们将在部分讨论这一点。 - - - 部分 - 将展示内存管理系统如何实现get_new_pair。 - - - - - - get_new_pair - - - -// The book proposes a primitive function get_new_pair. -// Since JavaScript does not provide such a function, let's -// define it as follows, for the sake of the example. - -function get_new_pair() { - return pair(undefined, undefined); -} -{ - - - - pair (primitive function)implemented with mutators - get_new_pair - mutable_pair_example - [ [ 1, 2 ], 4 ] - -(define (cons x y) - (let ((new (get-new-pair))) - (set-car! new x) - (set-cdr! new y) - new)) - - -function pair(x, y) { - const fresh = get_new_pair(); - set_head(fresh, x); - set_tail(fresh, y); - return fresh; -} - - - - - mutable_pair_example - - - -pair(pair(1, 2), 4); -} - - - - - - - 以下是追加列表的 - - 过程 - 函数 - - 在节中介绍: - - append_example3 - 9 - -append(list(1, 3), list(5, 7, 9)); - - -list_ref(append(list(1, 3), list(5, 7, 9)), 4); - - - - 以下 - - 过程 - 函数 - - 用于追加列表在部分中介绍: - - append2 - append_example3 - 9 - -(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)); -} - - - - Append - 函数 append - - - 通过连续地 - - - cons将 - x的元素加入到 - y中。 - - - 将x的元素添加到 - y的前面。 - - - 这个 - - 过程 - 函数 - - appendappend 变异器append_mutator vs. - - append! - append_mutator - - - 类似于 append ,但是它是一个修改器 - 而不是构造器。它通过将列表拼接在一起来附加列表, - 修改 x ,现在变为 - - cdr - tail - - 。 y 。(调用 - - append! - append_mutator - - - 带空是错误 x。) - - append_mutator - append_mutator - last_pair - -(define (append! x y) - (set-cdr! (last-pair x) y) - x) - - -function append_mutator(x, y) { - set_tail(last_pair(x), y); - return x; -} - - - 这里 - - last-pair - last_pair - - 是一个 - - 过程 - 函数 - - ,返回其参数中的最后一个序对: - - last_pair_example_2 - -last_pair(list(1, 2, 3, 4, 5)); - - - - last_pair - last_pair - last_pair_example_2 - [ 5, null ] - -(define (last-pair x) - (if (null? (cdr x)) - x - (last-pair (cdr x)))) - - -function last_pair(x) { - return is_null(tail(x)) - ? x - : last_pair(tail(x)); -} - - - 考虑交互 - - append_interaction_1 - append2 - append_mutator - -(define x (list 'a 'b)) - - -const x = list("a", "b"); - - - - append_interaction_2 - append_interaction_1 - -(define y (list 'c 'd)) - - -const y = list("c", "d"); - - - - append_interaction_3 - append_interaction_2 - -(define z (append x y)) - - -const z = append(x, y); - - - - append_interaction_4 - append_interaction_3 - -z - - -(a b c d) - - -z; - - -["a", ["b", ["c", ["d, null]]]] - - - - append_interaction_5 - append_interaction_4 - -(cdr x) - - -<response> - - -tail(x); - - -response - - - - append_interaction_6 - append_interaction_5 - -(define w (append! x y)) - - -const w = append_mutator(x, y); - - - - append_interaction_7 - append_interaction_6 - -w - - -(a b c d) - - -w; - - -["a", ["b", ["c", ["d", null]]]] - - - - append_interaction_8 - append_interaction_7 - -(cdr x) - - -<response> - - -tail(x); - - -response - - - 缺少的response是什么? - 画出方框与指针图来解释你的答案。 - - - - 考虑以下 - 列表中的循环 - - make-cycle - make_cycle - - - 过程, - 函数, - - 它使用 - - - last-pair - - - last_pair - - - - 过程 - 函数 - - 在练习中定义: - - make_cycle - make_cycle - last_pair - make_cycle_example - -(define (make-cycle x) - (set-cdr! (last-pair x) x) - x) - - -function make_cycle(x) { - set_tail(last_pair(x), x); - return x; -} - - - 画出一个方框与指针图显示结构 -z - 由...创建的 - - make_cycle_example - make_cycle - 'b' - -(define z (make-cycle (list 'a 'b 'c))) - - -const z = make_cycle(list("a", "b", "c")); - - -const z = make_cycle(list("a", "b", "c")); -list_ref(z, 100); - - - 如果我们尝试计算 - - (last-pair z)? - last_pair(z)? - - - - - (由GitHub用户jonathantorres提供) - 如果我们尝试计算last_pair(z),程序将进入一个无限循环,因为列表的末尾指向开头。 - -
- -
- -
-
- - - 以下 - - 过程 - 函数 - - 是非常有用的,尽管比较晦涩: - - mystery - mystery_loop - mystery_loop_example_1 - mystery_loop_example_2 - -(define (mystery x) - (define (loop x y) - (if (null? x) - y - (let ((temp (cdr x))) - (set-cdr! x y) - (loop temp x)))) - (loop x '())) - - -function mystery(x) { - function loop(x, y) { - if (is_null(x)) { - return y; - } else { - const temp = tail(x); - set_tail(x, y); - return loop(temp, x); - } - } - return loop(x, null); -} - - - - Loop - 函数 loop - - - 使用临时 - - - 变量 - - - 名称 - - -temp - 用于保存 - - cdr - tail - - 的旧值 -x,因为下一行的 - - set-cdr! - set_tail - - 破坏了 - - cdr。 - tail - - 解释什么 -mystery - 通常是做什么的。假设 -v - 由...定义为 - - mystery_loop_example_1 - -(define v (list 'a 'b 'c 'd)) - - -const v = list("a", "b", "c", "d"); - - - 画出一个方框与指针图来表示该列表 -v - 绑定到。假设我们现在计算 - - mystery_loop_example_2 - mystery_loop - -(define w (mystery v)) - - -const w = mystery(v); - - - 画出方框与指针图来展示结构 -vw - 在评估此 - - 表达式 - 程序 - - 后,展示结构的方框与指针图。什么将被打印为的值 -vw ? - - - (由GitHub用户jonathantorres提供) - 应用mystery(x)将原地反转列表x。 - 最初 - v看起来像这样: - -
- -
- - 计算 - const w = mystery(v); - 后 - v 和 - w 的值变为: - -
- -
- - 函数display - 打印["a", null]为 - v 和 - ["d", ["c", ["b", ["a", null]]]]为 - w。 -
-
- - 可变数据对象列表结构 - list structuremutable - 可变数据对象序对 - 序对可变 - - - 共享和同一性 - - - 数据共享 - 共享数据 - - 共享数据 - pair_example1 - -(define x (list 'a 'b)) -(define z1 (cons x x)) - - -const x = list("a", "b"); -const z1 = pair(x, x); - - - 如图所示 - - - 图, - - - 图, - - -z1 可变数据对象序对流生成函数 x 。这种共享 -x 由 - - car - head - - 和 - - cdr - tail - - 指向 -z1 这是以 - - cons - pair - - 被简单实现的结果。通常,使用 - - cons - pair - - 构建列表会导致许多单独的序对在多个不同的结构中共享的序对的连接结构。 - - -
-
- - 列表z1由 - (cons x x)形成。 - - -
- - -
-
- - 列表z1由 - pair(x, x)形成。 - - -
-
-
- - -
-
- - 列表z2由 - (cons (list 'a 'b) (list 'a 'b))形成。 - - -
- - -
-
- - 列表z2由 - pair(list("a", "b"), list("a", "b"))形成。 - - -
-
-
-
- - - 与 - - - 图相比, - 图 - - - 图相比, - 图 - - - 显示的是由...创建的结构 - - pair_example2 - -(define z2 (cons (list 'a 'b) (list 'a 'b))) - - -const z2 = pair(list("a", "b"), list("a", "b")); - - - In this structure, the pairs in the two - - (a b) - list("a", "b") - - - lists are distinct, although - - - the actual symbols are shared. - The two pairs are distinct because each call to - cons returns a new pair. The - symbols are shared; in Scheme there is a - symbol(s)uniqueness of - unique symbol with any given - name. Since Scheme provides no way to mutate a symbol, this sharing is - undetectable. Note also that the sharing is what enables us to - compare symbols using eq?, which simply checks equality of - pointers. - - - they contain the same strings.The two pairs are distinct - because each call to pair - returns a new pair. The strings are - string(s)uniqueness of - the same in the sense - that they are primitive data (just like numbers) that are composed of - the same characters in the same order. Since JavaScript provides no way - to mutate a string, any sharing that the designers of a JavaScript - interpreter might decide to implement for strings is undetectable. - We consider primitive data such as numbers, booleans, and strings - to be identical if and only if they are - indistinguishable. - - - - - - - When thought of as a list, z1 and - z2 both represent the same list: - - abab - [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] - -((a b) a b) - - -list(list("a", "b"), "a", "b") - - -list(list("a", "b"), "a", "b"); - - - In general, sharing is completely undetectable if we operate on lists using - only - - cons, - pair, - - - car, - head, - - and - - cdr. - tail. - - However, if we allow mutators on list structure, sharing becomes - significant. As an example of the difference that sharing can make, - consider the following - - procedure, - function, - which modifies the - - car - head - - of the structure to which it is applied: - - set_to_wow - -(define (set-to-wow! x) - (set-car! (car x) 'wow) - x) - - -function set_to_wow(x) { - set_head(head(x), "wow"); - return x; -} - - - Even though z1 and - z2 are the same structure, - applying - - set-to-wow! - set_to_wow - - to them yields different results. With z1, - altering the - - car - head - - also changes the - - cdr, - tail, - - because in z1 the - - car - head - - and the - - cdr - tail - - are the same pair. With z2, the - - car - head - - and - - cdr - tail - - are distinct, so - - set-to-wow! - set_to_wow - - modifies only the - - car: - head: - - - set_to_wow_example_1 - pair_example1 - [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] - -z1 - - - ((a b) a b) - - -z1; - - -[["a", ["b", null]], ["a", ["b", null]]] - - - - set_to_wow_example_2 - set_to_wow - pair_example1 - [ [ 'wow', [ 'b', null ] ], [ 'wow', [ 'b', null ] ] ] - -(set-to-wow! z1) - - -((wow b) wow b) - - -set_to_wow(z1); - - -[["wow", ["b", null]], ["wow", ["b", null]]] - - - - set_to_wow_example_3 - pair_example2 - [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] - -z2 - - -((a b) a b) - - -z2; - - > -[["a", ["b", null]], ["a", ["b", null]]] - - - - set_to_wow_example_4 - set_to_wow - pair_example2 - [ [ 'wow', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] - -(set-to-wow! z2) - - -((wow b) a b) - - -set_to_wow(z2); - - -[["wow", ["b", null]], ["a", ["b", null]]] - - - - - - - - One way to detect sharing in list structures is to use the predicate - eq?, which we introduced in - section as a way to test whether two - symbols are equal. More generally, (eq? x y) - tests whether x and - y are the same object (that is, whether - x and y - are equal as pointers). - - - One way to detect sharing in list structures is to use the - ===equalityas equality of pointers - ===generalas general comparison operator - === (for nonprimitive values) - primitive predicate ===, - which we introduced in - section to test whether two numbers - are equal - and extended in section to test whether - two strings are equal. When applied to two nonprimitive values, - x === y - tests whether x and - y are the same object (that is, whether - x and y - are equal as pointers). - - - Thus, with z1 and - z2 as defined in - - - figure - and, - - - figure - and, - - - - (eq? (carz1) (cdrz1)) - - head(z1) === tail(z1) - - - is true and - - - (eq? (car z2) (cdr z2)) - head(z2) === tail(z2) - - - is false. - - - - As will be seen in the following sections, we can exploit sharing to - greatly extend the repertoire of data structures that can be - represented by pairs. On the other hand, sharing can also be - mutable data objectsshared data - dangerous, since modifications made to structures will also affect - other structures that happen to share the modified parts. The mutation - operations - - set-car! - set_head - - and - - set-cdr! - set_tail - - should be used with care; unless we have a good understanding of how our - data objects are shared, mutation can have unanticipated - results.The subtleties of dealing with sharing of mutable data - objects reflect the underlying issues of sameness and - change that were raised in - section. We mentioned there - that admitting change to our language requires that a compound object must - have an identity that is something different from the pieces - from which it is composed. In - - Lisp, - JavaScript, - - we consider this identity to be the quality that is tested by - eq?, - ===, - - i.e., by equality of pointers. Since in most - - Lisp - JavaScript - - implementations a pointer is essentially a memory address, we are - solving the problem of defining the identity of objects by - stipulating that a data object itself is the information - stored in some particular set of memory locations in the computer. This - suffices for simple - - Lisp - JavaScript - - programs, but is hardly a general way to resolve the issue of - sameness in computational models. - - - - - Draw box-and-pointer diagrams to explain the effect of - - set-to-wow! - set_to_wow - - on the structures z1 and - z2 above. - - - - - Ben Bitdiddle decides to write a - - procedure - function - - to count the number of pairs in any list structure. - Its easy, he reasons. The number of pairs in - any structure is the number in the - - car - head - - plus the number in the - - cdr - tail - - plus one more to count the current pair. So Ben writes the following - - procedure: - function - - - count_pairs - count_pairs - -(define (count-pairs x) - (if (not (pair? x)) - 0 - (+ (count-pairs (car x)) - (count-pairs (cdr x)) - 1))) - - -function count_pairs(x) { - return ! is_pair(x) - ? 0 - : count_pairs(head(x)) + - count_pairs(tail(x)) + - 1; -} - - - Show that this - - procedure - function - - is not correct. In particular, draw box-and-pointer diagrams representing - list structures made up of exactly three pairs for which Bens - - procedure - function - - would return3; return 4; return 7; never return at all. - - - - exercise_3_16_solution - count_pairs - exercise_3_16_solution_example - - - -const three_list = list("a", "b", "c"); -const one = pair("d", "e"); -const two = pair(one, one); -const four_list = pair(two, "f"); -const seven_list = pair(two, two); -const cycle = list("g", "h", "i"); -set_tail(tail(tail(cycle)), cycle); - - - - - exercise_3_16_solution_example - -// return 3; return 4; return 7; -display(count_pairs(three_list)); -display(count_pairs(four_list)); -display(count_pairs(seven_list)); - -// never return at all -display(count_pairs(cycle)); - - - - - - - - Devise a correct version of the - - count-pairs - count_pairs - - - procedure - function - - of exercise that returns the number of - distinct pairs in any structure. (Hint: Traverse the structure, maintaining - an auxiliary data structure that is used to keep track of which pairs have - already been counted.) - - - exercise_3_17_solution_example - -// solution provided by GitHub user clean99 - -function count_pairs(x) { - let counted_pairs = null; - function is_counted_pair(current_counted_pairs, x) { - return is_null(current_counted_pairs) - ? false - : head(current_counted_pairs) === x - ? true - : is_counted_pair(tail(current_counted_pairs), x); - } - function count(x) { - if(! is_pair(x) || is_counted_pair(counted_pairs, x)) { - return 0; - } else { - counted_pairs = pair(x, counted_pairs); - return count(head(x)) + - count(tail(x)) + - 1; - } - } - return count(x); -} - - - - exercise_3_17_solution_example - -const three_list = list("a", "b", "c"); -const one = pair("d", "e"); -const two = pair(one, one); -const four_list = pair(two, "f"); -const seven_list = pair(two, two); -const cycle = list("g", "h", "i"); -set_tail(tail(tail(cycle)), cycle); - -// return 3; return 3; return 3; -display(count_pairs(three_list)); -display(count_pairs(four_list)); -display(count_pairs(seven_list)); - -// return 3 -display(count_pairs(cycle)); - - - - - - - Write a - - procedure - function - - that examines a list and - cycle in listdetecting - determines whether it contains a cycle, that is, - whether a program that tried to find the end of the list by taking - successive - - cdrs - tails - - would go into an infinite loop. Exercise - constructed such lists. - - - - exercise_3_18_solution_example - -// solution provided by GitHub user clean99 - -function contains_cycle(x) { - let counted_pairs = null; - function is_counted_pair(counted_pairs, x) { - return is_null(counted_pairs) - ? false - : head(counted_pairs) === x - ? true - : is_counted_pair(tail(counted_pairs), x); - } - function detect_cycle(x) { - if (is_null(x)) { - return false; - } else if (is_counted_pair(counted_pairs, x)) { - return true; - } else { - counted_pairs = pair(x, counted_pairs); - return detect_cycle(tail(x)); - } - } - return detect_cycle(x); -} - - - - exercise_3_18_solution_example - -const three_list = list("a", "b", "c"); -const cycle = list("g", "h", "i"); -set_tail(tail(tail(cycle)), cycle); - -// displays false -display(contains_cycle(three_list)); - -// displays true -display(contains_cycle(cycle)); - - - - - - - - Redo exercise using an algorithm that - takes only a constant amount of space. (This requires a very clever idea.) - - pair2_example - -const x = pair(1, 2); -set_head(x, 3); -head(x); - - - - Define a fast pointer and a slow pointer. The fast pointer goes forward - 2 steps every time, while the slow pointer goes forward 1 step every time. - If there is a cycle in the list, the fast pointer will eventually catch - up with the slow pointer. - - contains_cycle_example - make_cycle - -const c = make_cycle(list("c", "d", "e")); -const c1 = append(list("a", "b"), c); - -contains_cycle(c1); - - - - contains_cycle - contains_cycle_example - -function contains_cycle(x) { - function detect_cycle(fast, slow) { - return is_null(fast) || is_null(tail(fast)) - ? false - : fast === slow - ? true - : detect_cycle(tail(tail(fast)), tail(slow)); - } - return detect_cycle(tail(x), x); -} - - - - - - datashared - shared data - - - Mutation is just assignment - - - mutable data objectsfunctional representation of - mutable data objectsimplemented with assignment - pair(s)functional representation of - functional representation of datamutable data - - - When we introduced compound data, we observed in - section that pairs can be represented purely - in terms of - - procedures: - functions: - - - pair (primitive function)functional implementation of - head (primitive function)functional implementation of - tail (primitive function)functional implementation of - cons_1_2_run - 1 - -(define (cons x y) - (define (dispatch m) - (cond ((eq? m 'car) x) - ((eq? m 'cdr) y) - (else (error "Undefined operation - - CONS" m)))) - dispatch) - -(define (car z) (z 'car)) - -(define (cdr z) (z 'cdr)) - - -function pair(x, y) { - function dispatch(m) { - return m === "head" - ? x - : m === "tail" - ? y - : error(m, "undefined operation -- pair"); - } - return dispatch; -} - -function head(z) { return z("head"); } - -function tail(z) { return z("tail"); } - - - The same observation is true for mutable data. We can implement - mutable data objects as - - procedures - functions - - using assignment and local state. For instance, we can extend the above - pair implementation to handle - - set-car! - set_head - - and - - set-cdr! - set_tail - - in a manner analogous to the way we implemented bank accounts using - - make-account - make_account - - in section: - - cons_1_2_run_3 - -const x = pair(1, 2); -set_head(x, 3); -head(x); - - - - pair (primitive function)functional implementation of - head (primitive function)functional implementation of - tail (primitive function)functional implementation of - set_head (primitive function)functional implementation of - set_tail (primitive function)functional implementation of - pair2 - cons_1_2_run_3 - 3 - -(define (cons x y) - (define (set-x! v) (set! x v)) - (define (set-y! v) (set! y v)) - (define (dispatch m) - (cond ((eq? m 'car) x) - ((eq? m 'cdr) y) - ((eq? m 'set-car!) set-x!) - ((eq? m 'set-cdr!) set-y!) - (else (error "Undefined operation - - CONS" m)))) - dispatch) - -(define (car z) (z 'car)) - -(define (cdr z) (z 'cdr)) - -(define (set-car! z new-value) - ((z 'set-car!) new-value) - z) - -(define (set-cdr! z new-value) - ((z 'set-cdr!) new-value) - z) - - -function pair(x, y) { - function set_x(v) { x = v; } - function set_y(v) { y = v; } - return m => m === "head" - ? x - : m === "tail" - ? y - : m === "set_head" - ? set_x - : m === "set_tail" - ? set_y - : error(m, "undefined operation -- pair"); -} - -function head(z) { return z("head"); } - -function tail(z) { return z("tail"); } - -function set_head(z, new_value) { - z("set_head")(new_value); - return z; -} -function set_tail(z, new_value) { - z("set_tail")(new_value); - return z; -} - - - - - - Assignment is all that is needed, theoretically, to account for the - behavior of mutable data. As soon as we admit - - set! - assignment - - to our language, we raise all the issues, not only of assignment, but of - mutable data in general.On the other hand, from the viewpoint of - implementation, assignment requires us to modify the environment, which is - itself a mutable data structure. Thus, assignment and mutation are - equipotent: Each can be implemented in terms of the other. - - - - Draw environment diagrams to illustrate the evaluation of the sequence - of - - - expressions - - - statements - - - - pair2_example1 - pair2 - -(define x (cons 1 2)) -(define z (cons x x)) -(set-car! (cdr z) 17) - -(car x) - - -const x = pair(1, 2); -const z = pair(x, x); -set_head(tail(z), 17); - - - - pair2_example2 - pair2_example1 - 17 - -(car x) - - - 17 - - -head(x); - - -17 - - - using the - - - procedural - - - functional - - - implementation of pairs given above. (Compare - exercise.) - - - - mutable data objectsfunctional representation of - mutable data objectsimplemented with assignment - pair(s)functional representation of - functional representation of datamutable data - mutable data objects -
- diff --git a/xml/cn/chapter3/section3/subsection3.xml b/xml/cn/chapter3/section3/subsection3.xml deleted file mode 100644 index 647159345..000000000 --- a/xml/cn/chapter3/section3/subsection3.xml +++ /dev/null @@ -1,1000 +0,0 @@ - - - 表示表 - - - - - - - - 当我们在章节研究表示集合的各种方法时,我们在部分提到了通过识别键维护一个记录记录的键在表中的任务。在部分的数据导向编程的实现中,我们大量使用了二维表,其中通过两个键存储和检索信息。在这里,我们介绍如何将表构建为可变的列表结构。 - - - - 我们首先考虑一个一维一维表,其中每个值都存储在单个键下。我们将表实现为一个记录的列表,每个记录实现为一个由键和值组成的对。记录通过那些指向相继记录的carsheads对连接在一起形成列表。这些连接的对称为表的的主干主干。为了在向表中添加新记录时可以更改的位置,我们将表构建为一个带头结点的列表列表带头结点带头结点的列表。带头结点的列表在开始处有一个特殊的主干对,该对持有一个伪记录在这种情况下,任意选择的符号*table*.字符串"*table*".显示了表的方框与指针表示法。 - - -a: 1 -b: 2 -c: 3 - - -a: 1 -b: 2 -c: 3 - - - - -
-
- 作为带头结点列表表示的表。 - -
- - -
-
- 作为带头结点列表表示的表。 - -
-
-
-
- - - 要从表中提取信息,我们使用 -lookup - - 过程, - 函数, - - 它接受一个键作为参数并返回相关的值( - - 假 - 未定义 - - 如果 - 在那个键下没有存储值)。 - - 查找 - 函数查找 - - - 是根据 -assoc 操作,期望一个键和一个记录列表作为参数。注意assoc 从未看到伪记录。 - - Assoc - 函数assoc - - - 返回具有给定键作为其carhead的记录。 - - - - 因为assoc使用 - equal?,它可以识别 - 是符号、数字或列表结构的键。 - - - 因为 assoc 使用 - equal,它可以识别 - 是字符串、数字或列表结构的键。<!-- - --><!-- - --> - - 查找 - 函数 查找 - - - 然后检查所返回的记录 -assoc 不是 - - 假, - 未定义, - - 并返回值(记录的 - - cdr) - tail - 。 - - lookup1_example - make_table1 - insert_into_table1 - -const t = make_table(); -insert("a", 10, t); -lookup("a", t); - - - - lookupin one-dimensional table - assoc - 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)); -} - - - - - - 要在指定键下插入一个值到表中,我们首先使用 -assoc 查看表中是否已有该键的记录。如果没有,我们通过将键与值cons组成pair一个新记录,并将其插入到表的记录列表的头部,在伪记录之后。如果已有该键的记录,我们设置此记录的cdrtail为指定的新值。表的头部为我们提供了一个固定的位置进行修改以插入新记录。因此,第一个主干对是表示表“自身”的对象;即,指向表的指针就是指向这个对的指针。同一主干对总是开始于表。如果我们不这样安排, - - 插入! - 插入 - - - 在添加新记录时将需要返回表开始的新值。 - - insertin one-dimensional table - 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"; -} - - - - - - 要构建新的表,我们只需创建一个包含 - - - 符号*table*: - - - 仅包含字符串"*table*": - - - - make_table一维表 - make_table1 - -(define (make-table) - (list '*table*)) - - -function make_table() { - return list("*table*"); -} - - - 一维 - - - - 二维表 - - - 二维 - - - - 在二维表中,每个值由两个键索引。我们可以将这样的表构建为一维表,其中每个键标识一个子表。 - - - 图 - - - 图 - - - 显示了表的方框与指针表示法 - - -math: - +: 43 - -: 45 - *: 42 -letters: - a: 97 - b: 98 - - -"math": - "+": 43 - "-": 45 - "*": 42 -"letters": - "a": 97 - "b": 98 - - - 其具有两个子表。 (子表不需要特殊的头符号,串,因为标识子表的键起到这个作用。) - - -
-
- 一个二维表。 - -
- - -
-
- 一个二维表。 - -
-
-
-
- - - 当查找一个项目时,我们使用第一个键来识别正确的子表。然后我们使用第二个键来识别子表中的记录。 - - 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); - } -} - - - - - - 要在一对键下插入新项目,我们使用 -assoc 看是否已有子表存储在第一个键下。如果没有,我们构建一个包含单个记录的新子表 - - (key-2, - (key_2, - -value ) 并将其插入到第一个键下的表中。如果第一个键已经存在子表,我们将新记录插入到该子表中,使用上述一维表的插入方法: - - insertin two-dimensional table - 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"; -} - - - 二维 - - - - 创建局部表 - - - 局部 - - - 该 -lookup 和 - - 插入! - 插入 - - 上述操作将表作为参数。这使我们能够使用访问多个表的程序。处理多个表的另一种方法是拥有单独的 -lookup 和 - - 插入! - 插入 - - - 过程 - 函数 - - 为每个表提供。我们可以通过过程化地表示一个表来做到这一点,作为一个维护内部表的对象,作为其局部状态的一部分。当收到合适的消息时,此表对象提供操作内部表的 - - 过程 - 函数 - - 。这是以这种方式表示的二维表的生成器: - - make_tablemessage-passing implementation - 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; -} - - - - - - 使用 - - make-table, - make_table, - - 我们可以 - 操作与类型表实现 - 实现该 -get 和 - put 操作用于部分的数据导向编程,如下所示: - - operation_table_example - -put("a", "b", 10); -get("a", "b"); - - - - get - put - operation_table - make_table2 - operation_table_example - 10 - - operation-and-type tableimplementing -(define operation-table (make-table)) -(define get (operation-table 'lookup-proc)) -(define put (operation-table 'insert-proc!)) - - -const operation_table = make_table(); -const get = operation_table("lookup"); -const put = operation_table("insert"); - - - - 获取 - 函数 get - - - 接受两个键作为参数,并 -put 接受两个键和一个值作为参数。两个操作都访问同一个局部表,该表封装在 - - make-table. - make_table. - - 局部 - - - - 在上述表实现中,使用 - 测试键相等性 - 记录的键测试相等性 - 通过 - - equal? - equal - - 进行测试(由 -assoc ). 这并不总是合适的测试。例如,我们可能有一个带有数值键的表,其中我们查找的不是需要与数字完全匹配,而是只需在某个容差范围内的数字。设计一个表构造器 - - make-table - make_table - - 接受一个 - - same-key? - same_key - - - 过程 - 函数 - - 作为参数,用于测试键的“相等性”。 - - Make-table - 函数 make_table - - 应返回一个【134:12†cn.txt】。dispatch - - 过程 - 函数 - - 可用于访问适当的 - lookup 和 - - 插入! - 插入 - - - 过程函数 - - 用于局部表。 - - - - -// Solution by GitHub user clean99 - -function make_table(same_key) { - const local_table = list("*table*"); - function assoc(key, records) { - return is_null(records) - ? undefined - : same_key(key, head(head(records))) - ? head(records) - : assoc(key, tail(records)); - } - function lookup(key) { - const record = assoc(key, tail(local_table)); - return is_undefined(record) - ? undefined - : tail(record); - } - function insert(key, value) { - const record = assoc(key, tail(local_table)); - if (is_undefined(record)) { - set_tail(local_table, - pair(pair(key, value), tail(local_table))); - } else { - set_tail(record, value); - } - return "ok"; - } - function dispatch(m) { - return m === "lookup" - ? lookup - : m === "insert" - ? insert - : error(m, "unknow operation -- table"); - } - return dispatch; -} - -const operation_table = make_table((a, b) => a === b); -const get = operation_table("lookup"); -const put = operation_table("insert"); - - - - - - - 概括一维和二维表,展示如何实现一个在其中值存储在一个 - n$n$维 - 任意数量的键的表,并且不同的值可以存储在不同数量的键下。 - 该【150:4†cn.txt】 - lookup 和 - - 插入! - 插入 - - - 过程 - 函数 - - 应接受一个用于访问表的键列表作为输入。 - - - - solution_3_25 - solution_3_25_example - -// contributed by GitHub user tttinkl - -function assoc(key, records, same_key) { - return is_null(records) - ? undefined - : same_key(key, head(head(records))) - ? head(records) - : assoc(key, tail(records), same_key); -} - - -function make_table(same_key) { - const local_table = list("*table"); - - const get_value = tail; - - function is_table(t) { - return is_pair(t) && head(t) === "*table"; - } - - function lookup(keys) { - function lookup_generic(keys, table) { - if (is_null(keys)) { - return table; - } - const key_1 = head(keys); - const key_rest = tail(keys); - const record = assoc(key_1, tail(table), same_key); - if (is_undefined(record)) { - return undefined; - } - if (is_null(key_rest)) { - return get_value(record); - } else if (is_table(get_value(record))) { - return lookup_generic(key_rest, get_value(record)); - } else { - error('invalid key'); - } - } - return lookup_generic(keys, local_table); - } - - - function insert(keys, value) { - function insert_generic(keys, value, table) { - const key_1 = head(keys); - const key_rest = tail(keys); - const record = assoc(key_1, tail(table), same_key); - if (is_undefined(record)) { - if (is_null(key_rest)) { - set_tail( - table, - pair(pair(key_1, value), tail(table))); - } else { - const new_subtable = list("*table"); - set_tail( - table, - pair(pair(key_1, new_subtable), tail(table)) - ); - insert_generic(key_rest, value, new_subtable); - } - } else { - if (is_null(key_rest)) { - set_tail(record, value); - } else { - if (is_table(get_value(record))) { - insert_generic(key_rest, value, get_value(record)); - } else { - const new_subtable = list("*table"); - set_tail(record, new_subtable); - insert_generic(key_rest, value, new_subtable); - } - } - } - } - insert_generic(keys, value, local_table); - } - - function dispatch(m) { - return m === "lookup" - ? lookup - : m === "insert" - ? insert - : m === "show" - ? () => { - display(local_table); - return local_table; - } - : error(m, "unknow operation -- table"); - } - return dispatch; -} - -const table = make_table(equal); - -const get = table('lookup'); -const put = table('insert'); -const show = table('show'); - - - - solution_3_25_example - -put(list("a"), 1); -put(list("b", "c"), 2); -put(list("d", "e", "f"), 3); - -display(get(list("a"))); -display(get(list("b", "c"))); -display(get(list("d", "e", "f"))); - -put(list("a", "b"), 1); -display(get(list("a"))); -put(list("b", "c", "d"), 2); -display(get(list("b", "c"))); -put(list("b"), 1); -display(get(list("b"))); - - - - - - - 要搜索上面实现的表,需扫描记录列表。这基本上是部分中无序列表的表示。对于大型表,可能更有效率的是以不同方式构建表。描述一种表的实现方式,其中(key, value)记录使用 - 二叉树表结构为 - 表示为二叉树 vs. 无序列表 - 二叉树组织,假设键可以以某种方式排序(例如,数字或字母顺序)。(比较练习的章节。) - - - - ex_3_26_solution - ex_3_26_solution_example - -// provided by GitHub user devinryu - -function entry(tree) { return head(tree); } -function left_branch(tree) { return head(tail(tree)); } -function right_branch(tree) { return head(tail(tail(tree))); } -function make_tree(entry, left, right) { - return list(entry, left, right); -} - -// kv is list(key, value) -function adjoin_set(kv, set) { - return is_null(set) - ? make_tree(kv, null, null) - : head(kv) === head(entry(set)) - ? set - : head(kv) < head(entry(set)) - ? make_tree(entry(set), - adjoin_set(kv, left_branch(set)), - right_branch(set)) - : make_tree(entry(set), - left_branch(set), - adjoin_set(kv, right_branch(set))); -} - -function make_table() { - let local_table = null; - function lookup(given_key, tree_of_records) { - if (is_null(tree_of_records)) { - return null; - } else { - const this_entry = entry(tree_of_records); - const this_key = head(this_entry); - return given_key === this_key - ? this_entry - : given_key < this_key - ? lookup(given_key, - left_branch(tree_of_records)) - : lookup(given_key, - right_branch(tree_of_records)); - } - } - function insert(k, v) { - let record = lookup(k, local_table); - if(is_null(record)) { - local_table = adjoin_set(list(k, v), local_table); - } else { - // do nothing - } - } - function get(k) { - return head(tail(lookup(k, local_table))); - } - function print() { - return display(local_table); - } - function dispatch(m) { - return m === "lookup" - ? get - : m === "insert" - ? insert - : m === "print" - ? print - : error(m, "error"); - } - return dispatch; -} - - - - ex_3_26_solution_example - -const t = make_table(); -const get = t("lookup"); -const put = t("insert"); -const print = t("print"); - -// The test results - -put(3, "d"); -put(1, "a"); -put(2, "b"); -put(2, "c"); -put(4, "e"); -put(5, "f"); - -print(); - -display(get(2)); // displays: "b" - - - - - - - 记忆化 - 记忆化 - 制表法 - 用于存储计算值 - 记忆化 - (也称为制表法)是一种技术,它使 - - 过程 - 函数 - - 能够在局部表中记录先前计算过的值。这个技术可以显著改善程序的性能。一个记忆化的 - - 过程 - 函数 - - 维护一个表,其中将之前调用的值使用生成这些值的参数作为键存储。当记忆化的 - - 过程 - 函数 - - 被要求计算一个值时,首先查看表中是否已有该值,如果有,则直接返回该值。否则,它按常规方式计算新值并存储在表中。作为记忆化的一个例子,回忆部分用于计算斐波那契数的指数过程: - - fib_example - 8 - -(define (fib n) - (cond ((= n 0) 0) - ((= n 1) 1) - (else (+ (fib (- n 1)) - (fib (- n 2)))))) - - -function fib(n) { - return n === 0 - ? 0 - : n === 1 - ? 1 - : fib(n - 1) + fib(n - 2); -} - - - 相同过程函数的记忆化版本是 - - memorize_example - -memo_fib(5); - - - - fibwith memoization - memo_fib - memo_fib - memorize - memorize_example - 5 - -(define memo-fib - (memoize (lambda (n) - (cond ((= n 0) 0) - ((= n 1) 1) - (else (+ (memo-fib (- n 1)) - (memo-fib (- n 2)))))))) - - -const memo_fib = memoize(n => n === 0 - ? 0 - : n === 1 - ? 1 - : memo_fib(n - 1) + - memo_fib(n - 2) - ); - - - 记忆化器定义为 - - memoize - make_table1 - lookup1 - insert_into_table1 - memorize - -(define (memoize f) - (let ((table (make-table))) - (lambda (x) - (let ((previously-computed-result (lookup x table))) - (or previously-computed-result - (let ((result (f x))) - (insert! x result table) - result)))))) - - -function memoize(f) { - const table = make_table(); - return x => { - const previously_computed_result = - lookup(x, table); - if (is_undefined(previously_computed_result)) { - const result = f(x); - insert(x, result, table); - return result; - } else { - return previously_computed_result; - } - }; -} - - - 其中记忆化版本的 - - 过程 - 函数 - - 是 - $n$ 第 n 个斐波那契数,其步骤数与 【166:2†cn.txt】 的比例相同。 - $n$ 。该方案是否仍然有效,如果我们仅仅定义 - - memo-fib - memo_fib - - 为 - - (memoize fib)? - memoize(fib) - - - - -
diff --git a/xml/cn/chapter3/section3/subsection4.xml b/xml/cn/chapter3/section3/subsection4.xml deleted file mode 100644 index 4369afdfa..000000000 --- a/xml/cn/chapter3/section3/subsection4.xml +++ /dev/null @@ -1,1960 +0,0 @@ - - - 数字电路模拟器 - - - - 数字电路模拟 - - - 设计复杂的数字系统,例如计算机,是一项重要的工程活动。 数字系统通过连接简单元素构建。 尽管这些单个元素的行为很简单,但它们的网络可以表现出非常复杂的行为。 对拟议电路设计进行计算机模拟是数字系统工程师使用的重要工具。 在本节中,我们设计了一个用于执行数字逻辑模拟的系统。 该系统是称为事件驱动模拟模拟事件驱动事件驱动模拟的程序类型的典型代表,其中的动作 (事件) 触发稍后发生的更多事件,进而触发更多事件,以此类推。 - - - - 我们的电路计算模型将由对象组成,这些对象对应于构建该电路的基本组件。 有 - 导线 (在数字电路中) - 导线 ,其承载 信号 (数字) - 数字信号 - 数字信号 。 一个 数字信号在任何时候只能有两个可能的值之一,0 和 1。 还有各种类型的数字 函数盒 (在数字电路中) - 函数盒 ,其连接承载输入信号的导线到其他输出导线。 这些盒子产生的输出信号是从其输入信号计算得出的。 输出信号是 延迟延迟 (在数字电路中) - 由函数盒的类型决定的时间延迟。 例如,一个 - 反相器 - 反相器 - 是一个 - 原语函数盒,其输入反转。 如果输入信号变为反相器的输入信号改变为0,那么一个 - - 是一个反相器,其输入反转。 如果 - 输入信号变为0, - - 稍后反相器将把其输出信号改变为 1。 如果反相器的输入信号变为 1,那么一个 - 反相器延迟 - 稍后反相器将改变其输出信号为 0。 我们以图形象征性地绘制一个反相器 - 。 一个 - 与门 - 与门 , - 也如图所示 - ,是一个原语 - 函数盒,具有两个输入和一个输出。 它将其输出信号驱动到一个 - 逻辑与 (数字逻辑) - 逻辑与 - 输入的。 也就是说,如果 - 两个输入信号都变为 - 1,那么一个 与门延迟 - 时间 - 之后,与门将强制其输出信号变为 1;否则输出将为 0。 一个 - 或门 - 或门 - 是一个类似的二输入原语函数 - 盒,将其输出信号驱动到一个 - 逻辑或 (数字逻辑) - 逻辑或 的输入。 也就是说,如果至少一个输入信号为 1,则输出将变为 1;否则输出将变为0。 - - -
-
- 数字逻辑模拟器中的原语函数。 - - -
- - - 我们可以将原语函数连接在一起以构建更复杂的函数。 为此,我们将一些 - 函数盒的输出连接到其他函数盒的输入。 例如, - 半加器 - 加法器 - 半加器电路如图 - 所示,由一个 - 或门、两个与门和一个反相器组成。 它接受两个输入信号, - $A$ 和 $B$,并有 - 两个输出信号,$S$ 和 $C$。 - 当且仅当$A$ 和 $B$ - 中正好有一个为1 时,$S$ 将变为 1, - 当且仅当$A$ 和 $B$ 都为 1 时,$C$ 将变为 1。 从图中我们可以看到,由于涉及的 - 延迟,输出可能在不同时间产生。 - 数字电路设计中的许多困难源于这一事实。 -
-
- 半加器电路。 - - -
-
- - - 现在我们将构建一个程序来对我们希望研究的数字逻辑电路进行建模。 该程序将构造计算对象以模拟导线,这些对象将保持信号。 函数 - 盒将由 - - 过程 - 函数 - - 模拟,以确保信号之间的正确关系。 - - - - 我们模拟的一个基本元素将是一个 - - 过程 - 函数 - - make_wire - - make-wire, - make_wire, - - 其用于创建导线。例如,我们可以如下创建六根导线: - - make_wire_usage - make_wire - -(define a (make-wire)) -(define b (make-wire)) -(define c (make-wire)) -(define d (make-wire)) -(define e (make-wire)) -(define s (make-wire)) - - -const a = make_wire(); -const b = make_wire(); -const c = make_wire(); -const d = make_wire(); -const e = make_wire(); -const s = make_wire(); - - - 我们通过调用一个 - - 过程 - 函数 - - 将函数盒附加到一组导线上,该函数构造该类型的盒子。 构造函数的 - 参数 - - 过程 - 函数 - - 是要附加到盒子的导线。 例如,给定 - 我们可以构造与门、或门和反相器,我们可以将 - 中显示的半加器连接在一起: - - or_gate_example - or_gate - make_wire_usage - 'ok' - -(or-gate a b d) - - -ok - - -or_gate(a, b, d); - - -"ok" - - - - and_gate_example - and_gate - make_wire_usage - 'ok' - -(and-gate a b c) - - -ok - - -and_gate(a, b, c); - - -"ok" - - - - inverter_example - inverter - make_wire_usage - 'ok' - -(inverter c e) - - -ok - - -inverter(c, e); - - -"ok" - - - - and_gate_example_2 - and_gate - make_wire_usage - 'ok' - -(and-gate d e s) - - -ok - - -and_gate(d, e, s); - - -"ok" - - - - - - 更好的是,我们可以通过定义一个 - - 过程 - 函数 - - - half-adder - half_@adder - - 显式地命名此操作,该函数构建此电路,给定要附加到半加器的四个外部导线: - - half_adder_example - half_adder - -const a = make_wire(); -const b = make_wire(); -const s = make_wire(); -const c = make_wire(); -half_adder(a, b, s, c); - - - - half-adderhalf_adder - half_adder - make_wire - or_gate - and_gate - inverter - half_adder_example - 'ok' - -(define (half-adder a b s c) - (let ((d (make-wire)) (e (make-wire))) - (or-gate a b d) - (and-gate a b c) - (inverter c e) - (and-gate d e s) - 'ok)) - - -function half_adder(a, b, s, c) { - const d = make_wire(); - const e = make_wire(); - or_gate(a, b, d); - and_gate(a, b, c); - inverter(c, e); - and_gate(d, e, s); - return "ok"; -} - - - 定义这个操作的好处是我们可以使用 - - half-adder - half_adder - - 本身作为构建块来创建更复杂的 - 电路。 例如,图显示了一个 - 全加器 - 加法器 - 全加器由两个半加器和一个或门组成。 - 全加器是用于加两个二进制数字的基本电路元素。 这里 $A$ 和 $B$ - 是两个数中对应位置的位,$C_{\mathit{in}}$ 是 - 从右边一位的加法进位位。 该电路生成 - $\mathit{SUM}$,即对应位置的和位,以及 - $C_{\mathit{out}}$,即 - 需要向左传播的进位位。 我们可以如下构建一个 - 全加器: - - full_adder_example - full_adder - -const a = make_wire(); -const b = make_wire(); -const c_in = make_wire(); -const sum = make_wire(); -const c_out = make_wire(); -full_adder(a, b, c_in, sum, c_out); - - - - full-adderfull_adder - full_adder - make_wire - half_adder - or_gate - full_adder_example - 'ok' - -(define (full-adder a b c-in sum c-out) - (let ((s (make-wire)) - (c1 (make-wire)) - (c2 (make-wire))) - (half-adder b c-in s c1) - (half-adder a s sum c2) - (or-gate c1 c2 c-out) - 'ok)) - - -function full_adder(a, b, c_in, sum, c_out) { - const s = make_wire(); - const c1 = make_wire(); - const c2 = make_wire(); - half_adder(b, c_in, s, c1); - half_adder(a, s, sum, c2); - or_gate(c1, c2, c_out); - return "ok"; -} - - - 已经定义了 - - full-adder - full_adder - - 作为一个 - - 过程, - 函数, - - 我们现在可以将其用作构建块来创建更复杂的 - 电路。 (例如,请参见练习。) - - <!-- 图形因 SICP JS 分页而移动 --> - <!-- 图形代码在该文件后文的 PDF_ONLY 中重复 --> -
-
- 全加器电路。 - - -
-
-
- - - 从本质上讲,我们的模拟器为我们提供了构造 - 电路语言的工具。 如果我们采用我们在 - - Lisp - JavaScript - - 中研究语言的总体视角,如章节所述, - 我们可以说原语函数盒构成了语言的基本 - 元素,将盒子连接在一起提供了一种 - 组合的方法,而将布线模式指定为 - - 过程 - 函数 - - 则作为一种抽象的方法。 - - - - 原语函数盒 - - - - - 原语函数盒 - 数字电路模拟原语函数盒 - 实现了作用力,使得一个导线信号的变化影响其他 - 导线上的信号。 为了构建函数盒,我们使用以下导线操作: -
    -
  • - - (get-signal wire): - - get_signal(wire) - - - get_signal -

    - 返回导线上信号的当前值。 -
  • -
  • - - (set-signal! wire new-value): - - - set_signal(wire, new-value): - - - set_signal -

    - 将导线上信号的值更改为新值。 -
  • -
  • - - - (add-action! wire procedure-of-no-arguments): - - - add_action(wire, function-of-no-arguments): - - - add_action -

    - 声明指定的 - - 过程 - 函数 - - 应该在导线上的信号值改变时运行。这样 - - 过程 - 函数 - - 便是将导线信号值的变化传递到其他导线的方式。 -
  • -
- 此外,我们将使用一个 - - 过程 - 函数 - - after_delay - - - after-delay - - - after_delay - - - 接受一个时间延迟和一个 - - 过程 - 函数 - - 来执行,并在指定的 - 时间延迟后执行给定的 - - 过程 - 函数 - 【4:4†cn.txt】。 -
- - - 使用这些 - - 过程, - 函数, - - 我们可以定义原语数字逻辑函数。 要通过反相器将输入连接到输出,我们使用 - - add-action! - add_action - - - 以将一个 - - 过程 - 函数 - - 与输入导线相关联,该函数将在输入导线的信号值变化时运行。 - 该 - - 过程 - 函数 - - 计算输入信号的 - - 逻辑非 - logical_not - - - ,然后在一个 - - 反相器延迟 - inverter_delay, - - - 之后,将输出信号设置为此新值: - - inverterinverter - logical_not - inverter - get_signal - after_delay - inverter_example - 'ok' - -(define (inverter input output) - (define (invert-input) - (let ((new-value (logical-not (get-signal input)))) - (after-delay inverter-delay - (lambda () - (set-signal! output new-value))))) - (add-action! input invert-input) - 'ok) - -(define (logical-not s) - (cond ((= s 0) 1) - ((= s 1) 0) - (else (error "Invalid signal" s)))) - - -function inverter(input, output) { - function invert_input() { - const new_value = logical_not(get_signal(input)); - after_delay(inverter_delay, - () => set_signal(output, new_value)); - } - add_action(input, invert_input); - return "ok"; -} -function logical_not(s) { - return s === 0 - ? 1 - : s === 1 - ? 0 - : error(s, "invalid signal"); -} - - - - - - logical_and - -function logical_and(s1, s2) { - return s1 === 1 && s2 === 1 - ? 1 - : s1 === 0 || s1 === 1 - ? s2 === 0 || s2 === 1 - ? 0 - : error(s2, "invalid signal") - : error(s1, "invalid signal"); -} - - - - <!-- 图形因 SICP JS 分页而移动 --> - <!-- 图形代码是该文件之前 WEB_ONLY 中代码的副本 --> -
-
- 全加器电路。 - - -
-
- - - - 与门稍微复杂些。 操作 - - 过程 - 函数 - - 必须在门的任一输入发生变化时运行。它计算输入导线信号值的 - - 逻辑与 - (使用类似于 - 逻辑非的过程) - - - logical_and - (使用类似于 - logical_not的函数) - - - ,并在一个 - - 与门延迟 - and_gate_delay. - - - 之后,将新值出现在输出导线上。【192:6†cn.txt】 - - and-gateand_gate - and_gate - get_signal - after_delay - logical_and - and_gate_example - 'ok' - -(define (and-gate a1 a2 output) - (define (and-action-procedure) - (let ((new-value - (logical-and (get-signal a1) (get-signal a2)))) - (after-delay and-gate-delay - (lambda () - (set-signal! output new-value))))) - (add-action! a1 and-action-procedure) - (add-action! a2 and-action-procedure) - 'ok) - - -function and_gate(a1, a2, output) { - function and_action_function() { - const new_value = logical_and(get_signal(a1), - get_signal(a2)); - after_delay(and_gate_delay, - () => set_signal(output, new_value)); - } - add_action(a1, and_action_function); - add_action(a2, and_action_function); - return "ok"; -} - - - - - - 定义一个 - or-gate或门or-gateor_gate - 或门作为一个原语函数盒。 你的 - - or-gate - or_gate - - - 构造器应与 - - and-gate. - and_gate. - - - 类似。 - - - - logical_or - -// 由 GitHub 用户 clean99 提供 - -function logical_or(s1, s2) { - return s1 === 0 && s2 === 0 - ? 0 - : s1 === 0 || s1 === 1 - ? s2 === 0 || s2 === 1 - ? 1 - : error(s2, "invalid signal") - : error(s1, "invalid signal"); -} - - - - or_gate - get_signal - after_delay - logical_or - -// 由 GitHub 用户 clean99 提供 - -function or_gate(a1, a2, output) { - function or_action_function() { - const new_value = logical_or(get_signal(a1), - get_signal(a2)); - after_delay(or_gate_delay, - () => set_signal(output, new_value)); - } - add_action(a1, or_action_function); - add_action(a2, or_action_function); - return "ok"; -} - - - - - - - 另一种构造 - or-gate或门or-gateor_gate - 或门的方法是作为一种由与门和反相器构建的复合数字逻辑 - 设备。 定义一个 - - 过程 - 函数 - - - or-gate - or_gate - - 来完成此任务。 在 - - 与门延迟 - and_gate_delay - - - 和 - - 反相器延迟? - inverter_delay? - - - 下计算或门的延迟时间。 - - (由 GitHub 用户 taimoon 提供) - - 思路:~(~a & ~b) = nand(~a, ~b) = ~~a | ~~b = a | b - - -function nand_gate(a1, a2, out){ - const tmp = make_wire(); - and_gate(a1, a2, tmp); - inverter(tmp, out); -} - -function or_gate(a1, a2, out){ - const not_a1 = make_wire(); - const not_a2 = make_wire(); - inverter(a1, not_a1); - inverter(a2, not_a2); - nand_gate(not_a1, not_a2, out); -} - - - nand门的延迟时间为 - nand_gate_delay = and_gate_delay + inverter_delay,上述或门的延迟时间为 - or_gate_delay = nand_gate_delay + inverter_delay = and_gate_delay + 2 * inverter_delay。 - - - - - - 图显示了一个 - 行波进位加法器 - 加法器行波进位 - 行波进位加法器通过串接形成 -$n$ - 图显示了一个 - 行波进位加法器 - 加法器行波进位 - 行波进位加法器通过串接多个 - 全加器构成。 - 这是用于加两个二进制数的最简单的并行加法器形式。 -$n$ - 图显示了一个 - 行波进位加法器 - 加法器行波进位 - 行波进位加法器通过串接多个全加器构成。 - 这是用于加两个-bit二进制数的最简单的并行加法器形式。 - 输入 -$A_{1}$, - $A_{2}$, - $A_{3}$, , -$A_{n}$ 和 -$B_{1}$, - $B_{2}$, - $B_{3}$, , -$B_{n}$ - 是要相加的两个二进制数(每个 -$A_{k}$ 和 -$B_{k}$ - 是0或1)。电路生成 -$S_{1}$, - $S_{2}$, - $S_{3}$, - , -$S_{n}$ , - 这个 -$n$ - , - 这个 - 总和的位数,和 -$C$ ,从加法进位。编写一个 - - 过程 - 函数 - - - ripple-carry-adder - ripple_carry_adder - - - 来生成这个电路。该 - - 过程 - 函数 - - 应接受三个列表作为参数 -$n$ - 导线,每个这个 -$A_{k}$ ,这个 -$B_{k}$ ,和这个 -$S_{k}$ 并且 - 也有另一根导线 -$C$ -。行波进位加法器的主要缺点是需要等待进位信号传播。获取完整输出所需的延迟为 -$n$ -位行波进位加法器的完整输出所需的延迟,用于与门、或门和反相器的延迟来表示。 - - - -
-
- - $n$-位数的行波进位加法器。 - - -
- - 数字电路模拟原语函数盒 - - - 表示导线 - - - 在我们的模拟中 - 数字电路模拟表示导线 - ,导线将是一个具有两个局部 - 状态变量的计算对象: - - a信号值, - asignal_value - - - (初始值为0)和一组 - - 操作过程, - action_functions - - - ,当信号值变化时会运行。我们采用 - 消息传递在数字电路模拟中 - 的消息传递风格 - 实现导线,作为一系列局部 - - 过程 - 函数 - - 的集合 -dispatch - 过程 - 函数 - - ,用于选择适当的局部操作,就像我们 - 在章节中处理简单的银行账户对象那样: - - make_wire - make_wire - call_each - make_wire_usage - -(define (make-wire) - (let ((signal-value 0) (action-procedures '())) - (define (set-my-signal! new-value) - (if (not (= signal-value new-value)) - (begin (set! signal-value new-value) - (call-each action-procedures)) - 'done)) - -(define (accept-action-procedure! proc) - (set! action-procedures (cons proc action-procedures)) - (proc)) - -(define (dispatch m) - (cond ((eq? m 'get-signal) signal-value) - ((eq? m 'set-signal!) set-my-signal!) - ((eq? m 'add-action!) accept-action-procedure!) - (else (error "Unknown operation -- WIRE" m)))) - dispatch)) - - -function make_wire() { - let signal_value = 0; - let action_functions = null; - function set_my_signal(new_value) { - if (signal_value !== new_value) { - signal_value = new_value; - return call_each(action_functions); - } else { - return "done"; - } - } - function accept_action_function(fun) { - action_functions = pair(fun, action_functions); - fun(); - } - function dispatch(m) { - return m === "get_signal" - ? signal_value - : m === "set_signal" - ? set_my_signal - : m === "add_action" - ? accept_action_function - : error(m, "unknown operation -- wire"); - } - return dispatch; -} - - -局部的 - - 过程 - 函数 - - - set-my-signal - set_my_signal - - -测试新信号值是否改变了导线上信号。 如果是这样,它则运行每一个 - - 过程 - 函数 - -使用以下 - - 过程 - 函数 - - - call-each, - call_each - -用于调用不带参数的过程列表中的每一项: - - 过程: - 函数: - - - call_each - call_each - -(define (call-each procedures) - (if (null? procedures) - 'done - (begin - ((car procedures)) - (call-each (cdr procedures))))) - - -function call_each(functions) { - if (is_null(functions)) { - return "done"; - } else { - head(functions)(); - return call_each(tail(functions)); - } -} - - -局部 - - 过程 - 函数 - - - 接受-操作-过程, - accept_action_function - - -将给定的 - - 过程 - 函数 - -添加到要执行的 - - 过程列表中, - 函数 - -然后运行新的 - - 过程 - 函数 - -一次。 (参见练习。) - - - - 使用局部 -dispatch - - 过程 - 函数 - - 设置为指定,可以提供以下 - - 过程 - 函数 - - 以访问 - 导线上的局部操作: - 这些 - - 过程 - 函数 - -只是语法糖,允许我们使用普通的 - - - 过程性 - - - 函数式 - - -语法访问对象的局部 - - 过程 - 函数 -。令人惊讶的是,我们可以如此简单地交换 -过程 -函数 - -和 -数据的角色。 例如,如果我们写 - - (wire 'get-signal) - wire("get_signal") - - -我们认为 - wire 作为一个 - - 过程 - 函数 - -,用消息 - - get-signal - "get_signal" - - -作为输入进行调用。或者,编写 - - (get-signal wire) - get_signal(wire) - - -鼓励我们思考 -wire 作为一个数据 -对象,它是 - - 过程 - 函数 - -的输入 - - 获取信号 - get_signal - - -。 问题的本质在于,在一个可以将 -过程 -函数 - -视为对象的语言中, -过程 -函数 - -和数据之间没有根本区别,我们可以选择我们的语法糖使我们能以我们选择的风格进行编程。 - - - get_signal - set_signal - add_action - get_signal - -(define (get-signal wire) - (wire 'get-signal)) - -(define (set-signal! wire new-value) - ((wire 'set-signal!) new-value)) - - -(define (add-action! wire action-procedure) - ((wire 'add-action!) action-procedure)) - - -function get_signal(wire) { - return wire("get_signal"); -} -function set_signal(wire, new_value) { - return wire("set_signal")(new_value); -} -function add_action(wire, action_function) { - return wire("add_action")(action_function); -} - - - - - -导线,其中包含随时间变化的信号,可能逐步连接到设备上,是可变对象的典型代表。我们将它们建模为具有局部状态变量的 - - 过程 - 函数 - -,通过赋值进行修改。当创建新导线时,会分配一组新的状态变量(通过 - - let 表达式在 - - let 语句在 - - - - make-wire) - make_wire) - -中)并构造和返回一个新的 dispatch - - 过程 - 函数 - -,捕获具有新状态变量的环境。 - - - -导线是在已连接的各种设备之间共享的。因此,通过与一个设备的交互所做的更改将影响所有附加到该导线的其他设备。导线通过调用在建立连接时提供给它的动作 - - 过程 - 函数 - -来与其邻居通信更改。 - - - 数字电路模拟表示导线 - - - 议程 - - - 数字电路模拟议程 - - - 完成模拟器所需的唯一元素是 - - after-delay. - after_delay. - - 这里的想法是我们维护一个数据结构,称为 - 议程,它包含待办事项的计划。 - 为议程定义了以下操作: -
    -
  • - - (make-agenda): - make_agenda(): - - - make_agenda -

    - 返回一个新的空议程。 -
  • -
  • - - (empty-agenda? agenda): - - - is_empty_agenda(agenda) - - - is_empty_agenda -

    - 如果指定的议程为空,则为 true。 -
  • -
  • - - (first-agenda-item agenda): - - - first_agenda_item(agenda) - - - first_agenda_item -

    - 返回议程上的第一个项目。 -
  • -
  • - - - (remove-first-agenda-item! agenda): - - - remove_first_agenda_item(agenda) - - - remove_first_agenda_item -

    - 通过移除第一个项目来修改议程。 -
  • -
  • - - - (add-to-agenda! time action agenda): - - - add_to_agenda(time, action, agenda) - - - add_to_agenda -

    - 通过添加指定的动作 - - 过程 - 函数 - - 来修改议程,以便在指定的时间运行。 -
  • -
  • - - (current-time agenda): - - - current_time(agenda) - - - current_time -

    - 返回当前的模拟时间。 -
  • -
-
- - - 我们使用的特定议程表示为 - - 议程. - 议程. - - 该 - - 过程 - 函数 - - - 延迟后 - after_delay - - 向 - - 议程中添加新元素: - 议程中添加新元素: - - - after_delay - after_delay - 添加到议程中 - 创建议程 - 议程 - -(define (after-delay delay action) - (add-to-agenda! (+ delay (current-time the-agenda)) - action - the-agenda)) - - -function after_delay(delay, action) { - add_to_agenda(delay + current_time(the_agenda), - action, - the_agenda); -} - - - - - - - - 模拟由过程驱动 - propagate,它在 - the-agenda上运行, - 顺序执行议程中的每个过程。 - - - 模拟由函数驱动 - propagate,它顺序执行 - the_agenda - 上的每个函数。 - - - 一般来说,随着模拟运行,新的项目 - 将被添加到议程中,propagate - 将继续模拟,只要议程上还有项目: - - propagate - propagate - 删除第一个议程项目 - 第一个议程项目 - 议程 - -(define (propagate) - (if (empty-agenda? the-agenda) - 'done - (let ((first-item (first-agenda-item the-agenda))) - (first-item) - (remove-first-agenda-item! the-agenda) - (propagate)))) - - -function propagate() { - if (is_empty_agenda(the_agenda)) { - return "done"; - } else { - const first_item = first_agenda_item(the_agenda); - first_item(); - remove_first_agenda_item(the_agenda); - return propagate(); - } -} - - - - - 数字电路模拟议程 - - - - 模拟示例 - - - 数字电路模拟模拟示例 - 数字电路模拟模拟示例 - - 半加器模拟 - - - 我们首先通过初始化议程并为 - 原语函数盒指定延迟: - - the_agenda - make_agenda - -(define the-agenda (make-agenda)) -(define inverter-delay 2) -(define and-gate-delay 3) -(define or-gate-delay 5) - - -const the_agenda = make_agenda(); -const inverter_delay = 2; -const and_gate_delay = 3; -const or_gate_delay = 5; - - - 现在我们定义四根导线,并在其中两根上放置探测器: - - probing_two_wires - make_wire - probe - -(define input-1 (make-wire)) -(define input-2 (make-wire)) -(define sum (make-wire)) -(define carry (make-wire)) - -(probe 'sum sum) - - - sum 0 New-value = 0 - - -const input_1 = make_wire(); -const input_2 = make_wire(); -const sum = make_wire(); -const carry = make_wire(); - -probe("sum", sum); - - -"sum 0, new value = 0" - - - - probe_carry - probing_two_wires - -(probe 'carry carry) - - -carry 0 New-value = 0 - - -probe("carry", carry); - - -"carry 0, new value = 0" - - - - 过程, - 函数, - - 一个在导线上放置探测器的过程/函数,展示了模拟器的 - 一种用法。探测器指示导线每当其信号改变时,需打印新信号值,连同 - 当前时间和识别 - 导线: - 导线: - - - probe在数字电路模拟器中 - 探测器 - 议程 - get_signal - -(define (探测器 名称 导线) - (add-action! 导线 - (lambda () - (newline) - (display 名称) - (display " ") - (display (current-time 议程)) - (display " 新值 = ") - (display (get-signal 导线))))) - - -function probe(name, wire) { - add_action(wire, - () => display(name + " " + - stringify(current_time(the_agenda)) + - ", new value = " + - stringify(get_signal(wire)))); -} - - -function probe(name, wire) { - add_action(wire, - () => name + " " + - stringify(current_time(the_agenda)) + - ", new value = " + - stringify(get_signal(wire))); -} - - - - half_adder_example_2 - half_adder - probe_carry - 'ok' - -(half-adder input-1 input-2 sum carry) - - -ok - - -half_adder(input_1, input_2, sum, carry); - - -"ok" - - - - set_signal_example - half_adder_example_2 - 'done' - -(set-signal! input-1 1) - - -done - - -set_signal(input_1, 1); - - -"done" - - - - propagate_example_1 - set_signal_example - propagate - 'done' - -(propagate) - - -sum 8 New-value = 1 -done - - -propagate(); - - -"sum 8, new value = 1" -"done" - - - 这个 -sum - 信号在时间8变为1。 - 我们现在距离模拟开始时已有八个时间单位。 - 在此时,我们可以设置 - - 输入-2 - input_2 - - 的信号为1,并让值传播: - - set_signal_example_2 - propagate_example_1 - 'done' - -(set-signal! input-2 1) - - - done - - -set_signal(input_2, 1); - - -"done" - - - - propagate_example_2 - set_signal_example_2 - 'done' - -(propagate) - - -carry 11 New value = 1 -sum 16 New value = 0 -done - - -propagate(); - - -"carry 11, new value = 1" -"sum 16, new value = 0" -"done" - - - The carry changes to 1 at time 11 and the - sum changes to 0 at time 16. - - - digital-circuit simulationsample simulation - half-addersimulation of - - - - The internal - - procedure - function - - - accept-action-procedure! - accept_action_function - - - defined in - make_wire - - make-wire - make_wire - - - specifies that when a new action - - procedure - function - - is added to - a wire, the - - procedure - function - - is immediately run. Explain why this initialization - is necessary. In particular, trace through the half-adder example in - the paragraphs above and say how the systems response would differ - if we had defined - - accept-action-procedure! - accept_action_function - - - as - - -(define (accept-action-procedure! proc) - (set! action-procedures (cons proc action-procedures))) - - -function accept_action_function(fun) { - action_functions = pair(fun, action_functions); -} - - - - - - Implementing the agenda - - - digital-circuit simulationagenda implementation - - - Finally, we give details of the agenda data structure, which holds the - - procedures - functions - - that are scheduled for future execution. - - - - The agenda is made up of - time segment, in agenda - time segments. Each time segment is a - pair consisting of a number (the time) and a - queuesimulationin simulation agenda - queue (see - exercise) that holds the - - procedures - functions - - that are scheduled to be run during that time segment. - - make_time_segment - segment_time - segment_queue - make_time_segment - -(define (make-time-segment time queue) - (cons time queue)) - -(define (segment-time s) (car s)) - -(define (segment-queue s) (cdr s)) - - -function make_time_segment(time, queue) { - return pair(time, queue); -} -function segment_time(s) { return head(s); } - -function segment_queue(s) { return tail(s); } - - - We will operate on the time-segment queues using the queue operations - described in section. - - - - The agenda itself is a one-dimensional - tableused in simulation agenda - table of time segments. It - differs from the tables described in section - in that the segments will be sorted in order of increasing time. In - addition, we store the - current time, for simulation agenda - current time (i.e., the time of the last action - that was processed) at the head of the agenda. A newly constructed - agenda has no time segments and has a current time of 0:The - agenda is a - headed list - list(s)headed - headed list, like the tables in section, - but since the list is headed by the time, we do not need an additional - dummy header (such as the - - - *table* symbol - - - "*table*" string - - - used - with tables). - - make_agenda - current_time - set_current_time - segments - set_segments - first_segment - rest_segments - make_agenda - -(define (make-agenda) (list 0)) - -(define (current-time agenda) (car agenda)) - -(define (set-current-time! agenda time) - (set-car! agenda time)) - -(define (segments agenda) (cdr agenda)) - -(define (set-segments! agenda segments) - (set-cdr! agenda segments)) - -(define (first-segment agenda) (car (segments agenda))) - -(define (rest-segments agenda) (cdr (segments agenda))) - - -function make_agenda() { return list(0); } - -function current_time(agenda) { return head(agenda); } - -function set_current_time(agenda, time) { - set_head(agenda, time); -} -function segments(agenda) { return tail(agenda); } - -function set_segments(agenda, segs) { - set_tail(agenda, segs); -} -function first_segment(agenda) { return head(segments(agenda)); } - -function rest_segments(agenda) { return tail(segments(agenda)); } - - - - - - An agenda is empty if it has no time segments: - - is_empty_agenda - is_empty_agenda - make_agenda - -(define (empty-agenda? agenda) - (null? (segments agenda))) - - -function is_empty_agenda(agenda) { - return is_null(segments(agenda)); -} - - - - - - To add an action to an agenda, we first check if the agenda is empty. - If so, we create a time segment for the action and install this in - the agenda. Otherwise, we scan the agenda, examining the time of each - segment. If we find a segment for our appointed time, we add the - action to the associated queue. If we reach a time later than the one - to which we are appointed, we insert a new time segment into the - agenda just before it. If we reach the end of the agenda, we must - create a new time segment at the end. - - add_to_agenda - add_to_agenda - make_time_segment - make_queue - insert_queue - make_time_segment - make_agenda - -(define (add-to-agenda! time action agenda) - (define (belongs-before? segments) - (or (null? segments) - (< time (segment-time (car segments))))) - (define (make-new-time-segment time action) - (let ((q (make-queue))) - (insert-queue! q action) - (make-time-segment time q))) - (define (add-to-segments! segments) - (if (= (segment-time (car segments)) time) - (insert-queue! (segment-queue (car segments)) - action) - (let ((rest (cdr segments))) - (if (belongs-before? rest) - (set-cdr! - segments - (cons (make-new-time-segment time action) - (cdr segments))) - (add-to-segments! rest))))) - (let ((segments (segments agenda))) - (if (belongs-before? segments) - (set-segments! - agenda - (cons (make-new-time-segment time action) - segments)) - (add-to-segments! segments)))) - - -function add_to_agenda(time, action, agenda) { - function belongs_before(segs) { - return is_null(segs) || time < segment_time(head(segs)); - } - function make_new_time_segment(time, action) { - const q = make_queue(); - insert_queue(q, action); - return make_time_segment(time, q); - } - function add_to_segments(segs) { - if (segment_time(head(segs)) === time) { - insert_queue(segment_queue(head(segs)), action); - } else { - const rest = tail(segs); - if (belongs_before(rest)) { - set_tail(segs, pair(make_new_time_segment(time, action), - tail(segs))); - } else { - add_to_segments(rest); - } - } - } - const segs = segments(agenda); - if (belongs_before(segs)) { - set_segments(agenda, - pair(make_new_time_segment(time, action), segs)); - } else { - add_to_segments(segs); - } -} - - - - - - The - - procedure - function - - that removes the first item from the agenda deletes the - item at the front of the queue in the first time segment. If this - deletion makes the time segment empty, we remove it from the list of - segments:Observe that the - - - - if - expression in this - procedure - has no alternative expression. - - - conditional statement in this - function has an - blockempty - empty block as its alternative statement. - - - Such a - conditional statementone-armed (without alternative) - - - one-armed if expression - - - one-armed conditional statement - - - is used to decide whether to do something, rather than to select between two - - - expressions. - An if expression returns an - unspecified value if the predicate is false and there is no - alternative. - - statements. - - - - remove_first_agenda_item - remove_first_agenda_item - make_agenda - is_empty_queue - delete_queue - make_time_segment - -(define (remove-first-agenda-item! agenda) - (let ((q (segment-queue (first-segment agenda)))) - (delete-queue! q) - (if (empty-queue? q) - (set-segments! agenda (rest-segments agenda))))) - - -function remove_first_agenda_item(agenda) { - const q = segment_queue(first_segment(agenda)); - delete_queue(q); - if (is_empty_queue(q)) { - set_segments(agenda, rest_segments(agenda)); - } else {} -} - - - - - - The first agenda item is found at the head of the queue in the first - time segment. Whenever we extract an item, we also update the current - time:In this way, the current time will always be the time - of the action most recently processed. Storing this time at the head - of the agenda ensures that it will still be available even if the - associated time segment has been deleted. - - first_agenda_item - first_agenda_item - is_empty_agenda - make_time_segment - front_queue - -(define (first-agenda-item agenda) - (if (empty-agenda? agenda) - (error "Agenda is empty -- FIRST-AGENDA-ITEM") - (let ((first-seg (first-segment agenda))) - (set-current-time! agenda (segment-time first-seg)) - (front-queue (segment-queue first-seg))))) - - -function first_agenda_item(agenda) { - if (is_empty_agenda(agenda)) { - error("agenda is empty -- first_agenda_item"); - } else { - const first_seg = first_segment(agenda); - set_current_time(agenda, segment_time(first_seg)); - return front_queue(segment_queue(first_seg)); - } -} - - - - - - The - - procedures - functions - - to be run during each time segment of the agenda are kept in a queue. - Thus, the - - procedures - functions - - for each segment are called in the order in which they were added to the - agenda (first in, first out). Explain why this order must be used. In - particular, trace the behavior of an and-gate whose inputs change from - 0,1 to 1,0 in the same segment and say how the behavior would differ if - we stored a segments - - procedures - functions - - in an ordinary list, adding and removing - - procedures - functions - - only at the front (last in, first out). - - - - digital-circuit simulation - digital-circuit simulationagenda implementation - -
diff --git a/xml/cn/chapter1/chapter1.xml b/xml/zh/chapter1/chapter1.xml similarity index 100% rename from xml/cn/chapter1/chapter1.xml rename to xml/zh/chapter1/chapter1.xml diff --git a/xml/zh/chapter1/section1/section1.xml b/xml/zh/chapter1/section1/section1.xml new file mode 100644 index 000000000..f08fc0e4a --- /dev/null +++ b/xml/zh/chapter1/section1/section1.xml @@ -0,0 +1,125 @@ +
+ + + + + 编程的要素 + + + + + + + + + 一种强大的编程语言不仅仅是指示计算机执行任务的手段。这种语言还作为一种框架,让我们构思和组织关于过程的理念。因此,当我们描述一种语言时,应该特别关注该语言提供的将简单理念组合成更复杂理念的手段。每一种强大的语言都有三种机制来实现这一点: +
    +
  • + primitive expressions, + primitive expression + 它们代表该语言所关注的最简单实体, +
  • +
  • + means of combination, 由 + means of combination + combination, means of + 构建简单元素以形成更复杂元素, +
  • +
  • + means of abstraction, + means of abstraction + 通过它可以将复合元素命名并作为单元进行操作。 +
  • +
+
+ + + + + 在编程中,我们处理两种类型的元素: + + + procedure + 过程 + + + + 函数 + + + 和 + data + 数据。(稍后我们将发现它们实际上并不那么不同。) + 非正式地,数据就是我们想操作的东西,而 + + 过程 + 函数 + + 是描述操作数据规则的方法。 + 因此,任何强大的编程语言都应该能够描述原始数据和原始 + + 过程 + 函数 + + ,并且应该拥有用于组合和抽象 + + 过程 + 函数 + + 以及数据的方法。 + + + + + + 在本章中,我们只处理简单的 + numerical data + datanumerical + 数值数据,以便让我们能够专注于构建 + proceduresfunctions的规则。 + + 将数字描述为简单数据的这一表征是一种赤裸裸的虚张声势。事实上,对于任何编程语言来说,数字处理是最棘手且最令人困惑的方面之一。一些典型的问题包括: + integer(s) + real number + number(s)integer vs.real number + 一些计算机系统将整数(例如2)与实数(例如2.71)区分开来。实数2.00是否与整数2不同?用于整数的算术运算与用于实数的运算相同吗?6除以2的结果是3还是3.0?我们能够表示的数字有多大?我们能够表示的精确小数位数是多少?整数的范围是否与实数的范围相同? + numerical analysis + roundoff error + truncation error + 除了这些问题之外,当然还有一系列关于舍入和截断误差的问题这构成了整门数值分析科学。由于本书的重点是大规模程序设计而非数值技术,我们将忽略这些问题。本章中的数值示例将展示在非整数运算中保留有限小数精度时通常观察到的舍入行为。 + + 后续章节中,我们将看到,这些相同的规则也允许我们构建 + + procedures + functions + + 来操作复合数据。 + programmingelements of + + + + + &subsection1.1.1; + + + &subsection1.1.2; + + + &subsection1.1.3; + + + &subsection1.1.4; + + + &subsection1.1.5; + + + &subsection1.1.6; + + + &subsection1.1.7; + + + &subsection1.1.8; + +
diff --git a/xml/cn/chapter1/section1/subsection1.xml b/xml/zh/chapter1/section1/subsection1.xml similarity index 100% rename from xml/cn/chapter1/section1/subsection1.xml rename to xml/zh/chapter1/section1/subsection1.xml diff --git a/xml/cn/chapter1/section1/subsection2.xml b/xml/zh/chapter1/section1/subsection2.xml similarity index 100% rename from xml/cn/chapter1/section1/subsection2.xml rename to xml/zh/chapter1/section1/subsection2.xml diff --git a/xml/zh/chapter1/section1/subsection3.xml b/xml/zh/chapter1/section1/subsection3.xml new file mode 100644 index 000000000..e71a99b5b --- /dev/null +++ b/xml/zh/chapter1/section1/subsection3.xml @@ -0,0 +1,300 @@ + + + + + 组合式求值 + 求值组合式 + + + 运算符组合求值 + 求值运算符组合 + + + + 求值 + + 组合式 + 运算符组合 + + + + + + 本章的目标之一是将关于以过程方式思考的问题隔离开来。举例来说,我们来考虑这样一个情况:在求值 + 运算符 + + 组合时,解释器本身正遵循一个过程。 + + 由于关键字function的重要性,我们通常将对“procedure/procedural”的引用替换为对“function/functional”的引用;以上句子是个例外,对于此处的JavaScript版本,“thinking procedurally”和“procedure”这两个术语或许仍然适用。 + +
    +
  • + 要对 + 组合式, + 运算符组合, + + 进行求值,请按以下步骤操作: +
      +
    1. 求值该组合的 + 子表达式 + 操作数表达式 + + 。
    2. +
    3. + + + 将最左侧子表达式(即运算符)的值作为过程,应用于其它子表达式(即操作数)的值。 + + + 将由运算符所表示的函数应用于操作数值作为参数。 + + +
    4. +
    +
  • +
+ + Scheme版本不区分运算符组合和应用组合。然而,由于中缀记法,JavaScript版本需要针对这两者制定略有不同的规则。本节包含运算符组合的规则,而1.1.5节则介绍了函数应用的新规则。 + + 即使是这一简单规则也阐明了关于过程的一些重要观点。首先,请注意第一步规定,为了完成对组合的求值过程,我们必须先对组合中的每个操作数执行求值。因此,求值规则在本质上是递归递归;也就是说,它包含了一个步骤,即需要调用规则本身。 + + + 或许令人奇怪的是,求值规则在第一步中便规定应先求值组合的最左侧元素,因为此时该元素通常仅为诸如+*这样的运算符,代表一个内置的基本过程,如加法或乘法。我们稍后将看到,能够处理那些其运算符本身即为复合表达式的组合是非常有用的。 + + +
+ +注意看递归这一概念是如何简洁地用来表达 +递归表达复杂过程 +在深度嵌套的组合中,本来会被视为相当复杂的过程。例如,求值 + + + (* (+ 2 (* 4 6)) + (+ 3 5 7)) + + + (2 + 4 * 6) * (3 + 12); + + +需要将求值规则应用于四个不同的组合。我们可以通过将该组合以 +运算符组合的树形表示 +组合视为树树状的组合 +的形式来展现这一过程,如下所示: + + + figure. + + + figure. + + +每个组合都由一个树的节点表示,其对应的树的分支代表从运算符及其操作数延伸出来的部分。树的 +终端节点 +(即没有分支的节点)则代表运算符或数字。从树的角度来看求值,我们可以设想操作数的值从终端节点开始逐层向上渗透,并在更高层次上进行组合。总的来说,我们将看到,递归是一种处理分层、树状对象的非常强大技术。事实上,求值规则中“向上传递值”的形式正是一个通用过程的例子,这个过程被称为 +树累积 +树累积。 + + + +
+ + 树形表示,显示每个子组合的值。 + +
+
+ +
+
+ + 树形表示,显示每个子表达式的值。 + +
+
+
+
+ + + +接下来,请注意,第一步的重复应用使我们进入一个阶段,此时我们需要求值的对象不再是组合,而是原始表达式,例如 + + + 数字、内置运算符或其他名称。 + + + 数字或名称。 + + +我们通过规定来处理原始情况 +原始表达式的求值 +求值原始表达式 +,即规定: +
    +
  • + 数字的值就是它们所表示的数字, + +
  • + + +
  • + 内置运算符的值是执行相应操作的机器指令序列,且 +
  • +
    +
    + + 在 JavaScript 中,运算符不是值,因此这一项在 JavaScript 版本中不适用。 + +
  • + 其他名称的值是环境中与这些名称相关联的对象。 +
  • +
+ + + 我们可以规定,例如 +* 这样的符号也包含在全局环境中,并与它们对应的机器指令序列(作为其)关联,从而将第二条规则视为第三条规则的特殊情况。 + + +需要注意的关键点是 +环境作为求值上下文的作用 +在确定表达式中 + + + 符号 + + + 名称 + + +的含义时所起的作用。在像 + + + Lisp, + + + JavaScript, + + +这样的交互式语言中,如果不指定提供符号 + + x(甚至连 +)含义的信息,即谈论类似 + + + (+ x 1) + + + x + 1 + + +表达式的值是毫无意义的。正如我们将在第三章中看到的那样,将环境作为提供求值上下文的概念将在我们对程序执行的理解中发挥重要作用。 + + + 组合的求值 + 求值组合 + + + 运算符组合的求值 + 求值运算符组合 + + +
+ + 请注意,以上给出的求值规则没有处理 + + + define为何是一种特殊形式 + 定义。 + + + 常量声明为何是一种句法形式 + 声明。 + + + 例如,求值 + + + (define x 3) + + + const x = 3; + + + 不会将 + + + define + + + 一个相等运算符 = + + + 应用于两个参数,其中一个参数是 + + + 符号 + + + 名称 + + + x的值,而另一个为3,因为 + + + define + + + 声明 + + + 的目的正是为了将x与一个值关联。 + (也就是说, + + + (define x 3) + + + const x = 3; + + + 不是一个组合。) + + + + + 对于一般求值规则的这类例外情况被称为 + 特殊形式 + 特殊形式。 + 定义 + 是迄今为止我们见过的唯一一个特殊形式的例子,但我们很快还会遇到其他例子。 + 求值特殊形式的求值 + 每个特殊形式都有其各自的求值规则。各种表达式(每种都有其相关的求值规则)构成了编程语言的 + 语法编程语言 + 。与大多数其他编程语言相比,Lisp 具有非常简单的语法;也就是说,表达式的求值规则可由一个简单的一般规则和少数特殊形式的专门规则来描述。 + 那些仅作为方便的、可将可以用更统一方式书写的内容的替代表面结构的特殊句法形式,有时被称为 + 语法糖,借用了Perlis, Alan J.妙语 + Landin, Peter + 语法糖 + 分号 + 分号的癌症 + Pascal + LispPascal 对比 + 这一短语,该短语由 Peter Landin 创造。与其他语言的使用者相比,Lisp 程序员通常对语法问题不那么在意。(相反,翻阅任何一本 Pascal 手册,你会发现其中大部分内容都在描述语法。)这种对语法的轻视部分源自 Lisp 的灵活性,使得更换表面语法变得十分容易;部分则来自这样一种观察:许多所谓“方便”的句法结构使语言缺乏统一性,在程序变得庞大和复杂时,反而会带来更多问题而得不偿失。用 Alan Perlis 的话说, + 语法糖会导致分号的癌症。 + + + + + 在const中的字母以粗体显示,以表明它 + + + 单词 const + + 是 JavaScript 中的 + 关键字 + 关键字。关键字具有特定含义,因此不能用作名称。语句中的一个关键字或多个关键字的组合指示 JavaScript 解释器以特殊方式处理该语句。每种 + 句法形式 + 句法形式都有其各自的求值规则。各种语句和表达式(每个都有其相关的求值规则)构成了编程语言的 + 语法编程语言 + 。 + + + + +
diff --git a/xml/zh/chapter1/section1/subsection4.xml b/xml/zh/chapter1/section1/subsection4.xml new file mode 100644 index 000000000..dfccef3ae --- /dev/null +++ b/xml/zh/chapter1/section1/subsection4.xml @@ -0,0 +1,621 @@ + + + + + 复合 过程 + 函数 + + + + + + 我们已经在 + + Lisp + JavaScript + + 中识别出任何强大编程语言中必须出现的一些元素: +
    +
  • + 数字和算术运算是原始数据,并且 + + 过程. + 函数. + +
  • +
  • + 组合的嵌套提供了组合运算的一种手段。 +
  • +
  • + 常量声明通过将名称与值关联起来,提供了一种有限的抽象手段。 +
  • +
+ 现在我们将学习关于 + + + 过程的定义 + + + 的声明 + + + + + 过程定义, + + + 函数声明, + + + 一种更强大的抽象技术,通过它,一个复合运算可以被赋予一个名称,然后作为一个整体来引用。 +
+ + + + 我们首先考察如何表达“平方”这一概念: + 平方 + 我们可以这样说, + + + 要使一个数平方,将它乘以它本身。 + + + 要使一个数平方,把它与其自身相乘。 + + + + Scheme和JavaScript中的表达略有不同,以更好地适应JavaScript中的中缀表示法。 + + 这在我们的语言中表达为 + + square + function (keyword) + keywordsfunctionfunction + square_definition + square_example + +(define (square x) (* x x)) + + +function square(x) { + return x * x; +} + + + + square_example + + (square 14) + + +square(14); + + + + + + + + 我们可以按如下方式理解: + + + +(define (square x) (* x x)) +;; ^ ^ ^ ^ ^ ^ +;; 为了对某物求平方,将其与自身相乘。 + + +function square( x ) { return x * x; } +// ^ ^ ^ ^ ^ ^ ^ +// 为了对某物求平方,将其乘以它自身。 + + + + + + + \begin{flushleft}\normalcodesize + \begin{tabular}{@{}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c@{~}c} + \tt\textbf{function} & \tt square( & \tt x & \tt ) \verb+{+ & \tt\textbf{return} & \tt x & \tt * & \tt x & \tt; \verb+}+ \\ + $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ & & & $\Big\uparrow$ & $\Big\uparrow$ & $\Big\uparrow$ \\[4pt] + \normalsize To & \normalsize square & \normalsize something, & & \normalsize take &\normalsize it & \normalsize times & \normalsize itself. \\ + \end{tabular} + \end{flushleft} + + + 我们在这里有一个 + + + compound procedure + procedurecompound + compound procedure, + + + compound function + compound + compound function, + + + 它被命名为 square。这 + + procedure + function + + 表示将某物与自身相乘的操作。待相乘的数被赋予一个局部名称 x, + 这一名称在自然语言中起着代词相似的作用。 + + + namingof procedures + procedurenaming (with define) + procedurecreating with define + + + namingof functions + naming (with function declaration) + creating with function declaration + + + 计算 + + + definition + + + declaration + + + 会创建出这个复合 + + + procedure + + + function + + + 并将其与名称 + syntactic formsfunction declaration + + function declaration + declarationfunctionof function (function) + square关联。注意,这里结合了两种不同的操作:我们既在创建 + + + procedure, + + + function, + + 又在赋予其名称 square。实际上,将这两个概念分离开来非常重要——既可以创建未命名的 + + + procedures + + + functions + + ,也可以为已经创建的 + + + procedures + + + functions + + 赋予名称。我们将在章节中看到如何实现这一点。 + + + + + + + 过程定义的一般形式 + + + 函数声明的最简单形式 + + + 是 + + +(define ($\langle \textit{name} \rangle$ $\langle\textit{formal parameters}\rangle$) $\langle \textit{body} \rangle$) + + +function name(parameters) { return expression; } + + + The + + + 名字过程的过程 + 过程的名字 + $\langle \textit{name}\rangle$ + + + 名字函数的函数 + 的名字 + name + + + 是一个将与环境中 + + + 过程 + + + 函数 + + + 定义关联的符号。在本书中,我们将通过使用斜体符号 + 本书的符号表达式语法中的斜体符号 + 语法描述表达式的一般语法 + 来描述表达式的一般语法,这些斜体符号由 + + 角括号 + + 例如, + + + $\langle \textit{name}\rangle$ + 表示 + + + name + 表示 + + + 表达式中所需填充的 + The + + + 过程的形式参数 + 形式参数 + + + 的参数 + 参数 + + + + + $\langle \textit{formal parameters}\rangle$ + + + parameters + + + 是在 + + + 过程中 + + + 函数中 + + + 用以引用其对应参数的名称,这些参数属于 + + + 该过程。 + + + 该函数。 + + + + + + The + procedurebody of + body of a procedure + $\langle \textit{body} \rangle$ + 是一个表达式,当形式参数被替换为该过程所应用的实际参数时,该表达式将产生过程调用的值。此外, + sequence of expressionsproceduresin procedure body + 一般来说,过程的主体可以是一系列表达式。在这种情况下,解释器依次计算序列中的每个表达式,并返回最后一个表达式的值作为过程调用的结果。 + The $\langle \textit{name} \rangle$ + 和 + $\langle \textit{formal parameters} \rangle$ + 被组合在 + parenthesesprocedurein procedure definition + proceduredefinition of + 括号内,就如同在实际调用被定义的过程时那样。 + + + The parameters + 被组合在 + parenthesesfunctionin function declaration + parenthesesfunctionin function declaration + 括号内,并用逗号分隔,就像在应用被声明的函数时那样。 + return statement + return value + return (keyword) + syntactic formsreturn statement + keywordsreturnreturn + 在最简单的形式中,body of + body of a function + body + 的函数声明是单个 return statement,此外, + sequence of statementsfunctionsin function body + 一般来说,函数的主体可以是一系列语句。在这种情况下,解释器依次计算序列中的每个语句,直到一个返回语句确定函数调用的值。 + 它由关键字 + return + 开始,后跟将产生函数调用值的 return expression,当函数的 + + formal + + 参数被替换为该函数所应用的实际参数时。与常量声明和表达式语句类似, + return 语句 + semicolon (;)ending statement + declaration of + 都以分号结束。 + + + + + + + + 定义了square后,我们现在可以使用它: + + + 声明了square后,我们现在可以在一个函数应用表达式中使用它,该表达式通过在末尾加分号变成一个语句: + + + + square_definition + +(square 21) + + +441 + + +square(21); + + +441 + + + + + + + + 由于操作符组合在语法上与函数应用不同,JavaScript版本需要在此明确说明函数应用的求值规则。这为下一小节中的替换模型奠定了基础。 + + 函数应用紧随操作符组合之后出现,是我们遇到的第二种将表达式组合成更大表达式的方式。 + 函数应用的一般形式是 + + +function-expression(argument-expressions) + + + 其中,函数表达式 + function-expression指定了要应用于用逗号分隔的 + 参数 + argument-expressions的函数。 + 为了求值函数应用,解释器遵循 + evaluationof function application + function applicationevaluation of的过程,这一点与在第节中描述的操作符组合的求值过程非常相似。 +
    +
  • 为了求值一个函数应用,请执行以下步骤: +
      +
    1. + 计算该应用的子表达式,即函数表达式和参数表达式。 +
    2. +
    3. + 将函数表达式的值(一个函数)应用于参数表达式的值。 +
    4. +
    +
  • +
+
+
+ + square_definition + +(square (+ 2 5)) + + +49 + + +square(2 + 5); + + +49 + + + + + 这里,参数表达式本身是一个复合表达式,即操作符组合2 + 5。 + + + + square_square + 81 + square_definition + +(square (square 3)) + + +81 + + +square(square(3)); + + +81 + + + + + 当然,函数应用表达式也可以作为参数表达式使用。 + + +
+ + + + + 我们也可以将square用作构建其它 + + + 程序。 + + + 函数。 + + + 的构件。例如,$x^2 +y^2$可以表示为 + + +(+ (square x) (square y)) + + +square(x) + square(y) + + + 我们可以很容易地 + + 定义 + 声明 + + 一个 + + + 程序 sum-of-squares + + + 函数 sum_of_squares多部分名称(例如sum_of_squares)的书写方式会影响程序的可读性,不同的编程社区对此存在分歧。驼峰式 根据常见的JavaScript约定,即所谓的驼峰式,该名称应写作sumOfSquares。本书使用的约定是蛇形命名法,其更多地模仿了本书Scheme版本中采用的惯例,在该版本中连字符起到了下划线的作用。 + + + ,该过程接受任意两个数字作为参数,产生它们平方和的结果: + + sum_of_squares + sum_of_squares + 25 + sum_of_squares_example + square_definition + +(define (sum-of-squares x y) + (+ (square x) (square y))) + +(sum-of-squares 3 4) + + +function sum_of_squares(x, y) { + return square(x) + square(y); +} + + + + sum_of_squares_example + 25 + sum_of_squares + +(sum-of-squares 3 4) + + +25 + + +sum_of_squares(3, 4); + + +25 + + + 现在我们可以使用 + + + sum-of-squares + + + sum_of_squares + + + 作为构建更复杂 + + + 程序: + + + 函数: + + + 的构件。 + + f + f_example + 136 + sum_of_squares + +(define (f a) + (sum-of-squares (+ a 1) (* a 2))) + + +function f(a) { + return sum_of_squares(a + 1, a * 2); +} + + + + f_example + f + +(f 5) + + +136 + + +f(5); + + +136 + + + + + + + + + 复合过程 + 复合过程用法与基本过程相同 + 的使用方式与基本过程完全一致。实际上,仅从上面给出的sum-of-squares的定义中, + 无法判断square到底是像+*那样被内置于解释器, + 还是被定义为复合过程。 + + + 除了复合函数之外,任何JavaScript环境都提供 + 基本的 + 基本函数 + ,这些基本函数被内置于解释器中或从库中加载。 + 本书所用的JavaScript环境 + 除了由操作符提供的基本函数之外,本书使用的JavaScript环境还包括额外的基本函数, + 例如函数 + math_log(基本函数) + math_logMath.log + math_log, + 它用于计算其参数的自然对数。我们的JavaScript环境包含ECMAScript的 + Math对象 + 的所有函数和常量,这些名称均以math_$\ldots$为前缀。 + 例如,ECMAScript的Math.log + 可以作为math_log使用。本书的MIT出版社网页还提供了JavaScript包 + sicp JavaScript package + JavaScript包 sicp + sicp,该包提供了书中认为属于基本函数的所有其他JavaScript函数。 + + 这些额外的基本函数的用法与 + 复合函数使用方式类似基本函数 + 完全一致;计算表达式math_log(1)的结果为数字0。 + 实际上,仅从上面给出的sum_of_squares的定义中, + 无法判断square是内置于解释器、从库加载, + 还是被定义为复合函数。 + + + + +
diff --git a/xml/cn/chapter1/section1/subsection5.xml b/xml/zh/chapter1/section1/subsection5.xml similarity index 100% rename from xml/cn/chapter1/section1/subsection5.xml rename to xml/zh/chapter1/section1/subsection5.xml diff --git a/xml/cn/chapter1/section1/subsection6.xml b/xml/zh/chapter1/section1/subsection6.xml similarity index 100% rename from xml/cn/chapter1/section1/subsection6.xml rename to xml/zh/chapter1/section1/subsection6.xml diff --git a/xml/cn/chapter1/section1/subsection7.xml b/xml/zh/chapter1/section1/subsection7.xml similarity index 100% rename from xml/cn/chapter1/section1/subsection7.xml rename to xml/zh/chapter1/section1/subsection7.xml diff --git a/xml/cn/chapter1/section1/subsection8.xml b/xml/zh/chapter1/section1/subsection8.xml similarity index 100% rename from xml/cn/chapter1/section1/subsection8.xml rename to xml/zh/chapter1/section1/subsection8.xml diff --git a/xml/cn/chapter1/section2/section2.xml b/xml/zh/chapter1/section2/section2.xml similarity index 100% rename from xml/cn/chapter1/section2/section2.xml rename to xml/zh/chapter1/section2/section2.xml diff --git a/xml/cn/chapter1/section2/subsection1.xml b/xml/zh/chapter1/section2/subsection1.xml similarity index 100% rename from xml/cn/chapter1/section2/subsection1.xml rename to xml/zh/chapter1/section2/subsection1.xml diff --git a/xml/cn/chapter1/section2/subsection2.xml b/xml/zh/chapter1/section2/subsection2.xml similarity index 100% rename from xml/cn/chapter1/section2/subsection2.xml rename to xml/zh/chapter1/section2/subsection2.xml diff --git a/xml/cn/chapter1/section2/subsection3.xml b/xml/zh/chapter1/section2/subsection3.xml similarity index 100% rename from xml/cn/chapter1/section2/subsection3.xml rename to xml/zh/chapter1/section2/subsection3.xml diff --git a/xml/cn/chapter1/section2/subsection4.xml b/xml/zh/chapter1/section2/subsection4.xml similarity index 100% rename from xml/cn/chapter1/section2/subsection4.xml rename to xml/zh/chapter1/section2/subsection4.xml diff --git a/xml/cn/chapter1/section2/subsection5.xml b/xml/zh/chapter1/section2/subsection5.xml similarity index 100% rename from xml/cn/chapter1/section2/subsection5.xml rename to xml/zh/chapter1/section2/subsection5.xml diff --git a/xml/cn/chapter1/section2/subsection6.xml b/xml/zh/chapter1/section2/subsection6.xml similarity index 100% rename from xml/cn/chapter1/section2/subsection6.xml rename to xml/zh/chapter1/section2/subsection6.xml diff --git a/xml/cn/chapter1/section3/section3.xml b/xml/zh/chapter1/section3/section3.xml similarity index 100% rename from xml/cn/chapter1/section3/section3.xml rename to xml/zh/chapter1/section3/section3.xml diff --git a/xml/cn/chapter1/section3/subsection1.xml b/xml/zh/chapter1/section3/subsection1.xml similarity index 100% rename from xml/cn/chapter1/section3/subsection1.xml rename to xml/zh/chapter1/section3/subsection1.xml diff --git a/xml/cn/chapter1/section3/subsection2.xml b/xml/zh/chapter1/section3/subsection2.xml similarity index 100% rename from xml/cn/chapter1/section3/subsection2.xml rename to xml/zh/chapter1/section3/subsection2.xml diff --git a/xml/cn/chapter1/section3/subsection3.xml b/xml/zh/chapter1/section3/subsection3.xml similarity index 100% rename from xml/cn/chapter1/section3/subsection3.xml rename to xml/zh/chapter1/section3/subsection3.xml diff --git a/xml/cn/chapter1/section3/subsection4.xml b/xml/zh/chapter1/section3/subsection4.xml similarity index 100% rename from xml/cn/chapter1/section3/subsection4.xml rename to xml/zh/chapter1/section3/subsection4.xml diff --git a/xml/cn/chapter2/chapter2.xml b/xml/zh/chapter2/chapter2.xml similarity index 100% rename from xml/cn/chapter2/chapter2.xml rename to xml/zh/chapter2/chapter2.xml diff --git a/xml/cn/chapter2/section1/section1.xml b/xml/zh/chapter2/section1/section1.xml similarity index 100% rename from xml/cn/chapter2/section1/section1.xml rename to xml/zh/chapter2/section1/section1.xml diff --git a/xml/cn/chapter2/section1/subsection1.xml b/xml/zh/chapter2/section1/subsection1.xml similarity index 100% rename from xml/cn/chapter2/section1/subsection1.xml rename to xml/zh/chapter2/section1/subsection1.xml diff --git a/xml/cn/chapter2/section1/subsection2.xml b/xml/zh/chapter2/section1/subsection2.xml similarity index 100% rename from xml/cn/chapter2/section1/subsection2.xml rename to xml/zh/chapter2/section1/subsection2.xml diff --git a/xml/cn/chapter2/section1/subsection3.xml b/xml/zh/chapter2/section1/subsection3.xml similarity index 100% rename from xml/cn/chapter2/section1/subsection3.xml rename to xml/zh/chapter2/section1/subsection3.xml diff --git a/xml/cn/chapter2/section1/subsection4.xml b/xml/zh/chapter2/section1/subsection4.xml similarity index 100% rename from xml/cn/chapter2/section1/subsection4.xml rename to xml/zh/chapter2/section1/subsection4.xml diff --git a/xml/cn/chapter2/section2/section2.xml b/xml/zh/chapter2/section2/section2.xml similarity index 100% rename from xml/cn/chapter2/section2/section2.xml rename to xml/zh/chapter2/section2/section2.xml diff --git a/xml/cn/chapter2/section2/subsection1.xml b/xml/zh/chapter2/section2/subsection1.xml similarity index 100% rename from xml/cn/chapter2/section2/subsection1.xml rename to xml/zh/chapter2/section2/subsection1.xml diff --git a/xml/cn/chapter2/section2/subsection2.xml b/xml/zh/chapter2/section2/subsection2.xml similarity index 100% rename from xml/cn/chapter2/section2/subsection2.xml rename to xml/zh/chapter2/section2/subsection2.xml diff --git a/xml/cn/chapter2/section2/subsection3.xml b/xml/zh/chapter2/section2/subsection3.xml similarity index 100% rename from xml/cn/chapter2/section2/subsection3.xml rename to xml/zh/chapter2/section2/subsection3.xml diff --git a/xml/cn/chapter2/section2/subsection4.xml b/xml/zh/chapter2/section2/subsection4.xml similarity index 100% rename from xml/cn/chapter2/section2/subsection4.xml rename to xml/zh/chapter2/section2/subsection4.xml diff --git a/xml/cn/chapter2/section3/section3.xml b/xml/zh/chapter2/section3/section3.xml similarity index 100% rename from xml/cn/chapter2/section3/section3.xml rename to xml/zh/chapter2/section3/section3.xml diff --git a/xml/cn/chapter2/section3/subsection1.xml b/xml/zh/chapter2/section3/subsection1.xml similarity index 100% rename from xml/cn/chapter2/section3/subsection1.xml rename to xml/zh/chapter2/section3/subsection1.xml diff --git a/xml/cn/chapter2/section3/subsection2.xml b/xml/zh/chapter2/section3/subsection2.xml similarity index 100% rename from xml/cn/chapter2/section3/subsection2.xml rename to xml/zh/chapter2/section3/subsection2.xml diff --git a/xml/cn/chapter2/section3/subsection3.xml b/xml/zh/chapter2/section3/subsection3.xml similarity index 100% rename from xml/cn/chapter2/section3/subsection3.xml rename to xml/zh/chapter2/section3/subsection3.xml diff --git a/xml/cn/chapter2/section3/subsection4.xml b/xml/zh/chapter2/section3/subsection4.xml similarity index 100% rename from xml/cn/chapter2/section3/subsection4.xml rename to xml/zh/chapter2/section3/subsection4.xml diff --git a/xml/zh/chapter2/section4/section4.xml b/xml/zh/chapter2/section4/section4.xml new file mode 100644 index 000000000..f4611d0af --- /dev/null +++ b/xml/zh/chapter2/section4/section4.xml @@ -0,0 +1,163 @@ +
+ + 抽象数据的多重表示 + + + + + + + + + + 数据抽象 + + + + + 我们已经介绍了数据抽象,这是一种构造系统的方法,使得程序的大部分可以独立于实现程序所操作的数据对象时所涉及的选择而被指定。例如,我们在 中展示了如何将设计使用有理数的程序的任务与用计算机语言的构造复合数据的原始机制来实现有理数的任务分开。关键思想是在此竖立一个 抽象屏障 —— 在本例中,即有理数的选择器和构造器 + + (make-rat, + (make_rat, + + numer, + denom),它将有理数的使用方式与它们在列表结构中的底层表示隔离开来。一个类似的抽象屏障将执行有理算术的 + + 过程 + 函数 + + 的细节隔离开来, + + (add-rat, + (add_rat, + + + sub-rat, + sub_rat, + + + mul-rat, + mul_rat, + + 和 + + div-rat) + div_rat) + + 与使用有理数的 + + 过程 + 函数 + + 隔离。由此产生的程序具有图 所示的结构。 + + + + + 这些数据抽象屏障是控制复杂性强大的工具。通过将数据对象的底层表示隔离开来,我们可以将设计大型程序的任务划分为可以独立进行的较小任务。但这种数据抽象还不够强大,因为对于某个数据对象来说,“底层表示”这个说法并不总是有意义。 + + + + + 首先,一个数据对象可能有不止一种有用的表现形式,而我们可能希望设计能够处理多种表现形式的系统。举个简单的例子,复数可以用两种几乎等价的方式来表示:直角坐标形式(实部和虚部)以及极坐标形式(模和角)。有时候直角坐标形式更为合适,有时候极坐标形式更为合适。事实上,完全可以想象出这样一种系统,在该系统中,复数以这两种方式表示,并且用于操作复数的 + + 过程 + 函数 + + 都可以适用于任一表现形式。 + + + + + 更重要的是,编程系统通常由许多人在长时间内共同设计,并且受到随着时间变化的需求的制约。在这种环境下,显然不可能让所有人在事前就对数据表示的选择达成一致。因此,除了将表示与使用隔离开来的数据抽象屏障之外,我们还需要将不同设计选择彼此隔离并允许它们在单个程序中共存的抽象屏障。此外,由于大型程序通常是通过组合那些在各自独立环境中设计的 + pre-existing + preexisting + + 模块构成的,我们需要一些约定,使程序员能够additivity additively地将模块纳入更大的系统,也就是说,无须重新设计或重新实现这些模块。 + + + + + 在本节中,我们将学习如何处理可能由程序的不同部分以不同方式表示的数据。这需要构造通用过程函数 通用 通用过程函数过程函数,它们可以作用于以不止一种方式表示的数据。我们构造通用过程函数的主要技术将是以具有类型标签 类型标签的数据对象来工作,也就是说,这些数据对象包含关于如何处理它们的明确信息。我们还将讨论数据导向编程 数据导向编程,这是一种强大且方便的实现策略,用于添加性地组装具有通用操作的系统。 + + + + + 我们首先介绍简单的复数示例。我们将看到类型标签和数据导向风格如何使我们能够在保持抽象复数算术 算术关于复数 复数数据对象概念的同时,设计独立的矩形和极坐标表示形式。 + 我们将通过定义针对复数的算术 + + 过程 + 函数 + + 来实现这一点,这些算术 + + (add-complex, + (add_complex, + + + sub-complex, + sub_complex, + + + mul-complex, + mul_complex, + + 和 + + div-complex) + div_complex) + + 的定义,这些定义基于通用选择器,这些选择器可以独立于复数的表示方式来访问复数的各个部分。由此产生的复数系统,如下所示 + + + figure, + + + figure, + + + 包含两种不同类型的抽象屏障在复数系统中的抽象屏障。水平抽象屏障起着与 + figure中相同的作用。它们将“高层”操作与“低层”表示隔离开来。此外,还有一个垂直屏障,使我们能够分别设计和安装替代表示。 + + +
+
+ + 复数系统中的数据抽象屏障。 + + +
+
+ +
+
+ + 复数系统中的数据抽象屏障。 + + +
+
+
+
+ + + + 在节中,我们将展示如何使用类型标签和数据导向风格来开发一个通用算术软件包。这提供了 + + 过程 + 函数 + + (add, mul等)可用于操作各种数字,并且在需要新的数字类型时可以轻松扩展。在节中,我们还将展示如何在执行符号代数的系统中使用通用算术。 + + + + + &subsection2.4.1; + + + &subsection2.4.2; + + + &subsection2.4.3; + +
diff --git a/xml/zh/chapter2/section4/subsection1.xml b/xml/zh/chapter2/section4/subsection1.xml new file mode 100644 index 000000000..40c3bf6db --- /dev/null +++ b/xml/zh/chapter2/section4/subsection1.xml @@ -0,0 +1,362 @@ + + + + + + 复数直角坐标 vs.极坐标形式 + + + 我们将开发一个系统,用于对复数执行算术运算,这仅作为一个简单但不切实际的示例,展示一个使用通用操作的程序。我们首先讨论将复数表示为有序对的两种合理方法:直角坐标形式(实部和虚部)以及极坐标形式(幅值和角度)。在实际计算系统中,由于在直角坐标形式与极坐标形式转换过程中会产生舍入误差,因此通常更倾向于使用直角坐标形式。这就是为什么这个复数示例不切实际。然而,它清楚地阐述了使用通用操作设计系统的方法,并为本章后面将开发的更为实质性的系统提供了良好的引介。 Section 将展示如何通过使用类型标签和通用操作,使这两种表示方法在同一系统中共存。 + + + 类似于有理数,复数自然地表示为有序对。复数集合可以看作是一个具有两个正交轴的二维空间,即实数轴和虚数轴。(参见图。)从这个角度看,复数$z=x+iy$(其中$i^{2} =-1$)可视为平面上一个点,其实部为$x$,虚部为$y$。在这种表示法中,复数的加法简化为坐标的加法: + + \[ + \begin{array}{lll} + \mbox{Real-part}(z_{1}+z_{2}) & = & + \mbox{Real-part}(z_{1})+\mbox{Real-part}(z_{2}) \\[1ex] + \mbox{Imaginary-part}(z_{1}+z_{2}) & = & + \mbox{Imaginary-part}(z_{1})+\mbox{Imaginary-part}(z_{2}) + \end{array} + \] + + + + 当进行复数相乘时,更自然的方法是将复数用极坐标形式表示,也就是用模长和角度来表示(见图中用 $r$$A$ 标记的部分)。两个复数的乘积就是将其中一个复数按另一个复数的模长拉伸后,再按照后者的角度旋转所得到的向量citeturn0file1: + + \[ + \begin{array}{lll} + \mbox{Magnitude}(z_{1}\cdot z_{2}) & = & + \mbox{Magnitude}(z_{1})\cdot\mbox{Magnitude}(z_{2})\\[1ex] + \mbox{Angle}(z_{1}\cdot z_{2}) & = & + \mbox{Angle}(z_{1})+\mbox{Angle}(z_{2}) + \end{array} + \] + + + + 因此,复数有两种不同的表示方法,分别适用于不同的操作。然而,从编写使用复数的程序者的角度来看,数据抽象原理表明,无论计算机采用哪种表示方法,所有处理复数的操作都应当可用。例如,能够求出由直角坐标指定的复数的幅度通常是非常有用的。同样,能够确定由极坐标指定的复数的实部往往也很有用。 +
+
+ 复数作为平面上的点。 + +
+
+ + + + + + 为了设计这样一个系统,我们可以遵循我们在设计有理数包时所采用的 + 数据抽象 + 数据抽象策略,该策略在
中描述。假设复数的操作是以四个选择器为基础实现的: + + real-part, + real_part, + + + imag-part, + imag_part, + + + magnitude, + magnitude, + + 和 angle。还假设我们有两个用于构造复数的 + + 过程 + 函数 + + 其中 + + make-from-real-imag + + make_from_real_imag + + + 返回具有指定实部和虚部的复数,而 + + + make-from-mag-ang + + + make_from_mag_ang + + + 返回具有指定模和角的复数。这两个 + + 过程 + 函数 + + 的特性是,对于任意复数 + z,无论使用 + + +(make-from-real-imag (real-part z) (imag-part z)) + + +make_from_real_imag(real_part(z), imag_part(z)); + + + 或者 + + +(make-from-mag-ang (magnitude z) (angle z)) + + +make_from_mag_ang(magnitude(z), angle(z)); + + + 均能生成与 z相等的复数。 +
+ + + + + 利用这些构造器和选择器,我们可以使用构造器和选择器所指定的抽象数据来实现复数的算术运算,就像我们在
中对有理数所做的一样。如上面的公式所示,我们可以用实部和虚部分别对复数进行加法和减法运算,而用模和角分别对复数进行乘法和除法运算: + + add_complex + sub_complex + mul_complex + div_complex + complex_number_calculation + +(define (add-complex z1 z2) + (make-from-real-imag (+ (real-part z1) (real-part z2)) + (+ (imag-part z1) (imag-part z2)))) + +(define (sub-complex z1 z2) + (make-from-real-imag (- (real-part z1) (real-part z2)) + (- (imag-part z1) (imag-part z2)))) + +(define (mul-complex z1 z2) + (make-from-mag-ang (* (magnitude z1) (magnitude z2)) + (+ (angle z1) (angle z2)))) + +(define (div-complex z1 z2) + (make-from-mag-ang (/ (magnitude z1) (magnitude z2)) + (- (angle z1) (angle z2)))) + + +function add_complex(z1, z2) { + return make_from_real_imag(real_part(z1) + real_part(z2), + imag_part(z1) + imag_part(z2)); +} +function sub_complex(z1, z2) { + return make_from_real_imag(real_part(z1) - real_part(z2), + imag_part(z1) - imag_part(z2)); +} +function mul_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) * magnitude(z2), + angle(z1) + angle(z2)); +} +function div_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) / magnitude(z2), + angle(z1) - angle(z2)); +} + + +
+ + + + 为了完善复数包,我们必须选择一种表示方式,并且必须用原始数字和原始列表结构来实现构造器和选择器。有两种明显的方法可以做到这一点:我们可以将复数表示为一对(实部,虚部)的矩形形式,或者表示为一对(模,角)的极坐标形式。我们该选择哪一种呢? + + + + + 为了使这些不同的选择变得具体,请设想有两个程序员,Ben Bitdiddle 和 Alyssa P. Hacker,他们正在独立设计复数系统的表示方法。 + Ben 选择以复数矩形形式来表示复数。 + 基于这一选择,提取复数的实部和虚部非常直接,构造具有给定实部和虚部的复数也同样简单。 + 为了求出模和角,或者构造具有给定模和角的复数,他使用了三角函数关系 + + \[ + \begin{array}{lllllll} + x & = & r\ \cos A & \quad \quad \quad & r & = & \sqrt{x^2 + y^2} \\ + y & = & r\ \sin A & & A & = & \arctan (y,x) + \end{array} + \] + + 该关系将实部和虚部 ($x$, $y$) 与模和角 ($(r, A)$) 联系起来。 + 此处所说的反正切函数, + + + 由 Scheme 计算的 + 反正切函数 + math_atan2 (基本函数) + math_atan2Math.atan2 + atan 过程, + + + 由 JavaScript 计算的 + 反正切函数 + math_atan2 (基本函数) + math_atan2Math.atan2 + math_atan2 函数, + + + 定义为接受两个参数 $y$$x$,并返回其正切值为 $y/x$ 的角。参数的符号决定了该角所在的象限。 + 因此,Ben 的表示方法由下列选择器和构造器给出: + + real_part矩形形式 + imag_part矩形形式 + magnitude矩形形式 + angle矩形形式 + make_from_real_imag矩形形式 + make_from_mag_ang矩形形式 + make_complex_number1 + complex_number_calculation + square_definition + make_complex_number_example + -3 + +(define (real-part z) (car z)) + +(define (imag-part z) (cdr z)) + +(define (magnitude z) + (sqrt (+ (square (real-part z)) (square (imag-part z))))) + +(define (angle z) + (atan (imag-part z) (real-part z))) + +(define (make-from-real-imag x y) (cons x y)) + +(define (make-from-mag-ang r a) + (cons (* r (cos a)) (* r (sin a)))) + + +function real_part(z) { return head(z); } + +function imag_part(z) { return tail(z); } + +function magnitude(z) { + return math_sqrt(square(real_part(z)) + square(imag_part(z))); +} +function angle(z) { + return math_atan2(imag_part(z), real_part(z)); +} +function make_from_real_imag(x, y) { return pair(x, y); } + +function make_from_mag_ang(r, a) { + return pair(r * math_cos(a), r * math_sin(a)); +} + + + + + make_complex_number_example + +const my_co_num_1 = make_from_real_imag(2.5, -0.5); +const my_co_num_2 = make_from_real_imag(2.5, -0.5); + +const result = add_complex(my_co_num_1, + mul_complex(my_co_num_2, + my_co_num_2)); + +imag_part(result); + + + + + + + + + 与之相反,Alyssa 选择以 + 复数极坐标形式 + 来表示复数。 + + 对她来说,选择模和角非常直接,但她必须使用 + 三角函数关系 + 来求得实部和虚部。 + Alyssa 的表示为: + + real_part极坐标形式 + imag_part极坐标形式 + magnitude极坐标形式 + angle极坐标形式 + make_from_real_imag极坐标形式 + make_from_mag_ang极坐标形式 + make_complex_number2 + complex_number_calculation + square_definition + make_complex_number_example + -3 + +(define (real-part z) + (* (magnitude z) (cos (angle z)))) + +(define (imag-part z) + (* (magnitude z) (sin (angle z)))) + +(define (magnitude z) (car z)) + +(define (angle z) (cdr z)) + +(define (make-from-real-imag x y) + (cons (sqrt (+ (square x) (square y))) + (atan y x))) + +(define (make-from-mag-ang r a) (cons r a)) + + +function real_part(z) { + return magnitude(z) * math_cos(angle(z)); +} +function imag_part(z) { + return magnitude(z) * math_sin(angle(z)); +} +function magnitude(z) { return head(z); } + +function angle(z) { return tail(z); } + +function make_from_real_imag(x, y) { + return pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x)); +} +function make_from_mag_ang(r, a) { return pair(r, a); } + + + + + + + 数据抽象的学科确保相同的实现 + + + add-complex, + + + add_@complex, + + + + + sub-complex, + + + sub_complex, + + + + + mul-complex, + + + mul_complex, + + + 和 + + + div-complex + + + div_complex + + + 都能在 Bens 的表示和 Alyssas 的表示中正常工作。 + + +
diff --git a/xml/cn/chapter2/section4/subsection2.xml b/xml/zh/chapter2/section4/subsection2.xml similarity index 59% rename from xml/cn/chapter2/section4/subsection2.xml rename to xml/zh/chapter2/section4/subsection2.xml index af2d356bf..fa9bf77e5 100644 --- a/xml/cn/chapter2/section4/subsection2.xml +++ b/xml/zh/chapter2/section4/subsection2.xml @@ -8,50 +8,57 @@ 带标签数据 数据带标签 - - 数据抽象的一种方式是将其视为最少承诺原则的应用。 + + + 一种看待数据抽象的方法是将其视为 + 最少承诺原则 最少承诺原则 - 最少承诺原则, 原则 - 最少承诺原则。 在第节中实现复数系统时,我们可以使用 Ben 的直角坐标表示或 Alyssa 的极坐标表示。选择器和构造器形成的抽象屏障使我们能够将具体数据对象表示选择推迟到最后一刻,从而在系统设计中保持最大的灵活性。 - - - 最少承诺原则可以被推向更极端的程度。 - 如果我们愿意,我们可以在设计选择器和构造器之后仍然保持表示的模糊性,并选择使用 Ben 的表示和 Alyssa 的表示。不过,如果将两种表示都包括在一个系统中,我们将需要某种方式来区分极坐标形式的数据和直角坐标形式的数据。否则,例如,如果我们被要求找到对magnitude为$(3,4)$的对的模长,我们将不知道是回答 5(按直角坐标形式解释)还是 3(按极坐标形式解释)。一种简单的方法是包括一个类型标签,即类型标签 + 最少承诺原则。 的一种应用。在第节中实现复数系统时,我们可以使用本杰明的直角坐标表示法或艾丽莎的极坐标表示法。由选择器和构造器构成的抽象屏障使我们能够将数据对象具体表示的选择推迟到最后时刻,从而在系统设计中保留最大的灵活性。 + + + + + 最少承诺原则甚至可以被推向更极端的程度。如果我们愿意,即使在设计了选择器和构造器之后,我们仍然可以保持表示的不确定性,并选择同时使用本杰明的表示法和艾丽莎的表示法。然而,如果在一个系统中同时包含这两种表示方式,我们就需要某种方法来区分极坐标形式的数据与直角坐标形式的数据。否则,例如,如果要求我们求出序对 + magnitude + $(3,4)$的值,我们将不知道该回答 5(将数字解释为直角坐标形式)还是 3(将数字解释为极坐标形式)。实现这种区分的一种直接方法是,在每个复数中包含一个 + 类型标签 + 类型标签作为其一部分。然后,当我们需要操作一个复数时,就可以利用该标签来决定应用哪个选择器。 - 符号 rectangular - 字符串 "rectangular" + symbol 直角坐标 + string "直角坐标" - 或者 + 或 - polar作为 + 极坐标as - "polar"作为 + "极坐标"as - 每个复数的一部分。然后,当我们需要处理一个复数时,我们可以使用标签来决定应用哪个选择器。 - - - 为了操作带标签数据,我们假设我们有 + + + + + 为了操作带标签的数据,我们假设我们拥有 - 过程 + 过程 函数 - type-tag + 类型标签 type_tag - 和 contents 从数据对象中提取标签和实际内容(在复数的情况下为极坐标或直角坐标)。我们还将假设有一个 + 和contents,它从数据对象中提取标签和实际内容(对于复数而言,是极坐标或直角坐标)。我们还假设存在一个 - 过程 + 过程 函数 - attach-tag + 附加标签 attach_tag - ,它接受一个标签和内容并生成一个带标签的数据对象。实现这一点的一种简单方法是使用普通列表结构: - + ,它接受一个标签和内容并生成一个带标签的数据对象。实现这一点的一个直接方法是使用普通的列表结构: + attach_tag type_tag contents @@ -99,11 +106,13 @@ type_tag(my_frequency_1); - + + + - + 使用这些过程, - + 使用 type_tag, @@ -111,30 +120,32 @@ type_tag(my_frequency_1); 我们可以定义谓词 - rectangular? + rectangular? is_rectangular - - polar?, - is_polar + + polar?, + + is_polar, + - 分别识别直角坐标和极坐标数字: - + 它们分别用于识别直角坐标数和极坐标数: + is_rectangular is_polar rectangular_or_polar rectangular_or_polar_example attach_tag - + (define (rectangular? z) (eq? (type-tag z) 'rectangular)) (define (polar? z) (eq? (type-tag z) 'polar)) - + function is_rectangular(z) { return type_tag(z) === "rectangular"; @@ -143,15 +154,22 @@ function is_polar(z) { return type_tag(z) === "polar"; } - - - - 有了类型标签,Ben 和 Alyssa 现在可以修改他们的代码,使他们的两种不同表示能够在同一系统中共存。每当 Ben 构造一个复数时,他将其标记为直角坐标。每当 Alyssa 构造一个复数时,她将其标记为极坐标。此外,Ben 和 Alyssa 必须确保他们的 +
+ + + + 利用类型标签,Ben 和 Alyssa 现在可以修改他们的代码,使得他们两种不同的表示法能够共存在同一个系统中。每当 Ben 构造一个复数时,他就将其标记为 rectangular。每当 Alyssa 构造一个复数时,她就将其标记为 polar。此外,Ben 和 Alyssa 必须确保他们的 - 过程 + 过程 函数 - </SPLITINLINE> - 的名称不冲突。一种方法是让 Ben 附加后缀【40:10†cn.txt】。 rectangular 给他每个表示过程的名称添加后缀,并让 Alyssa 添加【44:4†cn.txt】。 polar 到她们的名字。以下是来自第节的 Ben更新的直角坐标表示【48:4†cn.txt】。 + + 的名称不会发生冲突。一种实现方法是让 Ben 在名称后附加后缀 +rectangular把后缀附加到他每个表示法的名称上 + + 过程 + 函数 + + 并让 Alyssa 附加polar到她的名称。下面是 Bens 修订后的矩形表示法,摘自章节: real_part_rectangular imag_part_rectangular magnitude_rectangular @@ -213,9 +231,9 @@ const bens_co_num = make_from_mag_ang_rectangular( imag_part_rectangular(contents(bens_co_num)); - - 以下是 Alyssa 更新的极坐标表示: - + + 下面是 Alyssa 的修订后的极坐标表示法: + real_part_polar imag_part_polar magnitude_polar @@ -278,46 +296,48 @@ imag_part_polar(contents(alyssas_co_num)); + - 选择器通用选择器 - 通用 过程函数通用选择器 - 每个通用选择器都实现为一个 + 选择器通用 + 通用 过程函数通用选择器 + 每个通用选择器均实现为一个 - 过程 + 过程 函数 - ,该过程检查其参数的标签并调用适当的 + ,它检查其参数的标签,并调用适用于该类型数据处理的 - 过程 + 过程 函数 - ,用于处理该类型的数据。例如,为了获得复数的实部, + 。例如,为了获得复数的实部, - real-part + real-part real_part 检查标签以确定是否使用 Ben 的 - real-part-rectangular + real-part-rectangular real_part_rectangular 或 Alyssa 的 - real-part-polar. - real_part_polar. + real-part-polar. + real_@part_@polar. - 无论哪种情况,我们都使用 【16:8†cn.txt】。 contents 提取裸露的、无标签的数据并根据需要将其发送到直角坐标或极坐标 + 在任一情况下,我们使用 contents 来提取裸数据(无标签的数据),并将其传送到直角坐标或极坐标 - 过程 + 过程 函数 - : - - real_partwith tagged data - imag_partwith tagged data - magnitudewith tagged data - anglewith tagged data + + 进行处理: + + real_part带标签数据 + imag_part带标签数据 + magnitude带标签数据 + angle带标签数据 make_complex_number rectangular_or_polar make_complex_number_rectangular @@ -330,28 +350,28 @@ imag_part_polar(contents(alyssas_co_num)); (real-part-rectangular (contents z))) ((polar? z) (real-part-polar (contents z))) - (else (error "Unknown type -- REAL-PART" z)))) + (else (error "未知类型 -- REAL-PART" z)))) (define (imag-part z) (cond ((rectangular? z) (imag-part-rectangular (contents z))) ((polar? z) (imag-part-polar (contents z))) - (else (error "Unknown type -- IMAG-PART" z)))) + (else (error "未知类型 -- IMAG-PART" z)))) (define (magnitude z) (cond ((rectangular? z) (magnitude-rectangular (contents z))) ((polar? z) (magnitude-polar (contents z))) - (else (error "Unknown type -- MAGNITUDE" z)))) + (else (error "未知类型 -- MAGNITUDE" z)))) (define (angle z) (cond ((rectangular? z) (angle-rectangular (contents z))) ((polar? z) (angle-polar (contents z))) - (else (error "Unknown type -- ANGLE" z)))) + (else (error "未知类型 -- ANGLE" z)))) function real_part(z) { @@ -359,28 +379,28 @@ function real_part(z) { ? real_part_rectangular(contents(z)) : is_polar(z) ? real_part_polar(contents(z)) - : error(z, "unknown type -- real_part"); + : error(z, "未知类型 -- real_part"); } function imag_part(z) { return is_rectangular(z) ? imag_part_rectangular(contents(z)) : is_polar(z) ? imag_part_polar(contents(z)) - : error(z, "unknown type -- imag_part"); + : error(z, "未知类型 -- imag_part"); } function magnitude(z) { return is_rectangular(z) ? magnitude_rectangular(contents(z)) : is_polar(z) ? magnitude_polar(contents(z)) - : error(z, "unknown type -- magnitude"); + : error(z, "未知类型 -- magnitude"); } function angle(z) { return is_rectangular(z) ? angle_rectangular(contents(z)) : is_polar(z) ? angle_polar(contents(z)) - : error(z, "unknown type -- angle"); + : error(z, "未知类型 -- angle"); } @@ -394,60 +414,61 @@ imag_part(alyssas_co_num); - - 为了实现复数算术运算,我们可以使用相同的 + + + + 为了实现复数的算术运算,我们可以使用相同的 - 过程 + 过程 函数 - add-complex, - add_complex, - + add-complex, + add_complex, - sub-complex, - sub_complex, - + sub-complex, + sub_complex, - mul-complex, - mul_complex, - + mul-complex, + mul_complex, - div-complex + div-complex div_complex - 来自第节, - 因为它们调用的选择器是通用的,因此可以与任何表示一起使用。例如, + 来自 节, + 因为它们所调用的选择器是通用的,所以可以适用于任一表示方式。例如, - 过程 + 过程 函数 - add-complex + add-complex add_complex 仍然是 - - + + (define (add-complex z1 z2) (make-from-real-imag (+ (real-part z1) (real-part z2)) (+ (imag-part z1) (imag-part z2)))) - + function add_complex(z1, z2) { return make_from_real_imag(real_part(z1) + real_part(z2), imag_part(z1) + imag_part(z2)); } - - - - 最后,我们必须选择是使用 Ben 的表示还是 Alyssa 的表示来构造复数。一个合理的选择是,在我们具有实部和虚部时构造直角坐标数,而在我们具有模长和角度时构造极坐标数: - + + + + + + 最后,我们必须选择使用 Ben 的表示法还是 Alyssa 的表示法来构造复数。一个合理的选择是:在有实部和虚部时构造矩形复数,而在有模长和角度时构造极坐标复数: + make_from_real_imag make_from_mag_ang make_complex_number_generic @@ -456,13 +477,13 @@ function add_complex(z1, z2) { make_complex_number_polar make_complex_number_generic_example 1.932653061713073 - + (define (make-from-real-imag x y) (make-from-real-imag-rectangular x y)) (define (make-from-mag-ang r a) (make-from-mag-ang-polar r a)) - + function make_from_real_imag(x, y) { return make_from_real_imag_rectangular(x, y); @@ -471,8 +492,8 @@ function make_from_mag_ang(r, a) { return make_from_mag_ang_polar(r, a); } - - + + make_complex_number_generic_example const alyssas_co_num = make_from_mag_ang( @@ -480,57 +501,63 @@ const alyssas_co_num = make_from_mag_ang( imag_part(alyssas_co_num); - - - + + + + + - +
通用复数算术系统的结构。 - +
- +
结构 - 复数算术系统结构 + 复数算术系统的结构 的通用复数算术系统。 - +
- $\!$结果复数系统的结构如 + $\!$最终得到的复数系统具有如下所示的结构, - - 图所示。 - + + figure。 + - 图所示。 + figure - 该系统被分解为三个相对独立的部分:复数算术运算,Alyssa 的极坐标实现,以及 Ben 的直角坐标实现。 極座標和直角坐標實現可以由 Ben 和 Alyssa 分別完成,這兩者都可以用作第三個程序員實現復數算術的 + 该系统被分解成三个相对独立的部分:复数算术运算、Alyssa的极坐标实现,以及Ben的矩形实现。极坐标和矩形实现可以由Ben和Alyssa各自独立编写,并且这两种实现都可以作为底层表示,由第三个程序员利用抽象构造器/选择器接口来实现复数算术 - 过程 + 过程 函数 - ,以抽象构造器/选择器接口的方式。 -
- - 因为每个数据对象都有其类型标签,所以选择器以通用方式操作 - 选择器通用选择器 - 通用 过程函数通用选择器 - 。即,每个选择器的定义行为取决于应用的数据类型。注意将不同表示进行接口的通用机制:在给定的表示实现中(比如 Alyssa 的极坐标包),复数是一个无类型的对(幅度、角度)。当通用选择器操作一个极坐标类型的数字时,它会去掉标签并将内容传给 Alyssa 的代码。相反,当 Alyssa 构造一个用于通用的数字时,她将其标记为一种类型,以便能被高层的 + 。 + + + + + 由于每个数据对象都带有其类型标签,因此选择器以 + selector通用的 + 通用过程函数通用选择器 + 方式操作数据。也就是说,每个选择器的定义都有一种行为,这种行为取决于它所应用数据的特定类型。注意连接各个独立表示之间的一般接口机制: + 在某个具体的表示实现中(例如,Alyssa的极坐标包),复数是一个无类型的对(模长,角度)。当一个通用选择器对一组极坐标类型进行操作时,它会剥离标签并将内容传递给Alyssa的代码。反过来,当Alyssa构造一个供通用使用的数字时,她会为其加上类型标签,以便它能被上层 - 过程识别。 - 函数。 + 过程 + 函数 - 这种在数据对象从一个层次到另一个层次传递时去除和附加标签的纪律可以是一个重要的组织策略,如我们将在第节中看到的那样。 - 复数表示为带标签数据 - 带标签数据 - 数据带标签 - + 正确识别。正如我们将在章节中看到的那样,在数据对象自上而下传递过程中剥离和附加标签的这种做法可以成为一种重要的组织策略。 + 复数以带标签的数据表示 + 带标签的数据 + 数据带标签的 +
+
diff --git a/xml/cn/chapter2/section4/subsection3.xml b/xml/zh/chapter2/section4/subsection3.xml similarity index 54% rename from xml/cn/chapter2/section4/subsection3.xml rename to xml/zh/chapter2/section4/subsection3.xml index 256992220..73772e49f 100644 --- a/xml/cn/chapter2/section4/subsection3.xml +++ b/xml/zh/chapter2/section4/subsection3.xml @@ -1,226 +1,259 @@ - - 数据导向编程和可加性 - + 数据导向编程 可加性 - 检查数据类型并调用适当 - 过程 + 检查数据的类型并调用适当的 + + 过程 函数 - 的一般策略被称为模块性通过类型分派分派按类型类型基于类型分派按类型分派。这是一种在系统设计中实现模块性的强大策略。然而,按照节实现分派有两个显著的弱点。一个弱点是通用接口 - 过程 + + 称为 + 模块性通过类型分派 + 在类型上分派在类型上 + 类型分派于 + 在类型上分派。 这是在系统设计中获得模块性的一个强大策略。 另一方面,如章节中所述那样实现分派存在两个显著的缺点。 其中一个缺点是通用接口 + + 过程 函数 - - (real-part, + + + (real-part, (real_part, - - imag-part, + + + imag-part, imag_part, - magnitude和angle)必须知道所有不同的表示。例如,假设我们想把一种新的复数表示集成到我们的复数系统中。我们需要用一种类型标识这种新表示,然后在每个通用接口 - 过程 + + magnitude,以及 + angle) 必须了解所有不同的表示方法。 例如,假设我们想将一种新的复数表示方法引入到我们的复数系统中。 我们需要用一个类型来标识这种新表示方式,然后在每个通用接口 + + 过程 函数 - 中添加一个子句,以检查新类型并应用适当的选择器以适应这种表示。 + + 中添加一条子句,以检查这种新类型并应用该表示方法的适当选择器。 - 该技术的另一个弱点是,尽管各个表示可以单独设计,但我们必须确保整个系统中没有两个 - 过程 + 该技术的另一个弱点在于,尽管各个表示法可以独立设计,我们必须保证整个系统中没有两个 + + 过程 函数 - 具有相同的名称。这就是Ben和Alyssa为什么必须更改他们在节的原始 - 过程 + + 具有相同的名称。这就是为什么 Ben 和 Alyssa 必须更改他们原来在章节中使用的 + + 过程 函数 - 名称的原因。 + + 的名称。 - 这两种弱点的根本问题在于,实施通用接口的技术不是可加性的。实现通用选择器的人员 + 这两种弱点背后的问题在于,实现泛型接口的技术并不是 累加的。实现泛型选择器 - 过程 + 过程 函数 - 必须在每次安装新表示时修改这些 + 的人员必须在每次安装一种新的表示法时修改那些 - 过程 + 过程 函数 - ,而接口个别表示的人员必须修改他们的代码以避免名称冲突。在每一种情况下,必须对代码进行的更改是直接的,但仍然要进行这些更改,这成为了不便和错误的来源。对于复数系统而言,这不是什么大问题,但假设复数的不同表示不止两种而是成百上千种。而且假设有许多通用选择器需要在抽象数据接口中维护。事实上,假设没有一个程序员知道所有的接口 + ,而与各个表示法交互的人员也必须修改他们的代码以避免名称冲突。在每种情况下,必须对代码做出的更改虽然是简单明了的,但这些更改仍然是必需的,这会导致不便和错误。对于现有的复数系统来说,这问题不大,但假设针对复数的表示法不止两种而是上百种,再加上在抽象数据接口中需要维护许多泛型选择器,事实上,假设没有哪个程序员能掌握所有接口 - 过程 + 过程 函数 - 或所有的表示。在诸如大型数据库管理系统的程序中,这个问题是现实的,必须加以解决。 + 或者所有的表示法,这个问题便切实存在,并且在像大规模数据库管理系统这样的程序中必须加以解决。 - 我们需要的是一种进一步模块化系统设计的方法。这由被称为数据导向编程的编程技术提供。要理解数据导向编程是如何工作的,应首先注意到,每当我们处理一组通用于不同类型的泛型操作时,我们实际上是在处理一个二维表格,其中一个轴是可能的操作,另一个轴是可能的类型。表中的条目是为每种类型的参数实现每个操作的 + 我们需要一种能够进一步模块化系统设计的方法。这正是由一种称为 数据导向编程 的编程技术所提供的。要理解数据导向编程的工作原理,可以从这样一个观察开始:每当我们处理一组适用于一系列不同类型的通用操作时,实际上我们是在处理一个二维表——其一轴上列举了可能的操作,另一轴上列举了可能的类型。该表中的各项就是为每种传入参数类型实现各个操作的 - 过程 + 程序 函数 - 。在前一节开发的复数系统中,操作名称、数据类型和实际 + 。在前一节中所开发的复数系统中,操作名称、数据类型与实际 - 过程 + 程序 函数 - - 之间的对应关系分散在通用接口 + </SPLITINLINE> + 之间的对应关系分散在通用接口 - 过程 + 程序 函数 - 的各种条件语句中。但相同的信息本可以在一个表中组织起来,如 + 的各个条件子句中。但同样的信息也可以组织在一个表中,如下所示: - - 图. - + + figure. + - 图. + figure. - 所示。 - - + + - 数据导向编程是一种直接与这样的用于数据导向编程表格协同工作的编程技术。之前,我们通过一组 - 过程 - 函数 - 实现了将复数算法代码与两个表示包接口的机制,这些函数各自执行显式的类型分派。这里我们将接口实现为一个单一的 - 过程 - 函数 - ,它在表中查找操作名称和参数类型的组合以找到要应用的正确 - 过程 - 函数 - ,然后应用于该参数的内容。如果这样做,那么为系统添加新的表示包时,我们无需更改任何现有的 - 过程; - 函数; - 我们只需在表中添加新的条目。 - - - - + 数据导向编程是一种设计程序直接使用这样的 + 用于数据导向编程的表 + 的技术。之前,我们实现了将复数运算代码与这两个表示包接口的机制,该机制是一组在类型上执行显式分派的 + + 过程 + 函数 + + 。这里我们将把接口实现为一个单一的 + + 过程 + 函数 + + ,它在表中查找操作名称和参数类型的组合以找到正确的 + + 过程 + 函数 + + 进行应用,然后将其应用于参数的内容。如果我们这样做,那么要向系统中添加一个新的表示包,我们不必更改任何现有的 + + 过程; + 函数; + + ,只需在表中添加新条目。 + + + +
- 复数系统的操作表。 - + 复数系统的运算表。 +
- +
- 复数系统的操作表。 - + 复数系统的运算表。 +
- To implement this plan, assume that we have two + 为了实现这一计划,假设我们有两个 - 过程, + 过程, 函数, - putget ,用于操作操作与类型表操作与类型表:
    -
  • - 放入 + putget,用于操作 + operation-and-type table + 操作与类型表: +
      +
    • + put - - (放入 - $\langle \textit{操作} \rangle\ \langle \textit{类型} - \rangle \ \langle \textit{项目} \rangle$ - - ) - + + (put + $\langle \textit{op} \rangle\ \langle \textit{type} + \rangle \ \langle \textit{item} \rangle$ + + ) + - put(<!-- - -->op<!-- - -->, <!-- - -->type<!-- - -->, <!-- - -->item<!-- - -->) + put(<!-- + -->op<!-- + -->, <!-- + -->type<!-- + -->, <!-- + -->item<!-- + -->)

      - 在表中安装 + 将 - - $\langle \textit{项目} \rangle$ - + + $\langle \textit{item} \rangle$ + item - ,由 + 安装到表中,以 - - $\langle \textit{操作} \rangle$ 和 - $\langle \textit{类型} \rangle$索引。 - + + $\langle \textit{op} \rangle$ 和 + $\langle \textit{type} \rangle$ + - op 和 - type索引。 + op 和 type. + 作为索引。
    • -
    • - 获取 +
    • + get - - (获取 - $\langle \textit{操作} \rangle\ \langle - \textit{类型}$ - ) - + + (get + $\langle \textit{op} \rangle\ \langle + \textit{type}$ + ) + - get(<!-- - -->op<!-- - -->, <!-- - -->type<!-- - -->) + get(<!-- + -->op<!-- + -->, <!-- + -->type<!-- + -->)

      - 在表中查找 + 查找 - - $\langle \textit{操作} \rangle$、 - $\langle \textit{类型} \rangle$ - + + $\langle \textit{op} \rangle$, + $\langle \textit{type} \rangle$ + - op、 + op, type - 项并返回找到的项目。如果未找到任何项目, - 获取返回 + 在表中的条目,并返回找到的项。 + 如果未找到任何项, + get 返回 - 假。 + false. - 一个使用名称undefined(预声明的名称) - undefined引用并由基本谓词 - is_undefined(基本函数) + 一个唯一的原始值,该值通过预声明名称 + undefined (预声明名称) + undefined 来引用,并由原始谓词 + is_undefined (原始函数) is_undefined - is_undefined识别的独特基本值。 - 在任何JavaScript实现中,名称undefined - 是预声明的,除了用于引用该基本值外,不应用于其他目的。 + is_undefined 识别。 + 名称 undefined 在任何 JavaScript 实现中都是预声明的,除指代该原始值外不应用于其他用途。
    • -
    - 现在,我们可以假设 -put 和 - get 包含在我们的语言中。在chapter(section)中,我们将看到如何实现这些以及其他用于操纵表的操作。 - - - 以下是数据导向编程在复数系统中的应用方式。开发直角坐标表示的Ben按照最初的方法实现他的代码。他定义了一组 - 过程, +
+ 目前,我们可以假设 put 和 + get 已包含在我们的语言中。在章(节)中,我们将看到如何实现这些以及其他用于操作表的操作. +
+ + 下面介绍如何在复数系统中使用数据导向编程。Ben 开发了矩形表示法,并且像最初那样实现了他的代码。他定义了一系列 + + 过程, 函数 - 或一个直角坐标表示直角坐标,并通过在表中添加条目来接口到系统的其他部分,这些条目告诉系统如何在直角坐标数上操作。这是通过调用以下 - 过程: + + 或一个 + + 矩形表示法 + 矩形 + ,并通过向表中添加条目来向系统其他部分接口,告诉系统如何对矩形数进行操作。这是通过调用下面的 + + 过程: 函数: - 实现的。 - + + operation_table_from_chapter_3 -// operation_table, put and get -// from chapter 3 (section 3.3.3) +// 来自第三章(第 3.3.3 节)的 operation_table、put 和 get @@ -241,7 +274,7 @@ install_rectangular_package(); install_rectangular_package_usage (define (install-rectangular-package) - ;; internal procedures + ;; 内部过程 (define (real-part z) (car z)) (define (imag-part z) (cdr z)) (define (make-from-real-imag x y) (cons x y)) @@ -253,7 +286,7 @@ install_rectangular_package(); (define (make-from-mag-ang r a) (cons (* r (cos a)) (* r (sin a)))) - ;; interface to the rest of the system + ;; 与系统其他部分的接口 (define (tag x) (attach-tag 'rectangular x)) (put 'real-part '(rectangular) real-part) (put 'imag-part '(rectangular) imag-part) @@ -267,7 +300,7 @@ install_rectangular_package(); function install_rectangular_package() { - // internal functions + // 内部函数 function real_part(z) { return head(z); } function imag_part(z) { return tail(z); } function make_from_real_imag(x, y) { return pair(x, y); } @@ -281,7 +314,7 @@ function install_rectangular_package() { return pair(r * math_cos(a), r * math_sin(a)); } - // interface to the rest of the system + // 与系统其他部分的接口 function tag(x) { return attach_tag("rectangular", x); } put("real_part", list("rectangular"), real_part); put("imag_part", list("rectangular"), imag_part); @@ -295,86 +328,86 @@ function install_rectangular_package() { } - - - 注意,这里的内部 + + + 请注意,此处的内部 - 过程 + 过程 函数 - 是Ben在节中独立工作时编写的相同 + 与 Ben 在独立工作时所编写的 - 过程 + 过程 函数 - 。为了与系统其他部分接口,不需要进行任何更改。此外,由于这些 + + (参见 节)是相同的。为了将它们与系统的其他部分接口,无需做任何修改。此外,由于这些 - 过程定义 + 过程定义 函数声明 - 是安装时的内部 + 是内部安装的 - 过程, + 过程, 函数, - Ben不需要担心与直角坐标包外的其他 + ,因此 Ben 无需担心与矩形模块之外的其他 - 过程 + 过程 函数 - 的名称冲突。为了与系统其他部分接口,Ben将他的 + 的名称冲突。为了将这些接口化供系统其他部分使用,Ben 将他的 - 实部 + real-part real_part - 过程 + 过程 函数 安装在操作名称 - 实部 + real-part real_part - 和类型 + 下,并安装在类型 - - (直角坐标), - list("rectangular"), + + (rectangular), + list("rectangular"), - 下,类似地用于其他选择器。 - 我们使用的是 + 下,其余选择器亦同。我们使用列表 - (直角坐标) + (rectangular) list("rectangular") - 列表,而不是 + 而不是 - 符号直角坐标 - 字符串"rectangular" + 符号 rectangular + 字符串 "rectangular" - ,以便可能存在具有多个不同类型参数的操作。 接口还定义了外部系统使用的构造器。构造器安装时的类型不必是列表,因为构造器总是用于创建特定类型的对象。 这些与Ben内部定义的构造器相同,只是在它们上面附加了标签。 - + ,以便支持具有多个参数且各参数类型不同的操作。
该接口还定义了供外部系统使用的构造函数。构造函数安装时所依附的类型不必为列表,因为构造函数始终用于生成特定类型的对象。 这些构造函数与 Ben 内部定义的构造函数完全相同,只不过它们附加了标签。 + - - Alyssas + + Alyssa极坐标表示 - 极坐标 - 极坐标包是类似的: - + 极坐标 + 极坐标包类似于: + install_polar_package install_polar_package operation_table_from_chapter_3 operation_table - attach_tag + 附加标签函数 square_definition install_polar_package_usage 'done' (define (install-polar-package) - ;; internal procedures + ;; 内部过程 (define (magnitude z) (car z)) (define (angle z) (cdr z)) (define (make-from-mag-ang r a) (cons r a)) @@ -385,8 +418,8 @@ function install_rectangular_package() { (define (make-from-real-imag x y) (cons (sqrt (+ (square x) (square y))) (atan y x))) - - ;; interface to the rest of the system + + ;; 系统接口 (define (tag x) (attach-tag 'polar x)) (put 'real-part '(polar) real-part) (put 'imag-part '(polar) imag-part) @@ -400,7 +433,7 @@ function install_rectangular_package() { function install_polar_package() { - // internal functions + // 内部函数 function magnitude(z) { return head(z); } function angle(z) { return tail(z); } function make_from_mag_ang(r, a) { return pair(r, a); } @@ -414,8 +447,8 @@ function install_polar_package() { return pair(math_sqrt(square(x) + square(y)), math_atan2(y, x)); } - - // interface to the rest of the system + + // 系统接口 function tag(x) { return attach_tag("polar", x); } put("real_part", list("polar"), real_part); put("imag_part", list("polar"), imag_part); @@ -436,95 +469,106 @@ function install_polar_package() { install_polar_package(); - + - 即使Ben和Alyssa仍然使用各自的原始 + 尽管 Ben 和 Alyssa 都仍然使用他们原来定义的 - 过程 + 过程 函数 - 并定义为相同的名称(例如, + ,且名称彼此相同(例如, - 实部), + real-part), real_part), - 这些声明现在属于不同的内部 + 这些声明现在分别在不同的 - 过程 + 过程 函数 - (参见节),因此没有名称冲突。 + 的内部(参见部分),因此不会产生命名冲突. - - 复数算术选择器通过一个通用操作 - 过程 + + 复杂算术选择器通过一个通用的操作 + + 过程 函数 - 访问表,该通用操作被称为 - apply-generic, - apply_generic, - - ,它将通用操作应用于一些参数。 - apply-generic + + 访问该表,该过程被称为 + + apply-generic, + apply_generic, + + 它将一个通用操作应用于一些参数。 + + Apply-generic 函数 apply_generic - 会根据操作名称和参数类型在表中查找,并在存在时应用结果 - 过程 + + 在表中查找以操作名称及参数类型为依据的条目,并在存在时应用得到的 + + 过程 函数 - : - - - - apply-generic使用了在 - 点尾注释过程用于程序参数 - 练习中描述的点尾注释,因为不同的通用操作可能具有不同数量的参数。在 + + : + + + Apply-generic 使用练习中描述的 + dotted-tail notation过程对于过程参数 带省略尾参数记法, + 因为不同的通用操作可能需要不同数量的参数。 + 在 - apply-generic, + apply-generic, apply_generic, - op的值是 + 中,op 的值为传递给 - apply-generic + apply-generic apply_generic - 的第一个参数,而args的值是剩余参数的列表。 + 的第一个参数,而 args 的值为其余参数构成的列表。

- +
- - apply-generic还使用了 - 原语过程apply,它接受两个参数,一个过程和一个列表。apply利用列表中的元素作为参数来应用该过程。 - + + Apply-generic 还使用原始过程 + apply,该过程接受两个参数,即一个过程和一个列表。 + Apply + 利用列表中的元素作为参数来调用该过程。 + - 函数apply_generic使用了 - 段apply_in_underlying_javascript - apply_in_underlying_javascript中给出的函数, - 它接受两个参数,一个函数和一个列表,并用列表中的元素作为参数来应用该函数。参见部分 + 函数 + apply_generic + 使用在节中给出的函数 + apply_in_underlying_javascript + apply_in_underlying_javascript (脚注), (脚注2), + 该函数接受两个参数,即一个函数和一个列表,并利用列表中的元素作为参数来调用该函数。 - 例如, - + 例如, + (apply + (list 1 2 3 4)) apply_in_underlying_javascript(sum_of_squares, list(1, 3)) - - 返回10。
- + + 返回 10。 + apply_definition // In Source, most functions have a fixed number of arguments. @@ -564,12 +608,12 @@ function apply_generic(op, args) { 使用 - apply-generic, + apply-generic, apply_generic, - 我们可以定义我们的通用选择器如下: - + 我们可以如下定义我们的通用选择器: + real_partdata-directed imag_partdata-directed magnitudedata-directed @@ -612,24 +656,25 @@ const result = imag_part(result); - 注意,如果向系统中添加新的表示,这些都不会发生任何变化。 -
- - 我们还可以从表中提取构造器,以供包外的程序使用,这些程序通过实部和虚部以及模长和角度构造复数。如在节中,我们在有实部和虚部时构造直角坐标数,而在有模长和角度时构造极坐标数: - - make_complex_from_real_imag - make_complex_from_mag_ang + 请注意,即使向系统中添加新的表示,这些也完全不会发生任何变化。 + + + + 我们还可以从表格中提取构造函数,这些构造函数供包外的程序在由实部和虚部以及由模长和角度构成复数时使用。正如章节所示,每当我们拥有实部和虚部时,就构造矩形数;而每当我们拥有模长和角度时,就构造极坐标数: + + make_from_real_imag + make_from_mag_ang generic_constructors generic_selectors generic_selectors_example 9 - + (define (make-from-real-imag x y) ((get 'make-from-real-imag 'rectangular) x y)) (define (make-from-mag-ang r a) ((get 'make-from-mag-ang 'polar) r a)) - + function make_from_real_imag(x, y) { return get("make_from_real_imag", "rectangular")(x, y); @@ -638,12 +683,16 @@ function make_from_mag_ang(r, a) { return get("make_from_mag_ang", "polar")(r, a); } - - + +
- - 节描述了一个执行符号微分微分符号符号微分的程序: - + + + 节描述了一个执行 + 符号求导 + 求导符号 + 符号求导的程序: + deriv_2_4 is_variable is_same_variable @@ -714,16 +763,19 @@ list("+", list("*", list("*", x, y), list("+& list("+", x, 4))) - 我们可以将此程序视为对要微分的表达式类型进行分派。在这种情况下,数据的类型标签是代数运算符符号,例如 + 我们可以将该程序视为对待求导表达式的类型进行分派。在这种情况下,类型标签所表示的数据为代数运算符号 - (如+) - (如"+") + (例如 + + (例如 "+" - ,执行的操作是 - deriv . 我们可以通过重写基本导数 - 过程 + 而被执行的操作是 +deriv . 我们可以将该程序转换为数据导向风格, + 通过将基本求导 + + 过程 函数 - 为数据导向风格来转换这个程序 + + 重写为 deriv (symbolic)data-directed deriv_generic @@ -767,85 +819,85 @@ deriv("x", "x"); // 1 -
    -
  1. - 解释上面所做的事情。为什么我们不能将谓词 - - number? - is_number - - 和 - - variable? - is_variable - - - 并入数据导向分派中? -
  2. -
  3. - 编写求和及乘积导数的 - - 过程 - 函数 - - ,以及将它们安装到上述程序使用的表中的辅助代码。 -
  4. -
  5. - 选择任何你喜欢的额外微分规则,例如指数规则(练习),并将它安装到这个数据导向系统中。 -
  6. -
  7. - 在这个简单的代数操纵器中,表达式的类型是将它结合在一起的代数运算符。然而,假设我们以相反的方式索引 - - 过程 - 函数 - - ,这样在deriv中的分派行看起来像 - - + +
      +
    1. + 解释上面所做的工作。为什么我们不能将谓词 + + number? + is_number + + 和 + + variable? + is_variable + + 合并到数据导向调度中? +
    2. +
    3. + 写出求和与求积的导数的 + + 过程 + 函数 + + ,以及将它们安装到上述程序所使用的表中所需的辅助代码。 +
    4. +
    5. + 选择一个你喜欢的额外求导规则,例如关于指数的规则 + (),并将其安装到该数据导向系统中。 +
    6. +
    7. + 在这个简单的代数操纵器中,一个表达式的类型就是将其结合在一起的代数运算符。假设我们以相反的方式对 + + 过程 + 函数 + + 进行索引,以致在 deriv 中的调度行看起来像 + + ((get (operator exp) 'deriv) (operands exp) var) - + get(operator(exp), "deriv")(operands(exp), variable); - - 对导数系统有哪些相应的更改是必需的? -
    8. -
    - + + 那么,对求导系统需要做哪些相应的修改? +
  8. +
+
    -
  1. - 解释上面所做的事情。为什么我们不能将谓词 + +
  2. + 说明上面所做的工作。为什么我们不能将谓词 - number? - is_number - + number? + is_number - same-variable? - is_same_variable - - 并入数据导向分派中? -

    - 运算符符号非常方便地作为操作符表中的类型键。对于数字和变量,尽管我们可以为这些类型的表达式引入名称,如果我们改变表达式作为列表的表示方式,会没有如此明显的键。 -
  3. -
  4. - 编写求和及乘积导数的 - - 过程 + same-variable? + is_same_variable + + 同化到数据导向调度中? 操作符符号作为 operator table 中的 “type” 键非常有用。对于数字和变量,并没有那么明显的键,尽管如果我们改变表达式作为列表表示的方式,我们也可以为这些类型的表达式引入名称。 +

    +
  5. +
  6. + 编写求和与求积的导数的 + + 过程 函数 - 以及将它们安装到上述程序使用的表中的辅助代码。 -

    - + ,以及将它们安装到上述程序所使用的表中所需的辅助代码。
    +

    + deriv_generic_sum_product deriv_generic deriv_generic_data operation_table_from_chapter_3 operation_table deriv_xyx3_generic_example - [ '*', [ 'x', [ 'y', null ] ] ] + [ ''*', [ ''x', [ ''y', null ] ] ] (define (deriv-sum exp var) (make-sum (deriv (addend exp) var) @@ -858,10 +910,10 @@ get(operator(exp), "deriv")(operands(exp), variable); (multiplicand exp)))) (define (install-deriv) - (put 'deriv '+ deriv-sum) - (put 'deriv '* deriv-product) - 'done) - + (put ''deriv '+ deriv-sum) + (put ''deriv '* deriv-product) + ''done) + function deriv_sum(operands, variable) { return make_sum(deriv(addend(operands), variable), @@ -883,8 +935,8 @@ function install_deriv() { } install_deriv(); - - + + deriv_generic_data function make_sum(a1, a2) { @@ -911,42 +963,42 @@ function multiplicand(operands) { return head(tail(operands)); } - - + + deriv_xyx3_generic_example -(deriv '(* (* x y) (+ x 3)) 'x) +(deriv '*(* x y) (+ x 3)) (+ (* (* x y) (+ 1 0)) -(* (+ (* x 0) (* 1 y)) -(+ x 3))) + (* (+ (* x 0) (* 1 y)) + (+ x 3))) deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"); // [ "+", -// [["*", [["*", ["x", ["y", null]]], -// [["+", [1, [0, null]]], null]]], -// [["*", +// [["*", [["*", ["x", ["y", null]]], [["+", [1, [0, null]]], null]]], +// [["*", // [["+", -// [["*", ["x", [0, null]]], -// [["*", [1, ["y", null]]], null]]], +// [["*", ["x", [0, null]]], [["*", [1, ["y", null]]], null]]], // [["+", ["x", [3, null]]], null] ] ], // null ]]] head(tail(head(tail(deriv(list("*", list("*", "x", "y"), list("+", "x", 3)), "x"))))); - -
  7. -
  8. - - 选择任何你喜欢的额外微分规则,例如指数的规则(练习),并将其安装到这个数据导向系统中。 - -

    - - deriv_expo_data - + +
  9. + +
  10. + + 选择任意你喜欢的额外求导规则,例如指数运算的规则 + (exercise),并将其安装到这个数据导向系统中。 + +

    + + deriv_expo_data + function make_exponentiation(base, exp) { return list("**", base, exp); } @@ -956,29 +1008,29 @@ function base(operands) { function exponent(operands) { return head(tail(operands)); } - - - - example_deriv_expo_generic - + + + + example_deriv_expo_generic + deriv(list("**", "x", 4), "x"); - - + + head(tail(head(tail(head(tail(tail(deriv(list("**", "x", 4), "x")))))))); - - - - - - deriv_expo_put_it_all_together - deriv_generic - deriv_generic_data - deriv_expo_data - operation_table_from_chapter_3 - operation_table - example_deriv_expo_generic - 'x' - + + + + + + deriv_expo_put_it_all_together + deriv_generic + deriv_generic_data + deriv_expo_data + operation_table_from_chapter_3 + operation_table + example_deriv_expo_generic + 'x' + (define (deriv-exponentiation expr var) (let ((base (base expr)) (exponent (exponent expr))) @@ -989,8 +1041,8 @@ head(tail(head(tail(head(tail(tail(deriv(list("**", "x", 4), (define (install-exponentiation-extension) (put 'deriv '** deriv-exponentiation) 'done) - - + + function deriv_exponentiation(operands, variable) { const bas = base(operands); const exp = exponent(operands); @@ -1003,134 +1055,143 @@ function install_exponentiation_extension() { return "done"; } install_exponentiation_extension(); - - - -
  11. -
  12. - - 在这个简单的代数操作器中,表达式的类型是将其结合在一起的代数运算符。然而,假设我们以相反的方式索引 - - 过程 - 函数 - - ,这样deriv中的分派行看起来像 - - + + +
  13. +
  14. + + 在这个简单的代数操作器中,一个表达式的类型就是将其组合在一起的代数运算符。然而,假设我们以相反的方式为 + + procedures + functions + + 建立索引,使得在 + deriv + 中的派遣行看起来如下: + + ((get (operator exp) 'deriv) (operands exp) var) - - + + get(operator(exp), "deriv")(operands(exp), variable); - - - 对导数系统有哪些相应的更改是必需的? - -

    - 我们需要更改微分库安装过程中的参数顺序: - - + + + 那么,对求导系统需要做出哪些相应的更改? +
    +

    + 我们需要改变微分库安装过程中的参数顺序: + + (put '+ 'deriv deriv-sum ) (put '* 'deriv deriv-product) (put '** 'deriv deriv-exponentiation) - - + + put("+", "deriv", deriv_sum); put("*", "deriv", deriv_product); put("**", "deriv", deriv_exponentiation); - - -
  15. -
+ +
+ +
- Insatiable数据库Insatiable Enterprises 公司人事 Enterprises, Inc., 是一个高度分散的集团公司,由位于世界各地的大量独立部门组成。该公司的计算机设施刚刚通过一个巧妙的网络接口方案互连,使得整个网络对任何用户而言看似为一台单一计算机。Insatiable 的总裁在首次尝试利用网络从各部门文件中提取管理信息时,却发现虽然所有部门文件都被实现为 - + 贪得无厌 + 数据库贪得无厌企业人员 + 贪得无厌企业公司是一家高度分散的综合企业集团,由遍布全球的大量独立部门组成。该公司的计算机设施刚刚通过一种巧妙的网络接口方案互联,该方案使整个网络对任何用户而言看起来像是一台单独的计算机。 + 贪得无厌的总裁在首次尝试利用网络从各部门文件中提取管理信息时感到沮丧,她惊讶地发现,尽管所有部门文件都已实现为数据结构, + + Scheme, - + JavaScript, - 数据结构,但每个部门使用的具体数据结构却不尽相同。各部门经理紧急召开会议,以寻找一种既能满足总部需求又能保持各部门现有自治性的策略来整合文件。 + + 但所使用的具体数据结构在各部门之间各不相同。各部门经理紧急召开会议,商讨一种既能满足总部需求又能保持各部门现有自主性的文件整合策略。

- 显示如何用数据库数据导向编程数据导向编程实现这种策略。作为示例,假设每个部门的人事记录包含一个文件,里面有以员工姓名为关键字的一组记录。集合的结构因部门而异。此外,每个员工的记录本身也是一个集合(在各部门间的结构不同),其中包含的信息以诸如标识符为关键字。 - address 和 - salary 。尤其是: -
    -
  1. - 为总部实现一个 - - get-record - get_record - - - 过程 - 函数 - - ,该过程可以从指定的人事文件中检索特定员工的记录。 - - 过程 - 函数 - - 应适用于任何部门的文件。解释个别部门的文件应该如何结构化。特别是,必须提供什么类型的信息? -
  2. -
  3. - 为总部实现一个 - - get-salary - get_salary - - - 过程 - 函数 - - ,该过程可以从任何部门的人事文件中返回特定员工记录的薪资信息。为了使该操作正常工作,记录应该如何结构化? -
  4. -
  5. - 为总部实现一个 - - find-employee-record - find_employee_record - - - - 过程。 - 函数。 - - 该过程应搜索所有部门的文件以找到给定员工的记录并返回该记录。假设这个 - - 过程 - 函数 - - 的参数是员工的姓名和所有部门文件的列表。 -
  6. -
  7. - 当 Insatiable 收购一家新公司时,为了将新的人员信息纳入中央系统,必须进行哪些更改? -
  8. -
- + 展示如何通过 + 数据库数据导向编程和 + 数据导向编程来实现这种策略。 + 举例来说,假设每个部门的人事记录由一个文件组成,该文件包含一组以员工姓名为键的记录。该集合的结构在各部门之间各有不同。此外,每个员工的记录本身也是一个集合(各部门的结构各不相同),其中包含以标识符(例如)为键的信息 +address 和 + salary +
    +
  1. + 为总部实现一个 + + get-record + get_record + + + procedure + function + + ,该程序用于从指定的人事文件中检索指定员工的记录。此 + + procedure + function + + 应适用于任何部门的文件。请解释各个部门的文件应如何构造,特别是必须提供何种类型的信息? +
  2. +
  3. + 为总部实现一个 + + get-salary + get_salary + + + procedure + function + + ,该程序用于从任何部门的人事文件中返回给定员工记录的薪资信息。该记录应如何构造以便使此操作正常工作? +
  4. +
  5. + 为总部实现一个 + + find-employee-record + find_employee_record + + + procedure. + function. + + ,该程序应搜索所有部门的文件以查找指定员工的记录并返回该记录。假设此 + + procedure + function + + 接受的参数为员工姓名和所有部门文件的列表。 +
  6. +
  7. + 当 Insatiable 收购一家新公司时,为将新的人事信息纳入中央系统,必须进行哪些更改? +
  8. +
+
    -
  1. - 为总部实现一个 + +
  2. + 为总部实现一个 - get-record - get_record - + get-record + get_record - 过程 - 函数 + procedure + function - ,该过程可以从指定的人事文件中检索特定员工的记录。 - - 过程 - 函数 + ,该函数用于从指定的人事文件中检索指定员工的记录。该 + + procedure + function - 应适用于任何部门的文件。解释个别部门的文件应该如何结构化。特别是,必须提供什么类型的信息? + 应适用于任何部门的文件。请说明各个部门的文件应如何构造,特别是必须提供何种类型的信息?

    - 我们正在使用一个在段中标记的函数,为每个部门的文件标记一个唯一标识符。我们假设每个部门都提供了一个get_record函数的实现,并将其安装在公司范围的操作表中。 - + 我们使用位于 section 的标记函数,为每个部门的文件标记一个唯一的部门标识符。我们假设每个部门都提供了 + get_record 函数的实现,并将其安装在全公司的操作表中。 + function make_insatiable_file(division, file) { return pair(division, file); @@ -1147,29 +1208,29 @@ function get_record(employee_name, insatiable_file) { const division_record = get("get_record", the_division) (employee_name, insatiable_file_content( - insatiable_file)); + insatiable_file); return ! is_undefined(record) ? attach_tag(the_division, division_record) : undefined; } - -
  3. -
  4. - 为总部实现一个 + +
  5. +
  6. + 为总部实现一个 - get-salary + get-salary get_salary - 过程 - 函数 + procedure + function - ,该过程可以从任何部门的人事文件中返回特定员工记录的薪水信息。为了使该操作正常工作,记录应该如何结构化? + ,该函数用于从任何部门的人事文件中返回给定员工记录的薪资信息。该记录应如何构造才能使此操作正常运行?

    - 每个部门都需要实现诸如get_salary的函数,并将它们安装在Insatiable的操作表中。然后,Insatiable的函数get_salary可以像这样: - + 每个部门都需要实现诸如 get_salary 之类的函数,并将它们安装在 Insatiable 的操作表中。随后,Insatiable 的函数 get_salary 可按如下方式实现: + function make_insatiable_record(division, record) { return pair(division, record); @@ -1187,27 +1248,30 @@ function get_salary(insatiable_record) { (insatiable_record_content); } - - 请注意,我们依赖于get_record返回的任何员工记录都与其部门标签相关,这被用于通用函数get_salary中,以便从操作表中检索正确的实现。 -
  7. -
  8. - - 为总部实现一个 - - find-employee-record - find_employee_record - - 过程。 - 函数。 - - 该过程应搜索所有部门的文件以找到给定员工的记录并返回该记录。假设这个 - 过程 - 函数 - - 的参数是员工的姓名和所有部门文件的列表。 - - - + + 请注意,我们依赖于这样一个事实:任何由 get_record 返回的员工记录,都已使用其所属部门进行标记,而该部门标识用于通用函数 + get_salary 从操作表中检索正确的实现。 +
  9. + +
  10. + + 为总部实现一个 + + find-employee-record + find_employee_record + + + procedure. + function. + + 这应该搜索所有部门的文件以查找给定员工的记录并返回该记录。假设该 + procedure + function + + 接受一个员工的姓名和所有部门文件的列表作为参数。 + + + function find_employee_record(employee_name, personnel_files) { if (is_null(personnel_files)) { @@ -1222,71 +1286,81 @@ function find_employee_record(employee_name, tail(personnel_files)); } } - - -
  11. -
  12. - - 当 Insatiable 收购一家新公司时,为了将新的人员信息纳入中央 - 系统,必须进行哪些更改? - -

    - 我们需要为每个新收购的公司做以下事情: -
      -
    • - 确定一个名称作为与新部门有关的任何数据项的标签。 -
    • -
    • - 编写所有特定于部门的函数,例如 - get_salary - 并使用部门标签将它们安装在公司范围的操作表中。 -
    • -
    • - 将员工文件添加到 - personnel_files列表中。 - 请注意,这是一个破坏性操作类似于操作表的扩展在于数据结构被永久地和不可逆转地修改; 详见段。 -
    • -
    -
  13. -
+ + + +
  • + + 当 Insatiable 接管一家新公司时,为了将新的人事信息纳入中央系统,必须做出哪些更改? + +

    + 对于每家新收购的公司,我们需要进行以下操作: +
      +
    • + 决定一个名称,用作与新部门相关的任何数据项的标签。 +
    • +
    • + 编写所有部门特定的函数,如 + get_salary + ,并使用该部门标签将它们安装到整个公司的操作表中。 +
    • +
    • + 将员工文件添加到 + personnel_files + 列表中。注意,这是一个 破坏性 + 操作类似于操作表的扩展——因为数据结构将被永久且不可撤销地修改;section 详细解释了这一概念。 +
    • +
    +
  • +
    - 数据导向编程 - 可加性 + + 数据导向编程 - + + 可加性 + + + + 消息传递 - + - 消息传递 - - 数据导向编程的关键思想是通过处理显式的操作与类型表(例如 - - - 图中的表) - - - 图中的表)。 - - 来在程序中处理通用操作。在 - 节中,我们使用的编程风格是通过让每个操作处理自己的分派来组织所需的类型分派。实际上,这将操作与类型表分解为行,每个通用操作 - 过程 - 函数 - 表示表的一行。 - - - 一种替代的实现策略是将表分解为列,与其使用基于数据类型的智能操作,不如使用基于操作名称的智能数据对象。我们可以通过安排这样一种方式来实现,例如让一个数据对象(如直角坐标数)被表示为一个 - 过程 - 函数 - ,该过程(或函数)将所需的操作名称作为输入并执行所指示的操作。在这种规则中, - - make-from-real-imag - make_from_real_imag - - 可以写成 - + + + +数据导向编程的关键思想是通过显式处理操作与类型表来处理程序中的通用操作,例如在 + + + figure. + + + figure. + + +中所示的表格。我们在 +section +中所采用的编程风格,是通过让每个操作负责其自身的分派来组织所需的基于类型的分派。实际上,这将操作与类型表分解为多行,每个通用操作 + + procedure + function + +都表示该表中的一行。 + + 另一种实现策略是将表分解为列,并且不使用智能操作来进行基于数据类型的分派,而使用智能数据对象来进行基于操作名称的分派。我们可以通过这样的安排,使得一个数据对象,例如矩形数,被表示为 + + procedure + function + +它将所需的操作名称作为输入,并执行相应的操作。在这种结构中, + + make-from-real-imag + make_from_real_imag + +可以写成 make_from_real_imagmessage-passing make_from_real_imag_message_passing square_definition @@ -1511,21 +1585,23 @@ function install_polar_package() { install_polar_package(); - - 相应的 - apply-generic + + 对应的 + + apply-generic apply_generic - 过程, - 函数, + procedure, + function, - 应用通用操作到一个参数时,现在简单地将操作名传递给数据对象并让对象完成工作:这种组织的一个限制是它只允许单参数通用 - 过程 - 函数 - - + 将通用操作应用于一个参数的过程,现在只需将操作的名称传给数据对象,并让该对象完成工作:这种组织方式的一项局限是它只允许一个参数的通用 + procedures + functions + + 。 + apply_genericwith message passing apply_generic_message_passing make_from_real_imag_message_passing @@ -1576,86 +1652,92 @@ function angle(z) { return apply_generic("angle", list(z)); } - - 注意 + + 请注意,由 - make-from-real-imag + make-from-real-imag make_from_real_imag 返回的值是一个 - 过程内部 - 函数内部 + 过程内部的 + 函数内部的 - dispatch - - 过程。 - 函数。 +dispatch + + + 过程. + 函数. - 这是当 - apply-generic + 这是 + + 过程 + 函数 + + ,当 + + apply-generic apply_generic - 请求执行操作时被调用的 - 过程 - 函数 - + 请求执行操作时被调用. + - 这种编程风格被称为消息传递。这个名称来自于这样一个图象:一个数据对象是一个接收到所请求操作名称作为消息的实体。我们已经在节中看到过一个消息传递的例子,在那里我们展示了如何用没有数据对象仅用 + 这种编程风格被称为 消息传递。 这个名称来源于这样的形象:数据对象是一种实体,它以 消息 的形式接收所请求的操作名称。 我们已经在第节中看到过消息传递的一个例子,其中我们看到了 - cons, + cons, pair, - car, + car, head, - cdr + cdr tail - 可以被定义为只有 + 可以仅用 - 过程。 - 函数。 + 过程. + 函数. - 在这里我们看到消息传递不是一个数学技巧,而是一种组织通用操作系统的有用技术。在本章的剩余部分中,我们将继续使用数据导向编程,而非消息传递,来讨论通用算术操作。在中,我们将返回到消息传递,并且我们将看到它在结构化模拟程序中可以是一种强有力的工具。 + 来定义,而不需要任何数据对象。 这里我们看到,消息传递并不是一种数学技巧,而是一种用于组织具有通用操作的系统的有用技术。 在本章的余下部分,我们将继续使用数据定向编程,而不是消息传递,来讨论通用算术运算。 在第章中,我们将回到消息传递,并看到它可以成为构建仿真程序的有力工具。 - 实现构造器 - make_from_mag_ang消息传递 + 实现构造函数 + make_from_mag_angmessage-passing - make-from-mag-ang + make-from-mag-ang make_from_mag_ang - 以消息传递风格。这个 + 以消息传递风格实现。该 - 过程 + 过程 函数 - 应当类似于上述给出的 + 应与上面给出的 - make-from-real-imag + make-from-real-imag make_from_real_imag - 过程。 - 函数。 + 过程 + 函数 + 类似。 - + make_from_mag_ang_message_passing make_from_real_imag_message_passing message_passing_example_2 4.589053123706931 - - + + function make_from_mag_ang(r, a) { function dispatch(op) { @@ -1674,9 +1756,9 @@ function make_from_mag_ang(r, a) { - + - + message_passing_example_2 const my_complex_number = @@ -1688,27 +1770,27 @@ const result = real_part(result); - + - + - 随着具有通用操作的大型系统的发展,可能需要新的数据对象类型或新的操作。对于三种策略中的每一种具有显式分派比较不同风格分派的通用操作、数据导向风格和消息传递风格描述在系统中为了增加新类型或新操作而必须进行的更改。对于需要经常添加新类型的系统,哪种组织最合适?对于需要经常添加新操作的系统,哪种最合适? - + 作为一个具有通用操作的大型系统不断演化,可能需要添加新的数据对象类型或新的操作。对于下列三种策略——具有显式调度的通用操作、数据定向风格和消息传递风格——描述为了添加新类型或新操作,系统必须做出的改变。哪个组织方式对于经常需要添加新类型的系统最为合适?哪个对于经常需要添加新操作的系统最为合适? +
    • - 具有显式分派的通用操作:对于每种新类型,我们需要触及每个通用接口函数,添加一个新案例。 + 具有显式调度的通用操作:对于每个新类型,我们需要修改每个通用接口函数,并添加新的案例。
    • - 数据导向风格:在这里,通用接口函数的实现可以简洁地打包在每个新类型的安装库中。我们也可以为新操作提供安装库。 + 数据定向风格:在这种风格中,通用接口函数的实现可以整齐地打包在每个新类型的install库中。我们也可以为新操作配备install库。
    • - 消息传递风格:与数据导向风格类似,我们需要为每种新类型编写一个库。在这种情况下,库由一个分派函数组成,每个通用接口函数都有一个案例。 + 消息传递风格:与数据定向风格相似,我们需要为每个新类型编写一个库。在这种情况下,该库由一个包含每个通用接口函数情况的调度函数组成。
    - 总的来说,当我们需要频繁添加新操作时,使用数据导向风格是最好的,而在我们频繁添加新类型时,消息传递风格更为合适。 + 总的来说,当我们需要频繁添加新操作时,数据定向风格可能是最佳选择;而当我们需要频繁添加新类型时,消息传递风格则更为合适。
    diff --git a/xml/zh/chapter2/section5/section5.xml b/xml/zh/chapter2/section5/section5.xml new file mode 100644 index 000000000..bef50999d --- /dev/null +++ b/xml/zh/chapter2/section5/section5.xml @@ -0,0 +1,98 @@ +
    + 具有通用操作的系统 + + + + + +在上一节中,我们看到了如何设计系统,使得数据对象可以以不止一种方式表示。关键思想是通过通用接口将指定数据操作的代码与多种表示形式关联起来 + + procedures. + functions. + +现在我们将看到如何利用这一相同思想,不仅定义针对不同表示形式具有通用性的操作,而且定义针对不同类型参数具有算术通用性的操作。我们已经见过几种不同的算术运算包:内置于我们语言中的基本算术运算(+-*/),有理数算术 + + (add-rat, + (add_rat, + + + sub-rat, + sub_rat, + + + mul-rat, + mul_rat, + + + div-rat) + div_rat) + +的章节,以及我们在章节中实现的复数算术。现在我们将利用数据导向技术构造一个整合了此前构建的所有算术运算包的算术运算包。 + + + + + 图 + + + 图 + + + 展示了我们将构建的系统结构。注意 + 抽象屏障在泛型算术系统中 + 抽象屏障。从使用数字的人的角度来看,只有一个 + + 过程 + 函数 + + add,它对所提供的数字进行操作。 + + Add + 函数 add + + + 是泛型接口的一部分,该接口允许使用数字的程序以统一方式访问单独的普通算术、有理数算术和复数算术包。任何单独的算术包(例如复数包)也可以通过泛型 + + 过程 + 函数 + + (例如 + + add-complex) + add_complex) + + + 来访问,该过程组合了为不同表示方式(例如矩形和极坐标)设计的包。此外,系统的结构是累加的,因此可以分别设计各个算术包,并将它们组合成一个泛型算术系统。 + 消息传递 + + +
    +
    + 泛型算术系统。 + +
    +
    + +
    +
    + + 泛型 + 泛型算术运算系统结构 + 算术系统。 + + +
    +
    +
    +
    + + + &subsection2.5.1; + + + &subsection2.5.2; + + + &subsection2.5.3; + +
    diff --git a/xml/cn/chapter2/section5/subsection1.xml b/xml/zh/chapter2/section5/subsection1.xml similarity index 65% rename from xml/cn/chapter2/section5/subsection1.xml rename to xml/zh/chapter2/section5/subsection1.xml index 0092a3c8d..ad6a33029 100644 --- a/xml/cn/chapter2/section5/subsection1.xml +++ b/xml/zh/chapter2/section5/subsection1.xml @@ -1,54 +1,60 @@ - - 通用算术操作 - + + 通用算术操作 + - 通用算术操作 - - 设计通用算术操作的任务类似于设计通用复数操作的任务。例如,我们希望有一个通用加法 + + 通用算术操作 + + + + 设计通用算术操作的任务类似于设计通用复数运算的任务。例如,我们希望拥有一个通用加法 - 过程 + 过程 函数 - add,它在普通数字上像普通原始加法 - +一样运作,就像在有理数上 + add,其行为在普通数字上就像普通基本加法 + + + 一样,在有理数上则像 - add-rat + add-rat add_rat - ,在复数上像 + 的运算,在复数上则像 - add-complex + add-complex add_complex - 一样。我们可以通过采用我们在实现通用复数选择器时使用的相同策略来实现add和其他通用算术操作。我们将为每种数字附加一个类型标签,并使通用 + 的运算。我们可以实现add以及其他通用算术操作,方法是采用我们在第节中为复数实现通用选择器时所使用的相同策略。我们将为每种数字附加一个类型标签,并使通用 - 过程 + 过程 函数 - 根据其参数的数据类型分派到一个适当的软件包。 - - - 通用算术 + 根据其参数的数据类型调度到相应的包中。 + + + + + 通用算术 - 过程 + 过程 函数 - 定义如下: - + 的定义如下: + add (通用) sub (通用) mul (通用) div (通用) ops apply_generic - + (define (add x y) (apply-generic 'add x y)) (define (sub x y) (apply-generic 'sub x y)) (define (mul x y) (apply-generic 'mul x y)) (define (div x y) (apply-generic 'div x y)) - + function add(x, y) { return apply_generic("add", list(x, y)); } @@ -58,42 +64,49 @@ function mul(x, y) { return apply_generic("mul", list(x, y)); } function div(x, y) { return apply_generic("div", list(x, y)); } - - - - 我们开始安装一个用于处理 - 通用在通用算术系统中 - 普通数 (在通用算术系统中) - 普通数字的软件包,即我们语言的原语数字。 + + + + + + 我们首先安装一个用于处理 + 数字通用在通用算术系统中 + 普通数字(在通用算术系统中) + 普通数字,即我们语言中的原始数字。 我们 - 会 + + will + - 为这些附加 + 为这些数字打上标记, + 使用 - scheme-number符号。 + 符号 scheme-number - 字符串"javascript_number"。 + string "javascript_number" - 此包中的算术操作是原语算术 + 本包中的算术运算是原始算术 - 过程 + 过程 函数 - (因此无需定义额外的 + (因此不需要定义额外的 - 过程 + 过程 函数 - 来处理未打标签的数字)。由于这些操作每个都需要两个 - 参数,它们被安装在由列表键控的表中 + 来处理未打标记的数字)。 由于这些运算各接受两个参数, + 它们被安装在以以下列表为键的表中: - - (scheme-number scheme-number): + + (scheme-number scheme-number): + - list("javascript_number", "javascript_number") + list("javascript_number", "javascript_number"): + - + packageJavaScript-number javascript_number package install_javascript_number_package @@ -138,42 +151,44 @@ function install_javascript_number_package() { - + + + 使用 - Scheme-number 包 - JavaScript-number 包 + Scheme-number package + JavaScript-number package 的用户将通过以下 - 过程: - 函数: + 过程: + 函数: - 创建(带标签的)普通数字: - + 创建(带标签的)普通数字。 + actually_install_javascript_number_package install_javascript_number_package(); - - + + make_javascript_number install_javascript_number_package_usage install_javascript_number_package actually_install_javascript_number_package install_javascript_number_package_usage_example [ 'javascript_number', 9 ] - + (define (make-scheme-number n) ((get 'make 'scheme-number) n)) - + function make_javascript_number(n) { return get("make", "javascript_number")(n); } - - + + install_javascript_number_package_usage_example const n1 = make_javascript_number(4); @@ -181,18 +196,21 @@ const n2 = make_javascript_number(5); add(n1, n2); + - - - - 现在通用算术系统的框架已到位, - 我们可以轻松地包含新类型的数字。这里有一个执行有理数算术的包。注意,由于可加性的好处,我们可以使用中的有理数代码,而无需修改,作为包中的内部 - - 过程 + + + + + 既然通用算术系统的框架已经建立, + 我们就可以轻松地引入新的数字类型。下面是一个执行有理算术的包。请注意,由于可加性的优势, + 我们可以直接使用来自第节的有理数代码, + 作为包内的内部 + 过程 函数 : - + packagerational-number rational package rational-number arithmeticinterfaced to generic arithmetic system @@ -306,52 +324,50 @@ add(r1, r2); + - 我们可以安装一个类似的软件包来处理复数,使用标签 + 我们可以安装一个类似的包来处理复数,使用标签 - complex。 + complex "complex" - 在创建软件包时,我们从表中提取由直角和极坐标包定义的操作 + 在创建该包时,我们从表中提取了运算操作 - make-from-real-imag + make-from-real-imag make_from_real_imag - make-from-mag-ang + make-from-mag-ang make_from_mag_ang - 。 - 可加性 - 可加性使我们可以使用相同的内部操作, + 这些操作由矩形和极坐标包定义。 + 加法性 + 加法性允许我们使用相同的 - add-complex, - add_complex, - + add-complex + add_complex - sub-complex, - sub_complex, - + sub-complex + sub_complex - mul-complex, - mul_complex, - + mul-complex + mul_complex - div-complex + div-complex div_complex - 过程 + 过程 函数 - ,来自。 + 作为内部运算,来自节. packagecomplex-number complex package @@ -451,14 +467,15 @@ function install_complex_package() { - - 复数包以外的程序可以通过实部和虚部或模长和角度来构造复数。请注意,最初在直角和极坐标包中定义的底层 + + + 复数包外的程序可以通过实部和虚部构造复数,也可以通过幅度和角度构造复数。请注意,最初在矩形和极坐标包中定义的底层 - 过程, + 过程, 函数, - 被导出到复数包中,并从那里导出到外部世界。 - + 被导出到复数包中,并由此导出到外部世界。 + actually_install_complex_package install_complex_package(); @@ -504,128 +521,136 @@ tail(add(r, p)); - - 我们这里有一个 - 类型标签两级 - 的两级标签系统。一个典型的复数,例如 -$3+4i$ 在直角形式中,如 - - - 图 - + + + + 这里我们所展示的是一个 + 类型标签双层 + 双层标签系统。一个典型的复数,例如以矩形形式表示的 $3+4i$,将被表示为如下所示 + + + 图形。 + - 图 + 图形 - 所示表示。外层标签 - (complex) + 外层标签 + (complex) ("complex") - 用于将数字引导到复数包。进入 - 复数包后,下一标签 + 用于将该数字定向到复数包中。一旦进入复数包,下一个标签 - (rectangular) + (rectangular) ("rectangular") - 用于将数字引导到直角包。在一个大型而复杂的系统中可能会有许多级别,每个级别通过通用操作与下一个级别接口。当一个数据对象被传递 - 向下时,用于将其引导到适当包的外层标签会被去掉(通过应用 -contents ) 以及下一级标签(如果有)变得可见,以便用于进一步分派。 + 用于将该数字定向到矩形包中。在一个庞大而复杂的系统中可能存在许多层级,每一层均通过通用操作与下一层接口。当数据对象向下传递时,用于将其定向到相应包的外层标签被剥离(通过应用 + contents),而下一层标签(如果有)将变得可见以用于进一步分发。 - +
    - 矩形形式中 $3+4i$ 的表示。 + 以矩形形式表示的 $3+4i$。 - +
    - +
    - 矩形形式中 $3+4i$ 的表示。 + 以矩形形式表示的 $3+4i$。 - +
    -
    - - 在上述软件包中,我们使用了 + + + + + 在上述包中,我们使用了 - add-rat, + add-rat, add_rat, - add-complex, + add-complex, add_complex, - + +
    和其他算术 - 过程 + 过程 函数 - ,正如最初编写的一样。一旦这些声明是不同安装 + 完全与原始编写相同。一旦这些声明成为不同安装 - 过程 - 函数 + 过程, + 函数, - 的内部,它们就不再需要彼此不同的名称:我们可以简单地在两个包中分别命名为add, - sub,mul,和 - div。 -
    - - Louis Reasoner 试图求值表达式 + 的内部部分,它们便不再需要相互区分的名称:我们可以仅在两个包中简单地将它们命名为 add, + sub, mul 和 + div。 +
    + + + + Louis Reasoner 尝试计算表达式 - (magnitude z) + (magnitude z) magnitude(z) 其中 -z 是如 + +z + + 是图中所示的对象 - - 图 - + + 图. + - 图 + 图. - 所示的对象。令他惊讶的是,结果不是 -$5$ - 他从 + 令他惊讶的是,答案却不是 + +$5$ + 他收到了来自 - apply-generic, - apply_generic, + apply-generic + apply_generic - 得到了一个错误信息,提示该操作没有方法。 -magnitude - 的类型 + 的错误信息,提示没有针对该操作的方法 +magnitude 在这些类型上 - (complex)。 - list("complex")。 - + (复数) + list("复数") - 他向 Alyssa P. Hacker 展示了这个互动,Alyssa 说问题是复数选择器从未为 + 他向 Alyssa P. Hacker 展示了这一交互过程,她说 问题在于,复数选择器从未为 - complex - "complex" + 复数 + "复数" - 数而定义,只为 + 数定义,而只为 - polar + 极坐标 "polar" - rectangular + 直角坐标 "rectangular" - 数字定义。要解决这个问题,你只需将以下内容添加到complex包中: + 数定义。要使其工作,你只需在 + 复数 + 包中添加以下内容: use_generic_magnitude install_complex_package_usage @@ -653,74 +678,71 @@ const z = make_complex_from_real_imag(3, 4); magnitude(z); - - 详细描述为什么这可以工作。作为一个例子,跟踪所有在求值表达式 + 详细描述为何这个方法能够奏效。例如,跟踪评估表达式时调用的所有 - (magnitude z) - magnitude(z) + 过程 + 函数 - 时调用的 + 的过程,其中 - 过程 - 函数 + (magnitude z) + magnitude(z) - ,其中 -z - 是如 +z 是下列图中所示的对象 - - 图 - + + figure. + - 图 + figure. - 所示的对象。特别是, + 特别是,调用了多少次 - apply-generic + apply-generic apply_generic - 被调用了多少次?在每种情况下被分派到哪个 + 函数?每种情况下分别调度到哪个 - 过程 + 过程 函数 ? - - 将上述行放入包中将会打开 -z ,这些行起到了传递作用。 + + 将上述代码行添加到包中会展开 + z,而这些代码行起到了传递的作用。 - apply-generic + apply-generic apply_generic - 将被调用两次。我们将使用替换模型跟踪程序的求值过程。 - + 将被调用两次。我们将用替换模型跟踪程序的求值过程。 + magnitude(z); apply_generic("magnitude", list(z)); -// In this case: +// 在这种情况下: // type_tags = map(type_tag, list(z)) -// Which evaluates to: -// type_tags = list("complex"); -// and -// fun = get("magnitude", list("complex")); -// which, due to the addition of -// put("magnitude", list("complex"), magnitude); -// fun = magnitude; +// 计算结果为: +// type_tags = list("复数"); +// 且 +// fun = get("magnitude", list("复数")); +// 由于添加了 +// put("magnitude", list("复数"), magnitude); +// 因此 fun = magnitude; apply(magnitude, map(contents, list(z))); apply(magnitude, pair("rectangular", pair(3, 4))); magnitude(pair("rectangular"), pair(3, 4)); apply_generic("magnitude", list(pair("rectangular"), pair(3, 4))); -// type_tags = map(type_tag, list(z)) evaluates to list("rectangular") -// fun = get("magnitude", list("rectangular")) which evaluates to +// type_tags = map(type_tag, list(z)) 的计算结果为 list("矩形") +// fun = get("magnitude", list("矩形")) 的计算结果为 // z => math_sqrt(square(real_part(z)) + square(imag_part(z))) -// z => math_sqrt(square(head(z)) + square(tail(z))) -apply(fun, map(contents, list(pair("rectangular"), pair(3, 4)))) -apply(fun, pair(3, 4)) +// 即 z => math_sqrt(square(head(z)) + square(tail(z))) +apply(fun, map(contents, list(pair("rectangular"), pair(3, 4)))); +apply(fun, pair(3, 4)); (z => math_sqrt(square(head(z)) + square(tail(z))))(pair(3, 4)); -math_sqrt(square(head(pair(3, 4))) + square(tail(pair(3, 4)))) +math_sqrt(square(head(pair(3, 4))) + square(tail(pair(3, 4)))); ... math_sqrt(square(3) + square(4)); ... @@ -729,124 +751,122 @@ math_sqrt(25); 5 -;;*** Use substitution rule: +;;*** 使用替换规则: (magnitude z) -;;** First apply-generic: +;;** 第一次调用 apply-generic: (apply-generic 'magnitude z) -;; where z is the whole object including symbol 'complex. +;; 其中 z 是包含符号 'complex 的整个对象。 -;;recall +;;回顾 (define (apply-generic op . args) (let ((type-tags (map type-tag args))) (let ((proc (get op type-tags))) (if proc (apply proc (map contents args)) (error - "No method for these types -- APPLY-GENERIC" - (list op type-tags)))))) -;; substitution + "这些类型没有对应的方法 -- APPLY-GENERIC" + (list op type-tags))))) +;; 替换 (let ((type-tags '(complex)) ... )) (let ((proc (get op '(complex))) ... )) (let ((proc magnitude) ... )) -(if proc... ;; true -(apply magnitude (contents z)) -(magnitude z-prime) ;; where z-prime is the contents (the cdr) of the original - ;; object, that is, with the 'complex stripped off. +(if proc... ;; 真 + (apply magnitude (contents z)) + (magnitude z-prime)) ;; 其中 z-prime 是原始对象的内容(即 cdr),也就是去掉 'complex 后的部分。 -;;** Second apply-generic: +;;** 第二次调用 apply-generic: (let ((type-tags '(rectangular)) ... )) (let ((proc (get op '(rectangular))) ... )) (let ((proc (get 'magnitude '(rectangular))) ... )) (let ((proc (lambda (z) (sqrt (+ (square (real-part z)) - (square (imag-part z)))))) ... ))) - -(if proc... ;; true -(apply (lambda (z) (sqrt (+ (square (real-part z)) - (square (imag-part z))))) (contents z-prime)) -(sqrt (+ (square 3) (square 4))) -5 + (square (imag-part z)))))) ... ))) +(if proc... ;; 真 + (apply (lambda (z) (sqrt (+ (square (real-part z)) + (square (imag-part z))))) (contents z-prime)) + (sqrt (+ (square 3) (square 4))) + 5 - + - - 内部 + + + 内部的 JavaScript内部类型系统 数据类型在 JavaScript 中 - is_number (原语函数)数据类型 and - is_string (原语函数)数据类型 and + is_number(基本函数)数据类型和 + is_string(基本函数)数据类型和 attach_tag使用 JavaScript 数据类型 type_tag使用 JavaScript 数据类型 contents使用 JavaScript 数据类型 - 过程 - 函数 + + 过程 + 函数 - scheme-number - javascript_number - + scheme-number + javascript_number - 包中本质上不过是对原语的调用 + 包中,本质上不过是对基本 - 过程 + 过程 函数 -+, - ,等等。我们的类型标签系统要求每个数据对象都必须附加一个类型,因此无法直接使用语言的原语。然而,事实上,所有 + +-等的调用。由于我们的类型标签系统要求每个数据对象都必须附有一个类型,因此不能直接使用语言的原始基本函数。实际上,所有 - Lisp + Lisp JavaScript - 实现确实有一个类型系统,它们在内部使用。原语谓词如 + 实现都有一个内部类型系统,用于它们自身的内部操作。像 - symbol? + symbol? is_string - number? + number? is_number - 判断数据对象是否具有特定类型。修改 + 这样的基本谓词决定了数据对象是否属于特定类型。请修改来自章节 - type-tag, + type-tag, type_tag, - 的定义 -contents ,和 + contents以及 - attach-tag + attach-tag attach_tag - 来自以便我们的通用系统利用 - Schemes - JavaScripts + 的定义,使我们的泛型系统能利用 + + Scheme的 + JavaScript的 - 内部类型系统。也就是说,系统应与之前一样运作,只是普通数字应简单地表示为 + 内部类型系统。也就是说,除了一点之外,系统的运行方式应与以前相同,即普通数字应仅表示为 - Scheme + Scheme JavaScript - 数字,而不是其 + 数字,而不是表示为对,其中 - car + car head - 是 + 为 - 符号 scheme-number。 + 符号 scheme-number. - 字符串 "javascript_number"。 + string "javascript_number". - - + 以下修改将利用 JavaScript 数字实现: - + function attach_tag(type_tag, contents) { return is_number(contents) @@ -868,40 +888,42 @@ function contents(datum) { : error(datum, "bad tagged datum -- contents"); } - + (define (attach-tag type-tag contents) (if (number? contents) contents (cons type-tag contents))) (define (type-tag datum) -(cond ((number? datum) 'scheme-number) - ((pair? datum) (car datum)) - (else (error "Wrong datum -- TYPE-TAG" datum)))) + (cond ((number? datum) 'scheme-number) + ((pair? datum) (car datum)) + (else (error "Wrong datum -- TYPE-TAG" datum)))) (define (contents datum) -(cond ((number? datum) datum) - ((pair? datum) (cdr datum)) - (else (error "Wrong datum -- CONTENTS" datum)))) - - + (cond ((number? datum) datum) + ((pair? datum) (cdr datum)) + (else (error "Wrong datum -- CONTENTS" datum)))) + + - + + - - 定义一个通用相等性谓词 - is_equal (通用相等性谓词) - 相等性通用在通用算术系统中 + + + 定义一个通用相等谓词 + is_equal(通用谓词) + 相等通用在通用算术系统中 - equ? + equ? is_equal - ,用于测试两个数的相等性,并将其安装在通用算术包中。此操作应适用于普通数字,有理数和复数。 - + 用于测试两个数字的相等性,并将其安装在通用算术包中。该操作应适用于普通数字、有理数和复数。 + - + -// provided by GitHub user clean99 +// 由 GitHub 用户 clean99 提供 function is_equal(x, y) { return apply_generic("is_equal", list(x, y)); @@ -943,25 +965,27 @@ function install_complex_package() { //... } - + - + + - + + 定义一个通用谓词 is_equal_to_zero (通用) - 零值测试 (通用) + 零测试 (通用) - =zero? + =zero? is_equal_to_zero - ,用于测试其参数是否为零,并将其安装在通用算术包中。此操作应适用于普通数字,有理数和复数。 - + 定义一个操作,用于测试其参数是否为零,并将其安装到通用算术包中。该操作应适用于普通数字、有理数以及复数。 + - + -// provided by GitHub user clean99 +// 由 GitHub 用户 clean99 提供 function is_equal_to_zero(x) { return apply_generic("is_equal_to_zero", list(x)); @@ -1004,9 +1028,12 @@ function install_complex_package() { //... } - + - + + + + + 通用算术操作 - 通用算术操作
    diff --git a/xml/zh/chapter2/section5/subsection2.xml b/xml/zh/chapter2/section5/subsection2.xml new file mode 100644 index 000000000..8db0cd1cc --- /dev/null +++ b/xml/zh/chapter2/section5/subsection2.xml @@ -0,0 +1,1359 @@ + + + + + + + + + 一种处理 + operation跨类型 + 跨类型运算 + type(s)跨类型运算 + 跨类型运算的方法是为每个对该运算有效的类型组合设计一个不同的 + + 过程 + 函数 + + 。例如,我们可以扩展复数包,使其提供一个用于将复数与普通数字相加的 + + 过程 + 函数 + + ,并使用标签 + + (complex scheme-number) + list("complex", "javascript_number") + + 将其安装到表中: + 我们还必须提供一个几乎相同的 + + 过程 + 函数 + + 来处理这些类型

    + + (scheme_number complex). + + list("javascript_number", "complex"). + +
    + + add_complex_to_javascript_number_example + +const c = make_complex_from_real_imag(4, 3); +const n = make_javascript_number(7); + +add(c, n); + + + + add_complex_to_javascript_num + add_complex_to_javascript_number + install_javascript_number_package_usage + install_complex_package_usage + add_complex_to_javascript_number_example + [ 'complex', [ 'rectangular', [ 11, 3 ] ] ] + +;; 要包含在复数包中 +(define (add-complex-to-schemenum z x) + (make-from-real_imag (+ (real-part z) x) + (imag-part z))) + +(put 'add '(complex scheme-number) + (lambda (z x) (tag (add-complex-to-schemenum z x)))) + + +// 要包含在复数包中 +function add_complex_to_javascript_num(z, x) { + return make_complex_from_real_imag(real_part(z) + x, imag_part(z)); +} +put("add", list("complex", "javascript_number"), + (z, x) => tag(add_complex_to_javascript_num(z, x))); + + +
    + + + 这种技术是可行的,但它非常繁琐。在这种系统中,引入新类型的代价不仅仅在于构建该类型的 + + 过程 + 函数 + + 包,还在于构建和安装实现跨类型运算的 + + 过程 + 函数 + + 。所需的代码量很容易远远超过仅定义类型自身运算所需的代码量。这种方法也削弱了我们以累加方式组合独立包的能力,或者至少限制各个包的实现者需要考虑其他包的程度。例如,在上述示例中,处理复数和普通数字之间混合运算的任务似乎理应由复数包负责。然而,组合有理数和复数的工作,可能由复数包、有理数包,或者由某个使用从这两个包中提取运算的第三方包来完成。在设计包含众多包和众多跨类型运算的系统时,制定各包之间责任分工的连贯策略可能是一项艰巨的任务。 + + + + + 强制类型转换 + + + + + 强制类型转换 + + + + 在完全无关的操作作用于完全无关的类型的总体情况下,实现显式跨类型操作虽繁琐,却是目前所能达到的最佳手段。 + 幸运的是,通过利用类型系统中可能潜在的附加结构,我们通常能做得更好。通常,不同的数据类型并非完全独立, + 而总存在某种方式可以将一种类型的对象视为另一种类型。这个过程被称为强制类型转换。例如, + 如果要求我们将一个普通数与一个复数组合进行算术运算,我们可以将普通数视为其虚部为零的复数。 + 这样一来,问题就转化为组合两个复数的问题,而这一问题可以通过复数算术包以常规方式处理。 + + + + 通常,我们可以通过设计 + 强制类型转换过程函数 + 强制类型转换 + + 过程 + 函数 + + ,将一种类型的对象转换成另一种类型的等价对象。下面是一个典型的强制类型转换 + + 过程, + 函数, + + ,它将给定的普通数转换为其实部为该数且虚部为零的复数: + + javascript_number_to_complex + javascript_number_to_complex + +(define (scheme-number->complex n) + (make-complex-from-real-imag (contents n) 0)) + + +function javascript_number_to_complex(n) { + return make_complex_from_real_imag(contents(n), 0); +} + + + + 强制类型转换用于强制类型转换 + 强制类型转换 + 我们将在一个特殊的强制类型转换表中安装这些 + + 过程 + 函数 + + ,以这两种类型的名称为索引: + + put_get_coercion + +(define coercion-list '()) + +(define (clear-coercion-list) + (set! coercion-list '())) + +(define (put-coercion type1 type2 item) + (if (get-coercion type1 type2) coercion-list + (set! coercion-list + (cons (list type1 type2 item) + coercion-list)))) + +(define (get-coercion type1 type2) + (define (get-type1 listItem) + (car listItem)) + (define (get-type2 listItem) + (cadr listItem)) + (define (get-item listItem) + (caddr listItem)) + (define (get-coercion-iter list type1 type2) + (if (null? list) #f + (let ((top (car list))) + (if (and (equal? type1 (get-type1 top)) + (equal? type2 (get-type2 top))) (get-item top) + (get-coercion-iter (cdr list) type1 type2))))) + (get-coercion-iter coercion-list type1 type2)) + + +let coercion_list = null; + +function clear_coercion_list() { + coercion_list = null; +} + +function put_coercion(type1, type2, item) { + if (is_null(get_coercion(type1, type2))) { + coercion_list = pair(list(type1, type2, item), + coercion_list); + } else { + return coercion_list; + } +} + +function get_coercion(type1, type2) { + function get_type1(list_item) { + return head(list_item); + } + function get_type2(list_item) { + return head(tail(list_item)); + } + function get_item(list_item) { + return head(tail(tail(list_item))); + } + function get_coercion_iter(items) { + if (is_null(items)) { + return undefined; + } else { + const top = head(items); + return equal(type1, get_type1(top)) && + equal(type2, get_type2(top)) + ? get_item(top) + : get_coercion_iter(tail(items)); + } + } + return get_coercion_iter(coercion_list); +} + + + + put_coercion_usage + put_get_coercion + javascript_number_to_complex + install_complex_package_usage + put_coercion_usage_example + put_get_coercion + + +(put-coercion 'scheme-number 'complex scheme-number->complex) + + +put_coercion("javascript_number", "complex", + javascript_number_to_complex); + + + + put_coercion_usage_example + +get_coercion("javascript_number", "complex"); + + + (我们假定存在 + + put-coercion + put_coercion + + 和 + + get-coercion + get_coercion + + + 过程 + 函数 + + 用于操作此表。) 通常表中的某些槽位将为空,因为通常无法将每种类型的任意数据对象强制转换为所有其他类型。例如,没有办法将任意复数强制转换为普通数字,因此表中不会包含普遍的 + + complex->scheme-number + + complex_to_javascript_number + + + + 过程 + 函数 + + 。 + + 强制类型转换表 + + apply-generic + + + apply_generic + + + 过程 + 函数 +强制类型转换 + 过程 + 函数 +强制类型转换关于更一般情况,请参见练习强制类型转换表 + 过程: + 函数: +apply_genericwith coercion + base_operation_table + +// operation_table, put and get +// from chapter 3 (section 3.3.3) +function assoc(key, records) { + return is_null(records) + ? undefined + : equal(key, head(head(records))) + ? head(records) + : assoc(key, tail(records)); +} +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)); + if (is_undefined(record)) { + return undefined; + } else { + return 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 + : "undefined operation -- table"; + } + return dispatch; +} +const operation_table = make_table(); +const get = operation_table("lookup"); +const put = operation_table("insert"); + +// In Source, most functions have a fixed number of arguments. +// (The function list is the only exception, to this so far.) +// The function apply_in_underlying_javascript allows us to +// apply any given function fun to all elements of the argument +// list args, as if they were separate arguments +function apply(fun, args) { + return apply_in_underlying_javascript(fun, args); +} +function add(x, y) { + return apply_generic("add", list(x, y)); +} +function sub(x, y) { + return apply_generic("sub", list(x, y)); +} +function mul(x, y) { + return apply_generic("mul", list(x, y)); +} +function div(x, y) { + return apply_generic("div", list(x, y)); +} + +function attach_tag(type_tag, contents) { + return pair(type_tag, contents); +} +function type_tag(datum) { + return is_pair(datum) + ? head(datum) + : error(datum, "bad tagged datum -- type_tag"); +} +function contents(datum) { + return is_pair(datum) + ? tail(datum) + : error(datum, "bad tagged datum -- contents"); +} + + + + javascript_number_package + base_operation_table + +function install_javascript_number_package() { + function tag(x) { + return attach_tag("javascript_number", x); + } + put("add", list("javascript_number", "javascript_number"), + (x, y) => tag(x + y)); + put("sub", list("javascript_number", "javascript_number"), + (x, y) => tag(x - y)); + put("mul", list("javascript_number", "javascript_number"), + (x, y) => tag(x * y)); + put("div", list("javascript_number", "javascript_number"), + (x, y) => tag(x / y)); + put("make", "javascript_number", + x => tag(x)); + return "done"; +} +install_javascript_number_package(); + +function make_javascript_number(n) { + return get("make", "javascript_number")(n); +} + + + + complex_number_package + base_operation_table + +// generic selector functions for complex numbers + +function real_part(z) { + return apply_generic("real_part", list(z)); +} +function imag_part(z) { + return apply_generic("imag_part", list(z)); +} +function magnitude(z) { + return apply_generic("magnitude", list(z)); +} +function angle(z) { + return apply_generic("angle", list(z)); +} +function square(x) { + return x * x; +} + +function install_rectangular_package() { + function real_part(z) { return head(z); } + function imag_part(z) { return tail(z); } + function make_from_real_imag(x, y) { return pair(x, y); } + function magnitude(z) { + return math_sqrt(square(real_part(z)) + + square(imag_part(z))); + } + function angle(z) { + return math_atan2(imag_part(z), real_part(z)); + } + function make_from_mag_ang(r, a) { + return pair(r * math_cos(a), r * math_sin(a)); + } + // interface to the rest of the system + function tag(x) { + return attach_tag("rectangular", x); + } + put("real_part", list("rectangular"), real_part); + put("imag_part", list("rectangular"), imag_part); + put("magnitude", list("rectangular"), magnitude); + put("angle", list("rectangular"), angle); + put("make_from_real_imag", "rectangular", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "rectangular", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} +install_rectangular_package(); + +function install_polar_package() { + // internal functions + function magnitude(z) { return head(z); } + function angle(z) { return tail(z); } + function make_from_mag_ang(r, a) { return pair(r, a); } + function real_part(z) { + return magnitude(z) * math_cos(angle(z)); + } + function imag_part(z) { + return magnitude(z) * math_sin(angle(z)); + } + function make_from_real_imag(x, y) { + return pair(math_sqrt(square(x) + square(y)), + math_atan2(y, x)); + } + + // interface to the rest of the system + function tag(x) { return attach_tag("polar", x); } + put("real_part", list("polar"), real_part); + put("imag_part", list("polar"), imag_part); + put("magnitude", list("polar"), magnitude); + put("angle", list("polar"), angle); + put("make_from_real_imag", "polar", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "polar", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} +install_polar_package(); + +function install_complex_package() { + // imported functions from rectangular and polar packages + function make_from_real_imag(x, y) { + return get("make_from_real_imag", "rectangular")(x, y); + } + function make_from_mag_ang(r, a) { + return get("make_from_mag_ang", "polar")(r, a); + } + + // internal functions + function add_complex(z1, z2) { + return make_from_real_imag(real_part(z1) + + real_part(z2), + imag_part(z1) + + imag_part(z2)); + } + function sub_complex(z1, z2) { + return make_from_real_imag(real_part(z1) - + real_part(z2), + imag_part(z1) - + imag_part(z2)); + } + function mul_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) * + magnitude(z2), + angle(z1) + + angle(z2)); + } + function div_complex(z1, z2) { + return make_from_mag_ang(magnitude(z1) / + magnitude(z2), + angle(z1) - + angle(z2)); + } + + // interface to rest of the system + function tag(z) { + return attach_tag("complex", z); + } + put("add", list("complex", "complex"), + (z1, z2) => tag(add_complex(z1, z2))); + put("sub", list("complex", "complex"), + (z1, z2) => tag(sub_complex(z1, z2))); + put("mul", list("complex", "complex"), + (z1, z2) => tag(mul_complex(z1, z2))); + put("div", list("complex", "complex"), + (z1, z2) => tag(div_complex(z1, z2))); + put("make_from_real_imag", "complex", + (x, y) => tag(make_from_real_imag(x, y))); + put("make_from_mag_ang", "complex", + (r, a) => tag(make_from_mag_ang(r, a))); + return "done"; +} +install_complex_package(); + +function make_complex_from_real_imag(x, y){ + return get("make_from_real_imag", "complex")(x, y); +} +function make_complex_from_mag_ang(r, a){ + return get("make_from_mag_ang", "complex")(r, a); +} + + + + coercion_support + +// coercion support + +let coercion_list = null; + +function clear_coercion_list() { + coercion_list = null; +} + +function put_coercion(type1, type2, item) { + if (is_undefined(get_coercion(type1, type2))) { + coercion_list = pair(list(type1, type2, item), + coercion_list); + } else { + return coercion_list; + } +} + +function get_coercion(type1, type2) { + function get_type1(list_item) { + return head(list_item); + } + function get_type2(list_item) { + return head(tail(list_item)); + } + function get_item(list_item) { + return head(tail(tail(list_item))); + } + function get_coercion_iter(items) { + if (is_null(items)) { + return undefined; + } else { + const top = head(items); + return equal(type1, get_type1(top)) && + equal(type2, get_type2(top)) + ? get_item(top) + : get_coercion_iter(tail(items)); + } + } + return get_coercion_iter(coercion_list); +} + + + + apply_generic_with_coercion_example + base_operation_table + javascript_number_package + complex_number_package + coercion_support + +function javascript_number_to_complex(n) { + return make_complex_from_real_imag(contents(n), 0); +} + +put_coercion("javascript_number", "complex", + javascript_number_to_complex); + +const c = make_complex_from_real_imag(4, 3); +const n = make_javascript_number(7); + +add(c, n); + + + + apply_generic_with_coercion + apply_generic_with_coercion_example + [ 'complex', [ 'rectangular', [ 11, 3 ] ] ] + +(define (apply-generic op . args) + (let ((type-tags (map type-tag args))) + (let ((proc (get op type-tags))) + (if proc + (apply proc (map contents args)) + (if (= (length args) 2) + (let ((type1 (car type-tags)) + (type2 (cadr type-tags)) + (a1 (car args)) + (a2 (cadr args))) + (let ((t1->t2 (get-coercion type1 type2)) + (t2->t1 (get-coercion type2 type1))) + (cond (t1->t2 + (apply-generic op (t1->t2 a1) a2)) + (t2->t1 + (apply-generic op a1 (t2->t1 a2))) + (else + (error "No method for these types" + (list op type-tags)))))) + (error "No method for these types" + (list op type-tags))))))) + + +function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + if (! is_undefined(fun)) { + return apply(fun, map(contents, args)); + } else { + if (length(args) === 2) { + const type1 = head(type_tags); + const type2 = head(tail(type_tags)); + const a1 = head(args); + const a2 = head(tail(args)); + const t1_to_t2 = get_coercion(type1, type2); + const t2_to_t1 = get_coercion(type2, type1); + return ! is_undefined(t1_to_t2) + ? apply_generic(op, list(t1_to_t2(a1), a2)) + : ! is_undefined(t2_to_t1) + ? apply_generic(op, list(a1, t2_to_t1(a2))) + : error(list(op, type_tags), + "no method for these types"); + } else { + return error(list(op, type_tags), + "no method for these types"); + } + } +} + + + + + + 正如上面所阐述的那样,这种强制转换方案相对于明确定义的跨类型操作方法具有许多优势。虽然我们仍然需要编写强制转换 + + 过程 + 函数 + + 来关联各类型(对于一个拥有 $n$ 种类型的系统来说,可能需要编写 $n^2$ + + 过程 + 函数 + + ),但我们只需为每一对类型编写一个 + + 过程 + 函数 + + ,而不是为每一组类型和每个通用操作编写不同的 + + 过程 + 函数 + + 。如果我们足够聪明,通常可以用少于 $n^2$ 个强制转换 + + 过程. + 函数. + + 来应付所有情况。例如,如果我们知道如何将类型 1 转换为类型 2,以及如何将类型 2 转换为类型 3,那么我们就可以利用这一知识将类型 1 转换为类型 3。这可以大大减少当我们向系统中添加新类型时需要显式提供的强制转换 + + 过程 + 函数 + + 的数量。如果我们愿意在系统中构造出足够的复杂性,就可以让系统搜索各类型之间关系的,并自动生成那些可以从显式给出的强制转换 + + 过程 + 函数 + + 中推导出的转换。 我们所依赖的事实是,不同类型之间适当的转换仅依赖于类型本身,而不依赖于所要应用的操作。 + + + + + + 另一方面,可能存在某些应用场景,其中我们的强制转换方案还不够通用。即使两个待合并对象都无法转换为对方的类型,通过将两个对象都转换为第三种类型仍然可能执行该操作。为了处理这种复杂性并同时保持程序的模块化,通常需要构建利用类型之间关系中更深层次结构的系统,如下文所述。 + + + + + + 类型的层次结构 + + + + + 类型层次结构 + + + 类型层次结构 + + + + 上面介绍的强制转换方案依赖于类型对之间自然关系的存在。通常,不同类型之间的关系往往具有更全局的结构。例如,假设我们正在构建一个通用算术系统,以处理整数、有理数、实数和复数。在这样的系统中,将整数视为特殊类型的有理数是非常自然的,而有理数又是特殊类型的实数,实数又是特殊类型的复数。实际上,我们拥有的是所谓的类型的层次结构,其中,例如,整数是有理数的子类型 + type(s)子类型 + 子类型(即任何可以应用于有理数的操作都能自动应用于整数)。反过来,我们说有理数构成了整数的超类型 + type(s)超类型 + 超类型。这里所讨论的这种层次结构非常简单,其中每种类型至多只有一个超类型,也至多只有一个子类型。这样的结构被称为,如图所示。 +
    +
    + 类型的塔。 + tower of types + type(s)tower of + + +
    +
    + + + + 如果我们有一个塔结构,那么我们可以极大地简化向层次结构中添加新类型的问题,因为我们只需指定新类型如何嵌入到其上一级超类型中以及它如何成为下一级类型的超类型。例如,如果我们想将一个整数添加到一个复数中,我们就不必显式定义一个特殊的强制转换 + + 过程 + 函数 + + + integer->complex. + integer_to_complex. + + + 而是定义整数如何转换为有理数、有理数如何转换为实数,以及实数如何转换为复数。然后我们允许系统通过这些步骤将整数转换为复数,进而将两个复数相加。 + + + + + 类型提升 + apply_generic使用类型塔 + 我们可以以如下方式重新设计我们的 + + apply-generic + apply_generic + + + + 过程 + 函数 + + :对于每种类型,我们需要提供一个 + raise + + 过程, + 函数, + + 用于将该类型的对象在类型塔中“提升”一个层级。然后,当系统需要对不同类型的对象进行操作时,它可以依次将低层类型提升,直到所有对象都处于类型塔中的同一层级。(习题 + + + 和 + + + 涉及实现这种策略的细节。) + + + + + 塔的另一个优点在于,我们可以轻松实现这样一种概念:每种类型继承了其超类型所定义的所有操作。例如,如果我们没有为整数提供一个专门的 + + 过程 + 函数 + + 来求取其实部,我们仍然应当期望 + + real-part + real_part + + 能够根据整数是复数子类型这一事实而对整数进行定义。在一个塔结构中,我们可以通过修改 + + apply-generic. + apply_generic. + + 的方式,使这一过程统一起来。如果所需的操作没有直接在给定对象的类型中定义,我们就会将该对象提升到其超类型后再试一次。这样,我们就沿着塔向上爬行,在转换参数的过程中,直到找到一个可以执行所需操作的层级,或者到达塔顶(此时我们便放弃)。 + + + + + 类型降级 + 与更一般的层次结构相比,塔的另一个优点在于它为我们提供了一种简单的方法,将数据对象“降级”为最简单的表示形式。例如,如果我们将 $2+3i$$4-3i$ 相加,我们希望得到的结果是整数 6,而不是复数 $6+0i$。 + 练习讨论了一种实现这种降级操作的方法。(诀窍在于,我们需要一种通用的方法来区分那些可以降级的对象,如 $6+0i$,与那些不能降级的对象,如 $6+2i$。) + + + + + + 层次结构的不足 + + + + hierarchy of typesinadequacy of + + + 如果我们的系统中的数据类型能自然地排成一座塔,那么,正如我们所见,这将极大地简化对不同类型实施通用操作时遇到的问题。不幸的是,通常并非如此。 图展示了一种更为复杂的混合类型排列方式,此图显示了不同几何图形类型之间的关系。 我们看到,通常情况下, + type(s)multiple subtype and supertype + supertypemultiple + subtypemultiple + 一种类型可能拥有不止一个子类型。例如,三角形和四边形都为多边形的子类型。此外,一种类型还可能拥有多个超类型。例如,一个等腰直角三角形既可看作是等腰三角形,也可看作是直角三角形。 这种多重超类型的问题尤其棘手,因为这意味着在层次结构中没有唯一的方法将一种类型“提升”(raise)。 在一个 + + procedure + function + + 中,为对象寻找适用某操作的“正确”(correct)超类型可能需要在整个类型网络中进行大量搜索,例如 + + apply-generic. + apply_generic. + + 而由于通常存在一个类型对应多个子类型的情况,将一个值在类型层次中“降级”(down)时也会遇到类似问题。 处理大量相互关联的类型,并同时保持大型系统设计的模块化,是非常困难的,这也是当前的研究热点之一.这一论断在本书第一版中亦有出现,其真理性至今未变,就如我们在 + + twelve years ago. + in 1984. + + 时所写的一样。 + 实现一个有用的、通用的框架,以表达不同类型实体之间的关系(哲学家称之为“ontology”,即本体论)似乎难以攻克。 混乱的主要差别在于,十年前存在的困境 + + ten years ago + in 1984 + + 与现今的困境在于,如今各种不足的本体论理论已体现在大量相应不足的编程语言中。例如,正如参考文献中所示,object-oriented programming languages + programming languageobject-oriented + 面向对象编程语言citeturn0file10citeturn0file14——以及当代面向对象语言之间那些微妙而令人困惑的差异——核心问题都在于如何处理相互关联类型上的通用操作。 我们在第章中关于计算对象的讨论则完全规避了这些问题。 熟悉面向对象编程的读者会注意到,我们在第章中对局部状态有大量论述,但我们甚至没有提及“classes”或“inheritance”。 实际上,我们怀疑仅靠计算机语言设计是无法充分解决这些问题的,还必须借助于知识表示和自动推理等领域的研究。 + + + +
    +
    + 几何图形类型之间的关系 + +
    + + + + + + apply_generic + 使用强制转换 + + Louis Reasoner已注意到, + + + apply-generic + + + apply_generic + + + 可能会尝试将参数强制转换为对方的类型,即使它们本来就具有相同的类型。因此,他推断,我们需要将 + + 过程 + 函数 + + 放入强制转换表中,以将每种类型的参数强制转换为其自身的类型。例如,除了上面显示的 + + + scheme-number->complex + + + javascript_number_to_complex + + + 的强制转换外,他还将执行以下操作: + + javascript_number_to_javascript_ number + complex_to_complex + +(define (scheme-number->scheme-number n) n) +(define (complex->complex z) z) +(put-coercion 'scheme-number 'scheme-number + scheme-number->scheme-number) +(put-coercion 'complex 'complex complex->complex) + + +function javascript_number_to_javascript_number(n) { return n; } + +function complex_to_complex(n) { return n; } + +put_coercion("javascript_number", "javascript_number", + javascript_number_to_javascript_number); +put_coercion("complex", "complex", complex_to_complex); + + + + +
      +
    1. + 在安装了Louis的强制转换 + + 过程 + 函数 + + 后,如果调用 + + apply-generic + apply_generic + + 并传入两个参数,其类型为 + + scheme-number + "complex" + + 或传入两个参数,其类型为 + + complex + "javascript_@number" + + 用于执行一个在对应类型表中未找到的操作,会发生什么情况?例如,假设我们已经定义了一个泛用的求幂运算: + + apply_generic + +(define (exp x y) (apply-generic 'exp x y)) + + +function exp(x, y) { + return apply_generic("exp", list(x, y)); +} + + + 并且在 + + Scheme-number + JavaScript-number + + 包中放置了一个用于求幂的 + + 过程 + 函数 + + ,而在其它包中则没有放置: + + +;; following added to Scheme-number package +(put 'exp '(scheme-number scheme-number) + (lambda (x y) (tag (expt x y)))) ; using primitive expt + + + // following added to JavaScript-number package +put("exp", list("javascript_number", "javascript_number"), + (x, y) => tag(math_exp(x, y))); // using primitive $\texttt{math\char`_exp}$ + + + 那么,如果我们使用两个复数作为参数来调用 exp 会怎样? +
    2. +
    3. + Louis是否正确,即必须对相同类型的参数进行强制转换的处理?或者说, + + apply-generic + apply_generic + + 本来就该能够正确运行? +
    4. +
    5. + 修改 + + apply-generic + apply_generic + + ,使得当两个参数具有相同类型时,不再尝试进行强制转换。 +
    6. +
    + + +
      +
    1. 如果Louis将强制转换函数放入操作表中,apply_generic将进入无限循环
    2. +
    3. Louis的代码不起作用。apply_generic照现有方式运行是正确的,但我们可以修改它,使其在尝试任何相同类型的强制转换之前便以错误退出。
    4. +
    5. + + apply_generic_with_unavailable_type_example + base_operation_table + complex_number_package + coercion_support + + + function install_javascript_number_package() { + function tag(x) { + return attach_tag("javascript_number", x); + } + put("add", list("javascript_number", "javascript_number"), + (x, y) => tag(x + y)); + put("sub", list("javascript_number", "javascript_number"), + (x, y) => tag(x - y)); + put("mul", list("javascript_number", "javascript_number"), + (x, y) => tag(x * y)); + put("div", list("javascript_number", "javascript_number"), + (x, y) => tag(x / y)); + put("exp", list("javascript_number", "javascript_number"), + (x, y) => tag(math_exp(x, y))); + put("make", "javascript_number", + x => tag(x)); + return "done"; + } + install_javascript_number_package(); + + function make_javascript_number(n) { + return get("make", "javascript_number")(n); + } + + function javascript_number_to_javascript_number(n) { + return n; + } + function complex_to_complex(n) { + return n; + } + put_coercion("javascript_number", "javascript_number", + javascript_number_to_javascript_number); + put_coercion("complex", "complex", + complex_to_complex); + function exp(x, y) { + return apply_generic("exp", list(x, y)); + } + + const c = make_javascript_number(4); + const d = make_javascript_number(2); + exp(c, d); + + + + + + apply_generic_with_unavailable_type + apply_generic_with_unavailable_type_example + + + + function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + if (! is_undefined(fun)) { + return apply(fun, map(contents, args)); + } else { + if (length(args) === 2) { + const type1 = head(type_tags); + const type2 = head(tail(type_tags)); + const a1 = head(args); + const a2 = head(tail(args)); + const t1_to_t2 = get_coercion(type1, type2); + const t2_to_t1 = get_coercion(type2, type1); + return type1 === type2 + ? error(list(op, type_tags), + "no method for these types") + : ! is_undefined(t1_to_t2) + ? apply_generic(op, list(t1_to_t2(a1), a2)) + : ! is_undefined(t2_to_t1) + ? apply_generic(op, list(a1, t2_to_t1(a2))) + : error(list(op, type_tags), + "no method for these types"); + } else { + return error(list(op, type_tags), + "no method for these types"); + } + } + } + + +
    6. +
    +
    + +
    + + + + apply_generic多参数强制转换 + 展示如何将 + + apply-generic + apply_generic + + + 推广为处理多个参数的一般情况中的强制转换。一种策略是尝试将所有参数首先强制转换为第一个参数的类型,然后转换为第二个参数的类型,以此类推。请给出一个示例,说明这种策略(以及上面给出的针对两个参数版本的策略)在某些情况下不够通用。 + (提示:考虑存在一些合适的混合类型操作,但这些操作不会被尝试的情况。) + + + + multi_coercion_example + base_operation_table + javascript_number_package + complex_number_package + coercion_support + +function javascript_number_to_complex(n) { + return make_complex_from_real_imag(contents(n), 0); +} + +put_coercion("javascript_number", "complex", + javascript_number_to_complex); + +put("add", list("complex", "complex", "complex"), + (x, y, z) => attach_tag("complex", make_complex_from_real_imag( + real_part(x) + real_part(y) + real_part(z), + imag_part(x) + imag_part(y) + imag_part(z)))); + +function add_three(x, y, z) { + return apply_generic("add", list(x, y, z)); +} + +const c = make_complex_from_real_imag(4, 3); +const n = make_javascript_number(7); +add_three(c, c, n); +// add_three(c, n, n); + + + + + + multi_coercion + multi_coercion_example + +function can_coerce_to(type_tags, target_type) { + return accumulate((type_tag, result) => + result && + (type_tag === target_type || + ! is_undefined(get_coercion(type_tag, target_type))), + true, + type_tags); +} + +function find_coerced_type(type_tags) { + return is_null(type_tags) + ? undefined + : can_coerce_to(type_tags, head(type_tags)) + ? head(type_tags) + : find_coerced_type(tail(type_tags)); +} + +function coerce_all(args, target_type) { + return map(arg => type_tag(arg) === target_type + ? arg + : get_coercion(type_tag(arg), target_type)(arg), + args); +} + +function apply_generic(op, args) { + const type_tags = map(type_tag, args); + const fun = get(op, type_tags); + if (! is_undefined(fun)) { + return apply(fun, map(contents, args)); + } else { + const target_type = find_coerced_type(type_tags); + if (! is_undefined(target_type)) { + return apply_generic(op, coerce_all(args, target_type)); + } else { + return error(list(op, type_tags), + "no method for these types"); + } + } +} + + + 一种这种方法不充分的情况是:若存在三种类型,A、B、C,其中A可以强制转换为B且C可以强制转换为B,并且存在针对(A, B, B)注册的操作。对(A, B, C)求值时,只会尝试(A, B, C)和(B, B, B)这两种情况,而实际上你可以将C强制转换为B,从而使用(A, B, B)的注册操作。 + + + + + + + type(s)raising + 假设你正在设计一个用于处理图所示的类型塔(整数、分式、实数、复数)的通用运算系统。对于每种类型(除复数外),设计一个 + + 过程 + 函数 + + ,使其能将该类型对象在类型塔中提升一级。请展示如何安装一个通用的raise操作,使其对于每种类型(除复数外)均能正常工作。 + + + + rational_number_package + +function gcd(a, b) { + return b === 0 ? a : gcd(b, a % b); +} + +function install_rational_package() { + // internal functions + function numer(x) { + return head(x); + } + function denom(x) { + return tail(x); + } + function make_rat(n, d) { + let g = gcd(n, d); + return pair(n / g, d / g); + } + function add_rat(x, y) { + return make_rat(numer(x) * denom(y) + + numer(y) * denom(x), + denom(x) * denom(y)); + } + function sub_rat(x, y) { + return make_rat(numer(x) * denom(y) - + numer(y) * denom(x), + denom(x) * denom(y)); + } + function mul_rat(x, y) { + return make_rat(numer(x) * numer(y), + denom(x) * denom(y)); + } + function div_rat(x, y) { + return make_rat(numer(x) * denom(y), + denom(x) * numer(y)); + } + // interface to rest of the system + function tag(x) { + return attach_tag("rational", x); + } + put("add", list("rational", "rational"), + (x, y) => tag(add_rat(x, y))); + put("sub", list("rational", "rational"), + (x, y) => tag(sub_rat(x, y))); + put("mul", list("rational", "rational"), + (x, y) => tag(mul_rat(x, y))); + put("div", list("rational", "rational"), + (x, y) => tag(div_rat(x, y))); + put("make", "rational", + (n, d) => tag(make_rat(n, d))); +} +install_rational_package(); + +function make_rational(n, d) { + return get("make", "rational")(n, d); +} + + + + + javascript_number_package + rational_number_package + complex_number_package + coercion_support + apply_generic_with_coercion + raise_example + +const a = make_javascript_number(2); +const b = make_rational(2, 3); +raise(a); +// raise(b); + + + + + raise + raise_example + +function raise(x) { + return apply_generic("raise", list(x)); +} + +// 添加到 Javascript-number 包 +put("raise", list("javascript_number"), + x => make_rational(x, 1)); +//// 添加到 rational 包 +// put("raise", list("rational"), +// x => make_real(div, numer(x), denom(x))); +// put("raise", list("rational"), +// x => make_complex_from_real_imag(x, 0)); +//// 添加到 real 包 +// put("raise", list("real"), +// x => make_complex_from_real_imag(x, 0)); + + + + + + + + + apply_generic通过提升进行强制转换 + 使用练习中的raise运算,修改 + + apply-generic + apply_generic + + + 过程 + 函数 + + ,使其通过连续提升的方法将参数强制转换为相同的类型,如本节所讨论的。你需要设计一种方法来测试两个类型中哪一个在类型塔中更高。请以与系统其他部分兼容的方式实现这一点,并且不会在向塔中添加新级别时引发问题。 + + + + + + + apply_generic通过强制转换来简化 + 类型降低 + 本节提到了一种通过在类型塔中尽可能降低数据对象来简化其的方法。设计一个 + 过程 + 函数 + + drop,用于实现exercise中所描述的类型塔的这一目标。关键在于以某种通用的方式判断一个对象是否可以降低。例如,复数< латексINLINE>$1.5+0i$</LATEXINLINE>可以降低到 + real, + "real", + + 的层次,复数< LATEXINLINE>$1+0i$</LATEXINLINE>可以降低到 + integer, + "integer", + + 的层次,而复数< LATEXINLINE>$2+3i$</LATEXINLINE>则完全无法降低。以下是判断一个对象是否能够降低的方案:首先定义一个通用操作project,该操作将对象在类型塔中下推。例如,对复数进行投影将涉及丢弃其虚部。接着,当我们对一个数进行project操作,并用raise将结果提升回原来的类型后,如果最终得到的值与初始值相等,则该数可以被降低。请详细说明如何实现这一思想,方法是编写一个drop</SCHEMEINLINE> + 过程 + 函数 + + 来使对象尽可能降低。你需要设计各种投影操作一个实数可以通过使用math_round(原始函数) + math_roundMath.round + + + round + + + math_round + + + 原语将其投影到整数,该原语返回最接近其参数的整数。 + 并将project安装为系统中的通用操作。你还需要使用一个通用的相等谓词,如exercise中所描述的那样。最后,利用drop重写来自exercise + apply-generic + apply_generic + ,使其在给出答案时能够简化结果。 + + + + + + 假设我们希望处理复数,其实部、虚部、模和角度可以是普通数字、有理数,或其他我们可能希望加入系统中的数字。描述并实现为适应这一需求所需对系统进行的更改。你将需要定义诸如 sinecosine 这样的操作,这些操作对于普通数字和有理数是通用的。 + + + + 强制类型转换 + 类型层次结构 + 类型的层次结构 + +
    diff --git a/xml/cn/chapter2/section5/subsection3.xml b/xml/zh/chapter2/section5/subsection3.xml similarity index 100% rename from xml/cn/chapter2/section5/subsection3.xml rename to xml/zh/chapter2/section5/subsection3.xml diff --git a/xml/cn/chapter3/chapter3.xml b/xml/zh/chapter3/chapter3.xml similarity index 100% rename from xml/cn/chapter3/chapter3.xml rename to xml/zh/chapter3/chapter3.xml diff --git a/xml/cn/chapter3/section1/section1.xml b/xml/zh/chapter3/section1/section1.xml similarity index 100% rename from xml/cn/chapter3/section1/section1.xml rename to xml/zh/chapter3/section1/section1.xml diff --git a/xml/cn/chapter3/section1/subsection1.xml b/xml/zh/chapter3/section1/subsection1.xml similarity index 100% rename from xml/cn/chapter3/section1/subsection1.xml rename to xml/zh/chapter3/section1/subsection1.xml diff --git a/xml/cn/chapter3/section1/subsection2.xml b/xml/zh/chapter3/section1/subsection2.xml similarity index 100% rename from xml/cn/chapter3/section1/subsection2.xml rename to xml/zh/chapter3/section1/subsection2.xml diff --git a/xml/cn/chapter3/section1/subsection3.xml b/xml/zh/chapter3/section1/subsection3.xml similarity index 100% rename from xml/cn/chapter3/section1/subsection3.xml rename to xml/zh/chapter3/section1/subsection3.xml diff --git a/xml/zh/chapter3/section2/section2.xml b/xml/zh/chapter3/section2/section2.xml new file mode 100644 index 000000000..01b82ee32 --- /dev/null +++ b/xml/zh/chapter3/section2/section2.xml @@ -0,0 +1,252 @@ +
    + The Environment Model of Evaluation + + + + + environment model of evaluation + + + + 当我们在第章中介绍复合 + + 过程 + 函数 + + 时,我们采用了(参见第节) + 过程应用的替换模型 + 来定义将 + + 过程 + 函数 + + 应用于参数时的含义: +
      +
    • 要将复合 + + 过程 + 函数 + + 应用于参数,应求值 + + 过程的主体 + 函数的返回表达式(更一般地说,是主体) + + ,其中每个 + + 形式参数 + + 被相应的参数替换。 +
    • +
    +
    + + + + + 一旦我们在编程语言中引入赋值,这样的定义便不再充分。特别是,第节论证了,在存在赋值的情况下, + + + 变量不能仅仅被视为值的名字,而必须以某种方式指示一个可以存储值的位置。 + + + 名字不能仅仅被看作代表一个值,而必须以某种方式指示一个可以存储值的位置。 + + + 在我们新的求值模型中,这些位置将被维护在称为environmentenvironments的结构中。 + + + + + + 环境是一系列的 + 帧(环境模型) + 。 每个帧是一个表(可能为空),包含 + 绑定 + 绑定,它们将 + + + 变量名 + + + 名字 + + + 与它们对应的值相关联。 + + + (单个帧中每个变量最多只能有一个绑定。) + + + (单个帧中每个名字最多只能有一个绑定。) + + + 每个帧还包含一个指向其 + 封闭环境 + environmentenclosing + 封闭环境 的指针,除非在讨论时该帧被视为 + 全局帧 + frame (environment model)global + 全局。 在某个环境中, + + + 变量的值 + + + 名字value of + 名称的值 + + + 是由在环境中第一个包含该 + + + 变量 + + + 名字 + + + 绑定的帧中给定的值。如果这一序列中的任何帧都没有为该 + + + 变量, + + + 名字, + + + 指定绑定,那么该 + + + 变量 + + + 名字 + + + 在该环境中被称为 + unbound name + nameunbound + unbound。 + + +
    +
    + + 一个简单的环境结构。 +
    +
    + +
    +
    + + + 一个简单的 + 求值的环境模型environment structure + 环境结构。 + +
    +
    +
    +
    + + + + + + + 图 + + + 图 + + + 展示了一个简单的环境结构,由三个帧组成,分别标记为 I、II 和 III。在图中,A、B、C 和 D 是指向环境的指针。C 和 D 指向相同的环境。 + + + 变量 + + + 名字 + + + zx 在帧 II 中被绑定,而 yx 在帧 I 中被绑定。在环境 D 中 x 的值为 3,相对于环境 B,x 的值也为 3。具体确定方式如下:我们检查序列中的第一个帧(帧 III),未找到 x 的绑定,因此转而检查其封闭环境 D,并在帧 I 中找到了该绑定。另一方面,在环境 A 中 x 的值为 7,因为序列中的第一个帧(帧 II)包含了将 x 绑定到 7 的绑定。就环境 A 而言,帧 II 中将 x 绑定到 7 的绑定被称为 + 遮蔽绑定 + 遮蔽 + 帧 I 中将 x 绑定到 3 的绑定。 + + + + + + 环境对于求值过程至关重要,因为它决定了一个表达式应当在何种上下文中被求值。事实上,可以说编程语言中的表达式本身没有任何意义。相反,一个表达式只有在某个环境中求值时才获得意义。 + + + 甚至对于像 (+ 1 1) 这样简单明了的表达式,其解释也取决于这样一种理解:人们正在一个上下文中操作,在这个上下文中 + 被视为加法的符号。 + + + 甚至对于像 display(1) 这样简单明了的表达式,其解释也取决于这样一种理解:人们正在一个上下文中操作,在这个上下文中名称 display 指的是用于显示值的原始函数。 + + + 因此,在我们的求值模型中,我们总是讨论相对于某个环境来求值表达式。为了描述与解释器的交互,我们假定存在一个 + global environment + 全局环境,该全局环境由单一帧(没有封闭环境)组成,其中包含与 + + + 符号 + + + 名称 + + + 相关联的原始 + + + 过程。 + + + 函数。 + + + 的值。例如,将 + + + + 被视为加法符号的观点 + + + display 被视为原始显示函数名称的观点 + + + 表现为:在全局环境中,符号 +(或名称 display)被绑定到原始 + + 加法过程。 + 显示函数。 + + + + + + + + 在求值程序之前,我们在全局环境中扩展了一个新的帧,称为程序帧,从而构成了程序环境 程序环境。我们会将程序顶层(即任何块之外)声明的名称加入到该帧中。随后,在程序环境下对给定的程序进行求值。 + + + + + + + &subsection3.2.1; + + + &subsection3.2.2; + + + &subsection3.2.3; + + + &subsection3.2.4; + + + &subsection3.2.5; + +
    diff --git a/xml/zh/chapter3/section2/subsection1.xml b/xml/zh/chapter3/section2/subsection1.xml new file mode 100644 index 000000000..b64028fa2 --- /dev/null +++ b/xml/zh/chapter3/section2/subsection1.xml @@ -0,0 +1,552 @@ + + + 评估规则 + + + + + + + + The overall specification of how the interpreter + function applicationenvironment model of + environment model of evaluationfunction application + evaluates a + + + combination + + + function application + + + remains the same as when we first introduced it in + section: +
      +
    • + To evaluate + + + a combination: + + + an application: + + +
        +
      1. + Evaluate the subexpressions + of the + + + combination.Assignment introduces a subtlety into step + 1 of the evaluation rule. As shown in + exercise, the + presence of assignment allows us to write expressions that will + produce different values depending on the + order of evaluationimplementation-dependent + implementation dependenciesorder of subexpression evaluation + order in which the subexpressions in a combination + are evaluated. Thus, to be precise, we should specify an + evaluation order in step 1 (e.g., left to right or right to + left). However, this order should always be considered to be an + implementation detail, and one should never write programs that + depend on some particular order. For instance, a sophisticated + compiler might optimize a program by varying the order in which + subexpressions are evaluated. The ECMAScript standard specifies + evaluation of subexpressions from left to right. + + + application.Assignment introduces a subtlety + into step 1 of the evaluation rule. As shown in + exercise, the + presence of assignment allows us to write expressions that will + produce different values depending on the order in which the + subexpressions in a combination + are evaluated. To eliminate such ambiguities, + order of evaluationJavaScriptin JavaScript + JavaScript specifies left-to-right evaluation of the + subexpressions of combinations and of the argument expressions + of applications. + + +
      2. +
      3. + Apply the value of the + + + operator + + + function + + + subexpression + to the values of the + + + operand + + + argument + + + subexpressions. +
      4. +
      +
    • +
    + The environment model of evaluation replaces the substitution model in + specifying what it means to apply a compound + + procedure + function + + to arguments. +
    + + + + 在求值的环境模型中,一个 + + 过程 + 函数 + + 总是由一段代码和一个指向环境的指针构成的一对。 + + 过程 + 函数 + + 只通过一种方式创建:通过求值一个 + + lambda + lambda + + 表达式。 + 通过 + + lambda + lambda 表达式 + + 创建 + 这产生了一个 + + 过程 + 函数 + + ,其代码取自于 + + lambda + lambda + + 表达式的文本,而其环境则是评估该 + + lambda + lambda + + 表达式以生成该 + + 过程 + 函数 + + 时所在的环境。 + 例如,考虑下面的 + + 过程定义 + 函数声明 + + square在环境模型中的环境 + + square_example + 196 + +(define (square x) + (* x x)) + + +function square(x) { + return x * x; +} + + + 在 + + + global + + + program + + + 环境中进行求值。该 + + 过程定义 + 函数声明 + + 语法 + + 只是语法糖,相当于 + 等同于 + + 一个底层隐含的 + + lambda + lambda + + 表达式。使用下面的方式也会得到等价的结果 + 脚注 第一章中提到,在完整的 JavaScript 中二者存在细微差别,但在本书中我们将忽略这些差别。 + + square_example + 196 + +(define square + (lambda (x) (* x x))) + + +const square = x => x * x; + + + 这一步评估了 + + (lambda (x) (* x x)) + + + x => x * x + + </SPLITINLINE> + 并将 square 绑定到所得值上,所有这些都发生在 + + + global + + + program + + + 环境中。 + + + + + + + + 图 + + + 图 + + + 显示了计算这个 + 声明环境模型的 + + define 表达式。 + 声明语句。 + + + + + + 全局环境包含程序环境。为减少冗杂,在这幅图之后我们将不再显示全局环境(因为它始终保持不变),但程序环境向上的指针提醒了我们它的存在。 + + + 该 + + 过程 + 函数 + + 对象是一对,其代码指明该 + + 过程 + 函数 + + 具有一个 + 形式参数 + ,即 x,以及一个 + + 过程体 + 函数体 + + + (* x x) + return x * x; + + 该 + + 过程 + 函数 + + 的环境部分是一个指向程序环境的指针,因为正是在该环境中对 + + lambda + lambda + + 表达式求值生成了该 + + 过程。 + 函数。 + + 一个新的绑定已被添加到程序帧中,它将该 + + 过程 + 函数 + + 对象与 + + + 符号 + + + 名称 + + + square 关联。 + + + 通常,define 通过向帧中添加绑定来创建定义。 + + + + + + + +
    +
    + + + 在全局环境中计算 (define (square x) (* x x)) 所产生的环境结构。 + +
    +
    + +
    +
    + + + 在程序环境中计算 function square(x) { return x * x; } 所产生的环境结构。 + +
    +
    +
    +
    + + + + + + + + + 一般来说,constfunctionlet 会向帧中添加绑定。常量禁止赋值,因此我们的环境模型需要区分指向常量的名称与指向变量的名称。我们通过在名称后跟的冒号之后写上等号来表示该名称是常量。我们将函数声明视为等同于常量声明;我们在第 1 章的脚注中提到,完整的 JavaScript 语言允许对通过函数声明声明的名称进行赋值。请注意图中冒号后面的等号。 + + + + + + 既然我们已经了解了如何创建 + + 过程 + 函数 + + ,那么我们就可以描述如何应用 + + 过程 + 函数 + + 。环境模型规定:要将一个 + + 过程 + 函数 + + 应用于参数,需创建一个含有帧的新环境,该帧将形式参数绑定到实际参数的值上。这个帧的外围环境就是由 + + 过程. + 函数. + + 所规定的环境。现在,在这个新环境中,对 + + 过程 + 函数 + + 的函数体进行求值。 + + + +为了展示如何遵循这一规则, + + + 图 + + + 图 + + +展示了通过求值表达式所创建的环境结构 + + (square 5) + square(5) + +在 + 全局 + 程序 + +环境中,其中square</SCHEMEINLINE>是通过 + + + 图. + + + 图. + + +生成的 + 过程 + 函数 + +。应用该 + 过程 + 函数 + +会创建一个新的环境(图中标记为 E1),该环境以一个帧开始,在这个帧中 + x + +,即作为 + 形式参数 + +对 + 过程, + 函数, + +的参数被绑定为数值 5。 + + + 注意,在环境 E1 中,名称 x 后面跟有冒号而没有等号,这表明参数 x 被视为一个变量。本例未利用参数 x 为变量的特性,但请回忆章节中函数 make_withdraw,它正是依赖于其参数作为变量的特性。 + + +从该帧向上延伸的指针表明,该帧的外围环境为 + + 全局 + 程序 + +环境。此 + 全局 + 程序 + +环境之所以被选择,是因为它被指定为 + square + + + 过程 + 函数 + +对象的一部分。在 E1 中,我们对 + 过程, + 函数, + + + (* x x). + return x * x;. + +的函数体进行求值。由于在 E1 中 x 的值为 5,因此结果为 + + (*55), + 5 * 5, + +即 25。 +square环境在环境模型中 + + +
    +
    + + + 在全局环境中求值(square 5)所创建的环境。 + +
    +
    + +
    +
    + + + 在程序环境中求值square(5)所创建的环境。 + +
    +
    +
    +
    + + + 过程/函数应用的环境模型可以用两条规则来概括: +
      +
    • + 一个 + 过程 + 函数 + 对象通过构建一个框架来应用于一组参数, + + 将过程的形式参数绑定 + 将函数的参数绑定 + 到调用的参数上,然后在新构造的环境中求值该 + + 过程 + 函数 + 的函数体。新帧的外围环境是被应用的 + + 过程 + 函数 + 对象的环境部分。该应用的结果是求值函数体时遇到的第一个返回语句的返回表达式的值。 +
    • +
    • + 通过在给定环境下求值 + lambda + lambda 表达式 + 表达式来创建一个 + 过程 + 函数 + 。生成的 + lambda 表达式 + + 过程 + 函数 + 对象是一个对,该对由 + lambda + lambda + 表达式的文本和一个指向创建该 + + 过程 + 函数 + 的环境的指针组成。 +
    • +
    +
    + + + + + defineenvironment model of + 我们还规定,使用define定义符号会在当前环境帧中创建一个绑定,并将所指示的值赋给该符号。 + + + + 我们在JavaScript版本中省略这一点,因为在节中有更详细的讨论。 + + + + + + 在某个环境中求值表达式(set! variable value)将定位该环境中该变量的绑定。为此,需要在环境中找到第一个包含该变量绑定的帧并修改该帧。如果该变量在环境中未被绑定,则set!会发出错误信号。 + + + assignmentevaluation of + 最后,我们规定赋值的行为,这一操作正是促使我们最初引入环境模型的原因。在某个环境中求值表达式name=value会定位该名称在环境中的绑定。也就是说,需要找到环境中第一个包含该名称绑定的帧。如果该绑定是变量绑定——在名称后仅用:指示,则该绑定将被修改以反映变量的新值。否则,如果该帧中的绑定是常量绑定——在名称后由:=指示,则此次赋值会触发"assignment to constant"错误。如果该名称在环境中未被绑定,则赋值会触发"variable undeclared"错误。 + + + + + + 这些求值规则虽然比替换模型复杂得多,但仍相当直观。此外,尽管求值模型较为抽象,但它正确描述了解释器如何求值表达式。在章中,我们将看到该模型如何作为构建可运行解释器的蓝图。以下各节通过分析一些示例程序详细阐述了该模型的细节。 + environment model of evaluationrules for evaluation + +
    diff --git a/xml/zh/chapter3/section2/subsection2.xml b/xml/zh/chapter3/section2/subsection2.xml new file mode 100644 index 000000000..00e2a3a33 --- /dev/null +++ b/xml/zh/chapter3/section2/subsection2.xml @@ -0,0 +1,439 @@ + + + + 应用简单 + + 过程 + 函数 + + + + + + + 求值环境模型 + + + 过程 + 函数 + + -应用实例 + + + + + + 函数应用 + 环境模型的 + + + + + + sum_of_squaresenvironment在环境模型中 + 当我们在节中介绍替换模型时,我们展示了如何通过 + + 组合 (f 5) + 调用 + f(5) + + 计算出136,给定如下 + + 过程定义: + 函数声明: + + + f_example + 136 + +(define (square x) + (* x x)) + +(define (sum-of-squares x y) + (+ (square x) (square y))) + +(define (f a) + (sum-of-squares (+ a 1) (* a 2))) + + +function square(x) { + return x * x; +} +function sum_of_squares(x, y) { + return square(x) + square(y); +} +function f(a) { + return sum_of_squares(a + 1, a * 2); +} + + + 我们可以使用环境模型来分析这个例子。 + 图显示了通过计算 + 过程 + 函数 + + fsquare以及 + + sum-of-squares + sum_of_squares + + 的定义在 + + 全局 + 程序 + + 环境中创建的三个对象。每个 + + 过程 + 函数 + + 对象都由一些代码和指向 + + 全局 + 程序 + + 环境的指针组成。 + + +
    +
    + + 全局环境中的过程对象。 +
    +
    + +
    +
    + + 程序环境中的函数对象。 +
    +
    +
    +
    + + + + + + <!-- 图已移动至后文以适应 SICP JS 分页 --> + <!-- 该图的代码在本文件后面的 PDF_ONLY 部分重复出现 --> + + +
    +
    + + + 通过求值 + (f 5) + 并使用图中所示的过程创建的环境。 + +
    +
    + +
    +
    + + + 通过求值 + f(5) + 并使用图中所示的函数创建的环境。 + +
    +
    +
    +
    + + 在 + + + + 中,我们可以看到通过求值表达式 + + (f 5). + f(5). + + 所创建的环境结构。对 f 的调用创建了一个新的环境 E1,该环境以一个包含绑定了实参 5 的框架开始,其中 a形式参数。 在 E1 中,我们求值 f 的函数体: + + +(sum-of-squares (+ a 1) (* a 2)) + + +return sum_of_squares(a + 1, a * 2); + + + 为了求值 + + + 这个组合,我们首先计算子表达式。 + + + 对于 return 语句,我们首先计算返回表达式的子表达式。 + + + 第一个子表达式, + + sum-of-squares, + sum_of_squares, + + 的值是一个 + + 过程 + 函数 + + 对象。(注意这个值是如何被找到的:我们首先在 E1 的第一个框架中查找,其中没有 + sum-of-squares. + sum_of_squares. + + 的绑定。然后,我们进入封闭的环境,即 + + 全局 + 程序 + + 环境,在 + + + 图.) + + + 图.) + + + 中找到该绑定。 + 另外两个子表达式通过应用基本运算 +* + 来计算两个组合 + + (+ a 1) + a + 1 + + 和 + + (* a 2) + a * 2 + + 分别得到 6 和 10。 +
    + + + + + 现在我们应用 + 过程 + 函数 + + 对象 + sum-of-squares + sum_of_squares + + + 到实参6和10。这会产生一个新环境E2,在该环境中, + 形式参数formal + xy被绑定到这些实参上。在E2中,我们求值 + + + 组合 + (+ (square x) (square y))。 + + + 语句 + + +return square(x) + square(y); + + + + + 这使得我们必须求值 + + (square x), + square(x), + + 其中square是在 + + 全局 + 程序 + + 框架中找到的,而x的值为6。 再次,我们建立一个新环境E3,在该环境中x被绑定为6,并在该环境中求值square的主体,即 + + (* x x). + return x * x;. + + 同时,在应用 + sum-of-squares, + sum_of_squares, + + + 的过程中,我们必须求值子表达式 + + (square y), + square(y), + + 其中y的值为10。对square的第二次调用创建了另一个环境E4,在该环境中, + x,即形式 + 参数square的绑定,被设为10。在E4中,我们必须求值 + + (* x x). + return x * x;. + + + + + + + 需要注意的重要一点是,每次调用 + square + 都会创建一个包含x + 的绑定的新环境。我们可以看到,不同的帧如何将所有名为 + x + 的局部变量分开。请注意,每个由 + square + 创建的帧都指向 + + global + program + + 环境,因为这是由 + square + + procedure + function + + 对象所指示的环境。 + + + + + + 子表达式求值后,其结果被返回。由对 square 的两次调用生成的值通过 + + sum-of-squares, + sum_of_squares, + + + 相加,并且该结果由 f 返回。由于我们在此的重点是环境结构,因此不详细讨论这些返回值如何在调用之间传递;然而,这也是求值过程中的一个重要方面,我们将在第章中详细讨论. + sum_of_squaresenvironment在环境模型中 + + + + + + + 在一节中, + 我们使用替换模型来分析两种 + 递归过程与迭代过程对比 + 迭代过程与递归过程对比 + + 过程 + 函数 + + 来计算 + factorial求值中的环境结构 + 阶乘,一个递归版本 + + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* n (factorial (- n 1))))) + + +function factorial(n) { + return n === 1 + ? 1 + : n * factorial(n - 1); +} + + + 和一个迭代版本 + + factorial_example + 120 + +(define (factorial n) + (fact-iter 1 1 n)) + +(define (fact-iter product counter max-count) + (if (> counter max-count) + product + (fact-iter (* counter product) + (+ counter 1) + max-count))) + + +function factorial(n) { + return fact_iter(1, 1, n); +} +function fact_iter(product, counter, max_count) { + return counter > max_count + ? product + : fact_iter(counter * product, + counter + 1, + max_count); +} + + + 展示使用每个版本的 factorial + + 过程 + 函数 + + 求值 + + (factorial 6) + factorial(6) + + 时创建的环境结构。 + 环境模型不会阐明我们在一节中提出的声明, + 即解释器可以通过尾递归在常量空间内执行 + + 过程 + 函数 + + ——例如 + + fact-iter + fact_iter + 。当我们在一节中讨论解释器的控制结构时, + 我们将讨论 + 求值环境模型与尾递归相关 + 尾递归与求值环境模型相关 + 的内容。 + + + + + 求值环境模型过程函数-应用实例 + + 函数应用环境模型的 + + + <!-- 图形为了 SICP JS 分页而从前面移至此处 --> + <!-- 图形代码是此文件中 WEB_ONLY 部分早期代码的副本 --> + + +
    +
    + + + 通过求值生成的环境 + (f 5), + 使用图中的过程。 + +
    +
    + +
    +
    + + + 通过求值生成的环境 + f(5), + 使用图中的函数。 + +
    +
    +
    +
    + +
    diff --git a/xml/zh/chapter3/section2/subsection3.xml b/xml/zh/chapter3/section2/subsection3.xml new file mode 100644 index 000000000..b45d9f4ba --- /dev/null +++ b/xml/zh/chapter3/section2/subsection3.xml @@ -0,0 +1,625 @@ + + + Frames 作为局部状态的存储库 + + + + 帧(环境模型)作为局部状态的存储库 + 局部状态在帧中维护 + 求值环境模型局部状态 + + + 我们可以借助环境模型来看 + + 过程 + 函数 + + 和赋值如何被用来表示具有局部状态的对象。例如,考虑 + make_withdrawenvironment在环境模型中 + 提款处理器 + 来自章节,通过调用 + + 过程 + 函数 + + 创建的 + + make_withdraw2 + +(define (make-withdraw balance) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds"))) + + +function make_withdraw(balance) { + return amount => { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "insufficient funds"; + } + }; +} + + + 下面我们描述对 + + make_withdraw2 + make_withdraw2_w1_declare + +(define W1 (make-withdraw 100)) + + +const W1 = make_withdraw(100); + + + 的求值,随后 + + make_withdraw2_w1_example + make_withdraw2_w1_declare + 50 + +(W1 50) + + + 50 + + +W1(50); + + +50 + + + + + Figure + + + Figure + + + 显示了 + + 定义 make-withdraw + 声明 make_withdraw + + + 过程 + 函数 + + 在 + + 全局 + 程序 + + 环境中的结果。这产生了一个 + + 过程 + 函数 + + 对象,该对象包含一个指向 + + 全局 + 程序 + + 环境的指针。到目前为止,这与我们之前见过的例子没有什么不同,只不过 + + + 该过程的主体本身就是一个 lambda 表达式。 + + + 该函数主体中的返回表达式本身就是一个 lambda 表达式。 + + + + +
    +
    + + 在全局环境中定义 make-withdraw 的结果。 + + +
    +
    + +
    +
    + + 在程序环境中声明 make_withdraw 的结果。 + + +
    +
    +
    +
    + + + 当我们把 + 过程 + 函数 + + make-withdraw + make_withdraw + + 应用于一个参数时,计算中有趣的部分就会发生: + + +(define W1 (make-withdraw 100)) + + +const W1 = make_withdraw(100); + + + 我们像往常一样开始,先建立一个环境 E1,在该环境中形式参数 + formal + balance 被绑定到参数 100。接着,在这个环境中,我们求值 + + make-withdraw, + + make_withdraw, + + + 的主体,也就是那个 + + lambda 表达式。 + 返回语句,其返回表达式是一个 lambda 表达式。对该 lambda 表达式的求值 + + 构造了一个新的 + + 过程 + 函数 + + 对象,该对象的代码由 + + lambda + lambda 表达式 + + 指定,而其环境为 E1,即求值该 + + lambda + lambda 表达式 + + 所在的环境。调用 + + make-withdraw. + + make_withdraw. + + 返回的值就是这个 + + 过程 + 函数 + + 对象。由于 + + define + 常量声明 + + 本身是在 + + 全局 + 程序 + + 环境中求值的,所以该对象被绑定到全局环境中的 + W1。 + + + Figure + + + Figure + + + 展示了结果环境结构。 + + +
    +
    + + 求值 + (define W1 (make-withdraw 100)) + 的结果。 + + +
    +
    + +
    +
    + + 求值 + const W1 = make_withdraw(100); + 的结果。 + + +
    +
    + </SPLIT> +
    + + + 现在我们可以分析当 W1 + 被应用于一个参数时会发生什么: + + +(W1 50) + + + 50 + + +W1(50); + + +50 + + + 我们首先构造一个帧,其中 amount,也就是 + formal + 参数 W1 被绑定到参数 50。关键之处在于, + 该帧的外部环境不是 + + global + program + + 环境,而是环境 E1,因为这是由 + W1 + + procedure + function + + 对象所指定的环境。在这个新环境中,我们求值 + + procedure: + function: + + 的主体: + + +(if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds") + + +if (balance >= amount) { + balance = balance - amount; + return balance; +} else { + return "insufficient funds"; +} + + + 结果的环境结构如 + + + figure. + + + figure. + + + 所示。被求值的表达式同时引用了 amount + 和 balance。 + + Amount + 变量 amount + + + 将在环境的第一帧中找到,而 balance 则通过跟随指向 E1 的外部环境指针而找到。 + + +
    +
    + + 通过应用过程对象 W1 创建的环境。 + + +
    +
    + +
    +
    + + 通过应用函数对象 W1 创建的环境。 + + +
    +
    + </SPLIT> +
    + + + 当执行 + + set! + 赋值 + + 时,E1 中 balance 的绑定发生了改变。调用 W1 完成后,balance 的值为 50,并且包含 balance 的帧仍然由 + + 过程 + 函数 + + 对象 W1 指向。绑定 amount 的帧(在其中我们执行了修改 balance 的代码)已不再相关,因为构造该帧的 + + 过程 + 函数 + + 调用已终止,且环境的其他部分不再指向该帧。下一次调用 W1 时,将构造一个新帧,该帧绑定 amount,其外部环境依然为 E1。我们看到 E1 作为存放 W1 + + 过程 + 函数 + + 对象局部状态变量的位置。 + + + Figure + + + Figure + + + 显示了调用 W1 之后的情况。 + + +
    +
    + + 调用 W1 后的环境。 + + +
    +
    + +
    +
    + + 调用 W1 后的环境。 + + +
    +
    +
    +
    + + + \pagebreak + + + + + 观察当我们通过再次调用 + + make_withdraw: + make_withdraw: + + + 来创建第二个withdraw对象时会发生什么 + + make_withdraw2 + make_withdraw2_w2_declare + 20 + +(define W2 (make-withdraw 100)) + + +const W2 = make_withdraw(100); + + +const W1 = make_withdraw(100); +W1(50); +const W2 = make_withdraw(100); +W2(80); + + + 这会生成以下的环境结构 + + + figure, + + + figure, + + + 其中显示W2是一个 + + 过程 + 函数 + + 对象,也就是说,一个包含一些代码和环境的对。用于W2的环境E2是由对 + + make-withdraw. + make_withdraw. + + + 的调用所创建的。它包含一个具有自身局部绑定balance的帧。另一方面, + W1W2拥有相同的代码:该代码是由 + + lambda + lambda + + 表达式在 + make-withdrawmake_withdraw + 的主体中指定的。关于W1W2是否共享存储在计算机中的相同物理代码,或者它们各自保留一份代码拷贝,这是实现的细节。对于我们在章节中实现的解释器来说,代码实际上是共享的。 我们在这里可以看到为什么W1W2表现为独立的对象。对W1的调用引用了存储在E1中的状态变量balance,而对W2的调用则引用了存储在E2中的balance。因此,一个对象局部状态的改变不会影响另一个对象。 + + +
    +
    + + 使用(define W2 (make-withdraw 100))创建第二个对象。 + + +
    +
    + +
    +
    + + 使用const W2 = make_withdraw(100);创建第二个对象。 + + +
    +
    +
    +
    + + + + + + + 在 + + make-withdraw + make_withdraw + + + 过程 + 函数 + + 中,局部变量balance作为 + + make-withdraw. + make_withdraw. + + 的参数而创建。我们也可以使用 + + 显式地, + 单独地, + + 来创建局部状态变量,采用 + + let, + 我们可以称之为一个 + lambda 表达式立即调用 + 立即调用的 lambda 表达式 + 立即调用的 lambda 表达式 + + + 如下所示: + + make_withdraw使用立即调用的 lambda 表达式 + make_withdraw3 + make_withdraw3_example + 20 + +(define (make-withdraw initial-amount) + (let ((balance initial-amount)) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")))) + + +function make_withdraw(initial_amount) { + return (balance => + amount => { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "insufficient funds"; + } + })(initial_amount); +} + + + + make_withdraw3_example + +const W1 = make_withdraw(100); +W1(80); + + + + + 回忆一下在章节中提到, + let作为语法糖的语法糖 + let求值模型 + let仅仅是对一个 + + 过程 + 函数 + + 调用的另一种语法形式: + + +(let ((var exp)) body) + + + 被解释为另一种语法形式: + + +((lambda (var) body) exp) + + + + + 外层的 lambda 表达式在求值后立即被调用。它的唯一目的是创建一个局部变量 + balance并将其初始化为initial_amount。 + + + 使用环境模型来分析这个make-withdraw的另一种版本, + 绘制如上所示的图形以说明它们之间的交互: + + make_withdraw3_example_2 + make_withdraw3 + 20 + +(define W1 (make-withdraw 100)) + +(W1 50) + +(define W2 (make-withdraw 100)) + + +const W1 = make_withdraw(100); + +W1(50); + +const W2 = make_withdraw(100); + + +const W1 = make_withdraw(100); +W1(50); +const W2 = make_withdraw(100); +W2(80); + + + 证明这两种版本的 + + make-withdraw + make_withdraw + + 创建了具有相同行为的对象。对于这两个版本,它们的环境结构有何不同? + +
      +
    • 黑色部分展示了练习 3.10 中函数的环境结构
    • +
    • 绿色部分展示了原始版本环境结构的差异(其中make_withdraw被替换为图 3.9 中的版本)
    • +
    +
    + +
    +
    +
    + + + + 帧(环境模型)作为局部状态存储库 + + + 局部状态保存在帧(环境模型)中 + + + 评估的环境模型局部状态 + + + make_withdraw环境在环境模型中 + +
    diff --git a/xml/cn/chapter3/section2/subsection5.xml b/xml/zh/chapter3/section2/subsection5.xml similarity index 100% rename from xml/cn/chapter3/section2/subsection5.xml rename to xml/zh/chapter3/section2/subsection5.xml diff --git a/xml/cn/chapter3/section3/section3.xml b/xml/zh/chapter3/section3/section3.xml similarity index 100% rename from xml/cn/chapter3/section3/section3.xml rename to xml/zh/chapter3/section3/section3.xml diff --git a/xml/cn/chapter3/section3/subsection2.xml b/xml/zh/chapter3/section3/subsection2.xml similarity index 59% rename from xml/cn/chapter3/section3/subsection2.xml rename to xml/zh/chapter3/section3/subsection2.xml index 35b17b1c8..2c7ebfe65 100644 --- a/xml/cn/chapter3/section3/subsection2.xml +++ b/xml/zh/chapter3/section3/subsection2.xml @@ -1,71 +1,70 @@ - - 队列表示 - + + 表示队列 + 队列 - 修改器 + 突变子 - set-car! + set-car! set_head - set-cdr! + set-cdr! set_tail - 使我们能够使用序对来构建无法使用 + 使我们能够使用对来构造那些仅靠 - cons, + cons, pair, - car, + car, head, - cdr + cdr tail - 单独构建的数据结构。本节展示了如何使用序对来表示一个称为队列的数据结构。第节将展示如何表示称为表的数据结构。 + 无法构造的数据结构。 本节展示了如何使用对来表示一种称为队列的数据结构。 第节将展示如何表示称为表的数据结构。 - - A 队列 is a sequence in which items are inserted at one end - (called the - 队列 - rear of the queue) and deleted from the other end (the - 队列 - front). + + 一个队列是一个序列,其中项目在一端插入(称为 + queuerear of + 尾部)并从另一端删除(称为 + queuefront of + 头部)。 - 图 + - shows an initially empty queue in which the items - ab 被插入。然后 a 被移除, cd 被插入,并且 b 被移除。因为项目总是按插入的顺序被移除,所以队列有时被称为 - 先入先出缓冲器 - FIFO(先入先出)缓冲器。 + 显示了一个最初为空的队列,其中插入了项目 + ab。随后a被移除,插入了cd,并移除了b。由于项目总是按照插入顺序被删除,队列有时也称为 + FIFO缓冲区 + FIFO(先进先出)缓冲区。 - +
    队列操作。 - +
    - +
    @@ -125,180 +124,186 @@
    - Operation + 操作 - Resulting Queue + 结果队列
    队列操作。 - +
    -
    - - - - 在 - 数据抽象用于队列 - 队列上的操作 - 数据抽象方面,我们可以将队列视为由以下一组操作定义的: -
      -
    • - 一个构造函数: -

      - make_queue + + + + + + + + 从 + 数据抽象用于队列 + 队列的操作 + 数据抽象的角度来看,我们可以将队列视为由下列一组操作定义: +
        +
      • + 一个构造函数: +

        + make_queue - (make-queue) + (make-queue) make_queue()

        - 返回一个空队列(一个不包含任何项目的队列)。 + 返回一个空队列(即不包含任何项的队列)。 \vspace{6pt}
      • 一个谓词:

        - is_empty_queue + is_empty_queue - (empty-queue? queue) + (empty-queue? queue) is_empty_queue(queue)

        - 用来测试队列是否为空。 + 用于测试队列是否为空。 \vspace{6pt}
      • 一个选择器:

        - front_queue + front_queue - (front-queue queue) + (front-queue queue) front_queue(queue)

        - 返回队列前端的对象,如果队列为空则提示错误;它不修改队列。 + 返回队列前端的对象;如果队列为空则发出错误信号,并且不修改队列。 \vspace{6pt}
      • -
      • - 两个修改器: +
      • + 两个变异操作:

        - (insert-queue! queue item) - + (insert-queue! queue item) + insert_queue(queue, item)

        - 插入 - insert_queue - 在队列的尾部插入项目,并以修改后的队列作为其值返回。\\[4pt]

        + 在队列尾部插入 + insert_queue + 项,并返回修改后的队列作为其值。\\[4pt]

        - (delete-queue! queue) + (delete-queue! queue) delete_queue(queue)

        - 移除 - delete_queue - 在队列的头部移除项目,并以修改后的队列作为其值返回,如果队列在删除前为空,则提示错误。 + 移除队列前端的 + delete_queue + 项,并返回修改后的队列作为其值;如果删除前队列为空,则发出错误信号。
      • -
      +
    - - 因为队列是一个项目序列,我们当然可以将其表示为一个普通的列表;队列的前部将是列表的 + + + + 由于队列是项目的序列,我们当然可以将其表示为普通列表;队列的前端将是列表的 - car + car head - ,在队列中插入一个项目相当于将一个新元素附加到列表的末尾,而从队列中删除一个项目则相当于获取列表的 + ,在队列中插入一个项目等同于在列表末尾追加一个新元素,而从队列中删除一个项目则相当于取出列表的 - cdr + cdr tail - 。然而,这种表示是低效的,因为为了插入一个项目,我们必须扫描列表直到到达末尾。由于我们扫描列表的唯一方法是通过连续的 + 。然而,这种表示方法效率不高,因为为了插入一个项目,我们必须遍历列表直到到达末尾。由于我们遍历列表的唯一方法是通过连续的 - cdr + cdr tail - 操作,这种扫描对于一个包含 - $n$ - 项的列表需要 - $\Theta(n)$ - 步骤。对列表表示的一种简单修改可以克服这种缺点,使得队列操作可以实现为只需 - $\Theta(1)$ - 步骤;也就是说,所需步骤的数量不依赖于队列的长度。 - + 操作,因此对于包含 $n$ 个项目的列表,这种遍历需要 $\Theta(n)$ 个步骤。对列表表示方法进行简单修改可以克服这一缺陷,使得队列操作的实现只需 $\Theta(1)$ 个步骤;也就是说,所需步骤的数量与队列的长度无关。 + - - 列表表示法的困难在于需要扫描以找到列表的末尾。我们需要扫描的原因是,虽然将列表表示为序对链的标准方法为我们提供了一个指向列表开头的指针,但它没有提供一个易于访问的指向末尾的指针。避免这一缺点的方法是将队列表示为一个列表,外加一个指示列表最后一对的额外指针。这样,当我们要插入一个项目时,我们可以查看尾部指针,从而避免扫描列表。 - - - 因此,队列被表示为一对指针, + + + 列表表示方法的问题在于需要扫描以查找列表的末尾。需要扫描的原因在于,尽管将列表表示为一系列对的链这一标准方法能够为我们提供一个指向列表开头的指针,但它却无法提供一个易于访问的指向列表末尾的指针。克服这一缺点的修改方法是将队列表示为一个列表,并附加一个额外的指针,该指针指示列表中的最后一对。这样,在插入项目时,我们就可以查阅尾部指针,从而避免扫描整个列表。 + + + + + + 队列因此被表示为一对指针, - front-ptr + front-ptr front_ptr - 和 + 以及 - rear-ptr, + rear-ptr, rear_ptr, - ,它们分别指示普通列表中的第一个和最后一个对。由于我们希望队列是一个可识别的对象,我们可以使用 + 它们分别指示普通列表中的第一对和最后一对。 + 由于我们希望队列是一个可识别的对象,因此可以使用 - cons + cons pair - 来组合这两个指针。因此,队列本身将是这两个指针的 + 来将这两个指针组合起来。于是,队列本身将是这两个指针的 - cons + cons pair - 。 + 。 - 图 - + Figure + Figure - 说明了这种表示法。 + 说明了这种表示方法。 - +
    - 实现一个具有前端和尾部指针的列表作为队列。 + 使用前、后指针将队列作为一个列表实现。 - +
    - +
    - 实现一个具有前端和尾部指针的列表作为队列。 + 使用前、后指针将队列作为一个列表实现。 - +
    -
    +
    - - 为了定义队列操作,我们使用以下 + + + + 为了定义队列操作,我们使用下面的 - 过程,函数, + 程序,函数, - 它们使我们能够选择和修改队列的前端和尾部指针: - + 这使我们能够选择并修改队列的前指针和后指针: + modify_pointers_example const q = pair(1, 2); set_front_ptr(q, 42); front_ptr(q); - - + + front_ptr rear_ptr set_front_ptr @@ -306,7 +311,7 @@ front_ptr(q); modify_pointers modify_pointers_example 42 - + (define (front-ptr queue) (car queue)) (define (rear-ptr queue) (cdr queue)) @@ -314,7 +319,7 @@ front_ptr(q); (define (set-front-ptr! queue item) (set-car! queue item)) (define (set-rear-ptr! queue item) (set-cdr! queue item)) - + function front_ptr(queue) { return head(queue); } @@ -324,12 +329,14 @@ function set_front_ptr(queue, item) { set_head(queue, item); } function set_rear_ptr(queue, item) { set_tail(queue, item); } - - + + - - 现在我们可以实现实际的队列操作。如果队列的前端指针是空列表,我们将认为队列为空。 - + + + + 现在我们可以实现实际的队列操作。我们将认为队列为空当且仅当其前指针为空列表: + is_empty_queue_example const q = pair(null, 2); @@ -348,23 +355,24 @@ is_empty_queue(q); function is_empty_queue(queue) { return is_null(front_ptr(queue)); } - + + 构造函数 - make-queue + make-queue make_queue - 构造函数返回一个最初为空的队列,一个其 + 返回一个初始为空的队列,该队列是一个对偶,其中的 - car + car head - cdr + cdr tail - 均为空列表的序对: - + 均为空列表: + make_queue_example modify_pointers @@ -383,14 +391,14 @@ front_ptr(q); function make_queue() { return pair(null, null); } - - 要选择队列前面的项目,我们返回由前端指针指示的序对的 + + 为了选择队列前部的元素,我们返回由前指针指示的对偶的 - car + car head : - + front_queue_example const q = pair(pair(1, 2), 3); @@ -419,45 +427,46 @@ function front_queue(queue) { - - 要在队列中插入一个项目,我们遵循在 + + + + 要在队列中插入一项,我们遵循的方法,其结果在 - 图中指示的方法。 - 中指示的方法。 + 中所示。 + 中所示。 - 我们首先创建一个新序对,其 + 首先创建一个新的对偶,其 - car + car head - 是要插入的项,其 + 为要插入的项,其 - cdr + cdr tail - 是空列表。如果队列最初为空,我们将队列的前端和尾部指针设置为这个新序对。否则,我们将队列中的最后一个序对修改为指向新序对,并且也将尾部指针设置为新序对。 + 为空列表。如果队列最初为空,则将队列的前指针和后指针都设为这个新对偶;否则,我们修改队列中最后一个对偶,使其指向新对偶,并将后指针也更新为该新对偶。 - +
    - 使用(insert-queue! q 'd)在图的队列上的结果。 + 使用(insert-queue! q 'd)对图中的队列进行操作的结果。 - +
    - +
    - 使用 - insert_queue(q, "d")在图的队列上的结果。 + 使用insert_queue(q, "d")对图中的队列进行操作的结果。 - +
    - + print_queue_example make_queue insert_queue @@ -507,44 +516,46 @@ function insert_queue(queue, item) {
    - - 要删除队列前面的项目,我们只需修改前端指针,使其现在指向队列中的第二个项目,可以通过跟随第一个项目的 + + + + 为了删除队列首部的项,我们只需修改前指针,使其现在指向队列中的第二项,这可以通过沿着第一个项的 - cdr + cdr tail - 指针来找到(见 - ):如果第一个项目是队列中的最后一个项目,删除后前端指针将为空列表,这将标记队列为空;我们不需要担心更新尾部指针,它仍将指向被删除的项目,因为 + 指针找到(参见 + ):如果第一个项是队列中的最后一项,则删除后前指针将变为空列表,从而标志该队列为空;我们不必担心更新后指针,因为它仍指向已删除的项,而 - - empty-queue? - + + empty-queue? + is_empty_queue - 只查看前端指针。 + 仅检查前指针。 - +
    - 在图的队列上使用(delete-queue! q)的结果。 + 对图中的队列使用(delete-queue! q)操作的结果。 - +
    - +
    - 在图的队列上使用delete_queue(q)的结果。 + 对图中的队列使用delete_queue(q)操作的结果。 - +
    - + delete_queue delete_queue is_empty_queue @@ -572,19 +583,21 @@ function delete_queue(queue) {
    - + + + Ben Bitdiddle 决定测试上面描述的队列实现。他在 - Lisp - JavaScript + procedures + functions - 解释器中输入这些 + 中输入这些过程/函数到 - 过程 - 函数 + Lisp + JavaScript - ,并继续尝试它们: - + 解释器,并开始尝试它们: + make_queue_example1 make_queue @@ -660,10 +673,42 @@ delete_queue(q1); (() b) - 这全错了! 他抱怨道。 解释器的响应显示最后一个项目被插入了两次。当我删除两个项目后,第二个 b 仍然在那,所以即使它应该是空的,队列并没有空。 Eva Lu Ator 建议 Ben 误解了正在发生的事情。 并不是说项目被插入队列两次, 她解释道。 只是标准的 Lisp JavaScript 打印程序不知道如何识别队列的表示。如果你想要正确打印队列,你需要为队列定义自己的打印 过程 函数 解释 Eva Lu 所讨论的内容。具体地,展示为什么 Ben 的例子产生了他们所做的打印结果。定义一个 过程 函数 print_queue print-queue print_queue ,它以队列为输入并打印队列中的项目序列。 - - - + Its all wrong! 他抱怨道。 + The interpreters response shows that the last item is inserted + into the queue twice. And when I delete both items, the second + b is still there, so the queue isnt + empty, even though its supposed to be. + Eva Lu Ator 认为 Ben 对正在发生的事情理解有误。 + Its not that the + items are going into the queue twice, 她解释道。 + Its just that the standard + + Lisp + JavaScript + + printer doesnt know how to make sense of the queue representation. + If you want to see the queue printed correctly, youll have to define + your own print + + procedure + function + + for queues. + 解释 Eva Lu 所说的内容。具体来说,展示为什么 Ben 的示例会产生如此的打印结果。 + 定义一个 + + procedure + function + + print_queue + + print-queue + print_queue + + ,它接受一个队列作为输入,并打印队列中项的序列。 + + + make_queue modify_pointers insert_queue @@ -675,8 +720,8 @@ function print_queue(q) { return display(head(q)); } - - + + ex_3_21_solution_example const q1 = make_queue(); @@ -688,33 +733,34 @@ print_queue(q1); // prints: ["a", ["b", null]] delete_queue(q1); print_queue(q1); // prints: ["b", null] - + - + + - - 我们可以将队列构建为具有局部状态的 + + + 除了用一对指针来表示队列,我们还可以构建一个具有局部状态的队列,这种队列是一种 - 过程 + 过程 函数 - 队列函数式实现 - ,而不是表示为一对指针。局部状态将由指向普通列表开头和结尾的指针组成。因此, + 队列函数式实现。局部状态将由指向普通列表开头和末尾的指针组成。因此, - make-queue + make-queue make_queue - 过程 + 过程 函数 - 将具有以下形式 - + 将具有如下形式: + (define (make-queue) (let ((front-ptr $\ldots$ ) (rear-ptr $\ldots$ )) - definitions of internal procedures + internal procedures 的定义 (define (dispatch m) $\ldots$) dispatch)) @@ -722,24 +768,24 @@ print_queue(q1); // prints: ["b", null] function make_queue() { let front_ptr = $\ldots$; let rear_ptr = $\ldots$; - declarations of internal functions + 内部函数的声明 function dispatch(m) {$\ldots$} return dispatch; } - + 完成 - make-queue + make-queue make_queue - 的定义并使用这种表示提供队列操作的实现。 - - - + 的定义,并使用这种表示法给出队列操作的实现。 + + + ex_3_22_example -// 由GitHub用户devinryu提供 +// 由 GitHub 用户 devinryu 提供 function make_queue() { let front_ptr = null; @@ -790,84 +836,92 @@ function print_queue(queue) { return queue("print_queue")(); } - - + + ex_3_22_example const q = make_queue(); -print_queue(q); // prints: null +print_queue(q); // 输出: null insert_queue(q, "a"); -print_queue(q); // prints: ["a", null] +print_queue(q); // 输出: ["a", null] insert_queue(q, "b"); -print_queue(q); // prints: ["a", ["b", null]] +print_queue(q); // 输出: ["a", ["b", null]] delete_queue(q); -print_queue(q); // prints: ["b", null] +print_queue(q); // 输出: ["b", null] - + - + + - - 双端队列 + + + 一个双端队列 队列双端 双端队列 - (double-ended queue) 是一个序列,其中项目可以在 + (双端队列) 是一种序列,在这种序列中可以在 - 前端或 - 前端或 + + 在前端或 + + + 在前端或在 + - 尾部插入和删除。 - 双端队列的操作有构造函数 + 后端插入和删除元素。 + 双端队列的操作包括构造函数 - make-deque, + make-deque, make_deque, 谓词 - empty-deque, + empty-deque, is_empty_deque, - + + 选择器 - front-deque + front-deque front_deque - rear-deque, + rear-deque, rear_deque, - 以及修改器 + 以及修改操作 - front-insert-deque!, + front-insert-deque!, front_insert_deque, - front-delete-deque!, + front-delete-deque!, front_delete_deque, - rear-insert-deque!, + rear-insert-deque!, rear_insert_deque, 和 - - rear-delete-deque。 - rear_delete_deque。 + + rear-delete-deque. + rear_delete_@deque. - 展示如何使用序对来表示双端队列,并给出这些操作的实现。注意不要让解释器尝试打印一个包含循环的结构。(见 练习。) - 所有操作都应该在 - $\Theta(1)$ 步骤内完成。 - + 请展示如何使用对偶(pair)表示法表示双端队列,并给出这些操作的具体实现。注意不要使解释器尝试打印包含循环结构的数据。(参见练习。) + 所有操作均应在 + $\Theta(1)$ + 步内完成。 + -// solution provided by GitHub user clean99 +// 解决方案由 GitHub 用户 clean99 提供 function make_deque() { return pair(null, null); } @@ -901,14 +955,14 @@ function is_one_item_deque(deque) { } function front_insert_deque(deque, item) { - // use another pair to store a forward pointer + // 使用另一个对偶存储前向指针 const new_pair = pair(pair(item, null), null); if (is_empty_deque(deque)) { set_front_ptr(deque, new_pair); set_rear_ptr(deque, new_pair); } else { set_tail(new_pair, front_ptr(deque)); - // set forward pointer to new_pair + // 将前向指针设置为 new_pair set_tail(head(front_ptr(deque)), new_pair); set_front_ptr(deque, new_pair); } @@ -916,7 +970,7 @@ function front_insert_deque(deque, item) { function front_delete_deque(deque) { if (is_empty_deque(deque)) { - error(deque, "front_delete_deque called with an empty deque"); + error(deque, "front_delete_deque 被空的双端队列调用"); } else if(is_one_item_deque(deque)) { set_front_ptr(deque, null); set_rear_ptr(deque, null); @@ -933,7 +987,7 @@ function rear_insert_deque(deque, item) { set_front_ptr(deque, new_pair); set_rear_ptr(deque, new_pair); } else { - // set new_pair forward pointer to last item + // 将 new_pair 的前向指针设置为最后一个元素 const new_pair = pair(pair(item, rear_ptr(deque)), null); set_tail(rear_ptr(deque), new_pair); set_rear_ptr(deque, new_pair); @@ -942,13 +996,13 @@ function rear_insert_deque(deque, item) { function rear_delete_deque(deque) { if (is_empty_deque(deque)) { - error(deque, "rear_delete_deque called with an empty deque"); + error(deque, "rear_delete_deque 被空的双端队列调用"); } else if(is_one_item_deque(deque)) { set_front_ptr(deque, null); set_rear_ptr(deque, null); return deque; } else { - // update rear_ptr to last item's forward pointer + // 更新 rear_ptr 为最后一个元素的前向指针 set_rear_ptr(deque, tail(head(rear_ptr(deque)))); return deque; } @@ -957,5 +1011,8 @@ function rear_delete_deque(deque) { - 队列 + + + 队列 +
    diff --git a/xml/cn/chapter3/section3/subsection5.xml b/xml/zh/chapter3/section3/subsection5.xml similarity index 100% rename from xml/cn/chapter3/section3/subsection5.xml rename to xml/zh/chapter3/section3/subsection5.xml diff --git a/xml/cn/chapter3/section4/section4.xml b/xml/zh/chapter3/section4/section4.xml similarity index 100% rename from xml/cn/chapter3/section4/section4.xml rename to xml/zh/chapter3/section4/section4.xml diff --git a/xml/cn/chapter3/section4/subsection1.xml b/xml/zh/chapter3/section4/subsection1.xml similarity index 100% rename from xml/cn/chapter3/section4/subsection1.xml rename to xml/zh/chapter3/section4/subsection1.xml diff --git a/xml/cn/chapter3/section4/subsection2.xml b/xml/zh/chapter3/section4/subsection2.xml similarity index 100% rename from xml/cn/chapter3/section4/subsection2.xml rename to xml/zh/chapter3/section4/subsection2.xml diff --git a/xml/cn/chapter3/section5/section5.xml b/xml/zh/chapter3/section5/section5.xml similarity index 100% rename from xml/cn/chapter3/section5/section5.xml rename to xml/zh/chapter3/section5/section5.xml diff --git a/xml/cn/chapter3/section5/subsection1.xml b/xml/zh/chapter3/section5/subsection1.xml similarity index 100% rename from xml/cn/chapter3/section5/subsection1.xml rename to xml/zh/chapter3/section5/subsection1.xml diff --git a/xml/cn/chapter3/section5/subsection2.xml b/xml/zh/chapter3/section5/subsection2.xml similarity index 100% rename from xml/cn/chapter3/section5/subsection2.xml rename to xml/zh/chapter3/section5/subsection2.xml diff --git a/xml/cn/chapter3/section5/subsection3.xml b/xml/zh/chapter3/section5/subsection3.xml similarity index 100% rename from xml/cn/chapter3/section5/subsection3.xml rename to xml/zh/chapter3/section5/subsection3.xml diff --git a/xml/cn/chapter3/section5/subsection4.xml b/xml/zh/chapter3/section5/subsection4.xml similarity index 100% rename from xml/cn/chapter3/section5/subsection4.xml rename to xml/zh/chapter3/section5/subsection4.xml diff --git a/xml/cn/chapter3/section5/subsection5.xml b/xml/zh/chapter3/section5/subsection5.xml similarity index 100% rename from xml/cn/chapter3/section5/subsection5.xml rename to xml/zh/chapter3/section5/subsection5.xml diff --git a/xml/cn/chapter4/chapter4.xml b/xml/zh/chapter4/chapter4.xml similarity index 100% rename from xml/cn/chapter4/chapter4.xml rename to xml/zh/chapter4/chapter4.xml diff --git a/xml/cn/chapter4/section1/section1.xml b/xml/zh/chapter4/section1/section1.xml similarity index 100% rename from xml/cn/chapter4/section1/section1.xml rename to xml/zh/chapter4/section1/section1.xml diff --git a/xml/cn/chapter4/section1/subsection1.xml b/xml/zh/chapter4/section1/subsection1.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection1.xml rename to xml/zh/chapter4/section1/subsection1.xml diff --git a/xml/cn/chapter4/section1/subsection2.xml b/xml/zh/chapter4/section1/subsection2.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection2.xml rename to xml/zh/chapter4/section1/subsection2.xml diff --git a/xml/cn/chapter4/section1/subsection3.xml b/xml/zh/chapter4/section1/subsection3.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection3.xml rename to xml/zh/chapter4/section1/subsection3.xml diff --git a/xml/cn/chapter4/section1/subsection4.xml b/xml/zh/chapter4/section1/subsection4.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection4.xml rename to xml/zh/chapter4/section1/subsection4.xml diff --git a/xml/cn/chapter4/section1/subsection5.xml b/xml/zh/chapter4/section1/subsection5.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection5.xml rename to xml/zh/chapter4/section1/subsection5.xml diff --git a/xml/cn/chapter4/section1/subsection6.xml b/xml/zh/chapter4/section1/subsection6.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection6.xml rename to xml/zh/chapter4/section1/subsection6.xml diff --git a/xml/cn/chapter4/section1/subsection7.xml b/xml/zh/chapter4/section1/subsection7.xml similarity index 100% rename from xml/cn/chapter4/section1/subsection7.xml rename to xml/zh/chapter4/section1/subsection7.xml diff --git a/xml/cn/chapter4/section2/section2.xml b/xml/zh/chapter4/section2/section2.xml similarity index 100% rename from xml/cn/chapter4/section2/section2.xml rename to xml/zh/chapter4/section2/section2.xml diff --git a/xml/cn/chapter4/section2/subsection1.xml b/xml/zh/chapter4/section2/subsection1.xml similarity index 100% rename from xml/cn/chapter4/section2/subsection1.xml rename to xml/zh/chapter4/section2/subsection1.xml diff --git a/xml/cn/chapter4/section2/subsection2.xml b/xml/zh/chapter4/section2/subsection2.xml similarity index 100% rename from xml/cn/chapter4/section2/subsection2.xml rename to xml/zh/chapter4/section2/subsection2.xml diff --git a/xml/cn/chapter4/section2/subsection3.xml b/xml/zh/chapter4/section2/subsection3.xml similarity index 100% rename from xml/cn/chapter4/section2/subsection3.xml rename to xml/zh/chapter4/section2/subsection3.xml diff --git a/xml/cn/chapter4/section3/section3.xml b/xml/zh/chapter4/section3/section3.xml similarity index 100% rename from xml/cn/chapter4/section3/section3.xml rename to xml/zh/chapter4/section3/section3.xml diff --git a/xml/cn/chapter4/section3/subsection1.xml b/xml/zh/chapter4/section3/subsection1.xml similarity index 100% rename from xml/cn/chapter4/section3/subsection1.xml rename to xml/zh/chapter4/section3/subsection1.xml diff --git a/xml/cn/chapter4/section3/subsection2.xml b/xml/zh/chapter4/section3/subsection2.xml similarity index 100% rename from xml/cn/chapter4/section3/subsection2.xml rename to xml/zh/chapter4/section3/subsection2.xml diff --git a/xml/cn/chapter4/section3/subsection3.xml b/xml/zh/chapter4/section3/subsection3.xml similarity index 100% rename from xml/cn/chapter4/section3/subsection3.xml rename to xml/zh/chapter4/section3/subsection3.xml diff --git a/xml/cn/chapter4/section4/section4.xml b/xml/zh/chapter4/section4/section4.xml similarity index 100% rename from xml/cn/chapter4/section4/section4.xml rename to xml/zh/chapter4/section4/section4.xml diff --git a/xml/cn/chapter4/section4/subsection1.xml b/xml/zh/chapter4/section4/subsection1.xml similarity index 100% rename from xml/cn/chapter4/section4/subsection1.xml rename to xml/zh/chapter4/section4/subsection1.xml diff --git a/xml/cn/chapter4/section4/subsection2.xml b/xml/zh/chapter4/section4/subsection2.xml similarity index 100% rename from xml/cn/chapter4/section4/subsection2.xml rename to xml/zh/chapter4/section4/subsection2.xml diff --git a/xml/cn/chapter4/section4/subsection3.xml b/xml/zh/chapter4/section4/subsection3.xml similarity index 100% rename from xml/cn/chapter4/section4/subsection3.xml rename to xml/zh/chapter4/section4/subsection3.xml diff --git a/xml/cn/chapter4/section4/subsection4.xml b/xml/zh/chapter4/section4/subsection4.xml similarity index 100% rename from xml/cn/chapter4/section4/subsection4.xml rename to xml/zh/chapter4/section4/subsection4.xml diff --git a/xml/cn/chapter5/chapter5.xml b/xml/zh/chapter5/chapter5.xml similarity index 100% rename from xml/cn/chapter5/chapter5.xml rename to xml/zh/chapter5/chapter5.xml diff --git a/xml/cn/chapter5/section1/section1.xml b/xml/zh/chapter5/section1/section1.xml similarity index 100% rename from xml/cn/chapter5/section1/section1.xml rename to xml/zh/chapter5/section1/section1.xml diff --git a/xml/cn/chapter5/section1/subsection1.xml b/xml/zh/chapter5/section1/subsection1.xml similarity index 100% rename from xml/cn/chapter5/section1/subsection1.xml rename to xml/zh/chapter5/section1/subsection1.xml diff --git a/xml/cn/chapter5/section1/subsection2.xml b/xml/zh/chapter5/section1/subsection2.xml similarity index 100% rename from xml/cn/chapter5/section1/subsection2.xml rename to xml/zh/chapter5/section1/subsection2.xml diff --git a/xml/cn/chapter5/section1/subsection3.xml b/xml/zh/chapter5/section1/subsection3.xml similarity index 100% rename from xml/cn/chapter5/section1/subsection3.xml rename to xml/zh/chapter5/section1/subsection3.xml diff --git a/xml/cn/chapter5/section1/subsection4.xml b/xml/zh/chapter5/section1/subsection4.xml similarity index 100% rename from xml/cn/chapter5/section1/subsection4.xml rename to xml/zh/chapter5/section1/subsection4.xml diff --git a/xml/cn/chapter5/section1/subsection5.xml b/xml/zh/chapter5/section1/subsection5.xml similarity index 100% rename from xml/cn/chapter5/section1/subsection5.xml rename to xml/zh/chapter5/section1/subsection5.xml diff --git a/xml/cn/chapter5/section2/section2.xml b/xml/zh/chapter5/section2/section2.xml similarity index 100% rename from xml/cn/chapter5/section2/section2.xml rename to xml/zh/chapter5/section2/section2.xml diff --git a/xml/cn/chapter5/section2/subsection1.xml b/xml/zh/chapter5/section2/subsection1.xml similarity index 100% rename from xml/cn/chapter5/section2/subsection1.xml rename to xml/zh/chapter5/section2/subsection1.xml diff --git a/xml/cn/chapter5/section2/subsection2.xml b/xml/zh/chapter5/section2/subsection2.xml similarity index 100% rename from xml/cn/chapter5/section2/subsection2.xml rename to xml/zh/chapter5/section2/subsection2.xml diff --git a/xml/cn/chapter5/section2/subsection3.xml b/xml/zh/chapter5/section2/subsection3.xml similarity index 100% rename from xml/cn/chapter5/section2/subsection3.xml rename to xml/zh/chapter5/section2/subsection3.xml diff --git a/xml/cn/chapter5/section2/subsection4.xml b/xml/zh/chapter5/section2/subsection4.xml similarity index 100% rename from xml/cn/chapter5/section2/subsection4.xml rename to xml/zh/chapter5/section2/subsection4.xml diff --git a/xml/cn/chapter5/section3/section3.xml b/xml/zh/chapter5/section3/section3.xml similarity index 100% rename from xml/cn/chapter5/section3/section3.xml rename to xml/zh/chapter5/section3/section3.xml diff --git a/xml/cn/chapter5/section3/subsection1.xml b/xml/zh/chapter5/section3/subsection1.xml similarity index 100% rename from xml/cn/chapter5/section3/subsection1.xml rename to xml/zh/chapter5/section3/subsection1.xml diff --git a/xml/cn/chapter5/section3/subsection2.xml b/xml/zh/chapter5/section3/subsection2.xml similarity index 100% rename from xml/cn/chapter5/section3/subsection2.xml rename to xml/zh/chapter5/section3/subsection2.xml diff --git a/xml/cn/chapter5/section4/section4.xml b/xml/zh/chapter5/section4/section4.xml similarity index 100% rename from xml/cn/chapter5/section4/section4.xml rename to xml/zh/chapter5/section4/section4.xml diff --git a/xml/cn/chapter5/section4/subsection1.xml b/xml/zh/chapter5/section4/subsection1.xml similarity index 100% rename from xml/cn/chapter5/section4/subsection1.xml rename to xml/zh/chapter5/section4/subsection1.xml diff --git a/xml/cn/chapter5/section4/subsection2.xml b/xml/zh/chapter5/section4/subsection2.xml similarity index 100% rename from xml/cn/chapter5/section4/subsection2.xml rename to xml/zh/chapter5/section4/subsection2.xml diff --git a/xml/cn/chapter5/section4/subsection3.xml b/xml/zh/chapter5/section4/subsection3.xml similarity index 100% rename from xml/cn/chapter5/section4/subsection3.xml rename to xml/zh/chapter5/section4/subsection3.xml diff --git a/xml/cn/chapter5/section4/subsection4.xml b/xml/zh/chapter5/section4/subsection4.xml similarity index 100% rename from xml/cn/chapter5/section4/subsection4.xml rename to xml/zh/chapter5/section4/subsection4.xml diff --git a/xml/cn/chapter5/section5/section5.xml b/xml/zh/chapter5/section5/section5.xml similarity index 100% rename from xml/cn/chapter5/section5/section5.xml rename to xml/zh/chapter5/section5/section5.xml diff --git a/xml/cn/chapter5/section5/subsection1.xml b/xml/zh/chapter5/section5/subsection1.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection1.xml rename to xml/zh/chapter5/section5/subsection1.xml diff --git a/xml/cn/chapter5/section5/subsection2.xml b/xml/zh/chapter5/section5/subsection2.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection2.xml rename to xml/zh/chapter5/section5/subsection2.xml diff --git a/xml/cn/chapter5/section5/subsection3.xml b/xml/zh/chapter5/section5/subsection3.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection3.xml rename to xml/zh/chapter5/section5/subsection3.xml diff --git a/xml/cn/chapter5/section5/subsection4.xml b/xml/zh/chapter5/section5/subsection4.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection4.xml rename to xml/zh/chapter5/section5/subsection4.xml diff --git a/xml/cn/chapter5/section5/subsection5.xml b/xml/zh/chapter5/section5/subsection5.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection5.xml rename to xml/zh/chapter5/section5/subsection5.xml diff --git a/xml/cn/chapter5/section5/subsection6.xml b/xml/zh/chapter5/section5/subsection6.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection6.xml rename to xml/zh/chapter5/section5/subsection6.xml diff --git a/xml/cn/chapter5/section5/subsection7.xml b/xml/zh/chapter5/section5/subsection7.xml similarity index 100% rename from xml/cn/chapter5/section5/subsection7.xml rename to xml/zh/chapter5/section5/subsection7.xml diff --git a/xml/cn/others/02foreword02.xml b/xml/zh/others/02foreword02.xml similarity index 100% rename from xml/cn/others/02foreword02.xml rename to xml/zh/others/02foreword02.xml diff --git a/xml/cn/others/02foreword84.xml b/xml/zh/others/02foreword84.xml similarity index 100% rename from xml/cn/others/02foreword84.xml rename to xml/zh/others/02foreword84.xml diff --git a/xml/cn/others/03prefaces03.xml b/xml/zh/others/03prefaces03.xml similarity index 100% rename from xml/cn/others/03prefaces03.xml rename to xml/zh/others/03prefaces03.xml diff --git a/xml/cn/others/03prefaces96.xml b/xml/zh/others/03prefaces96.xml similarity index 100% rename from xml/cn/others/03prefaces96.xml rename to xml/zh/others/03prefaces96.xml diff --git a/xml/cn/others/04acknowledgements04.xml b/xml/zh/others/04acknowledgements04.xml similarity index 100% rename from xml/cn/others/04acknowledgements04.xml rename to xml/zh/others/04acknowledgements04.xml diff --git a/xml/cn/others/06see06.xml b/xml/zh/others/06see06.xml similarity index 100% rename from xml/cn/others/06see06.xml rename to xml/zh/others/06see06.xml diff --git a/xml/cn/others/97references97.xml b/xml/zh/others/97references97.xml similarity index 100% rename from xml/cn/others/97references97.xml rename to xml/zh/others/97references97.xml diff --git a/xml/cn/others/98indexpreface98.xml b/xml/zh/others/98indexpreface98.xml similarity index 100% rename from xml/cn/others/98indexpreface98.xml rename to xml/zh/others/98indexpreface98.xml diff --git a/xml/cn/others/99making99.xml b/xml/zh/others/99making99.xml similarity index 100% rename from xml/cn/others/99making99.xml rename to xml/zh/others/99making99.xml From 0b4097ac149dd1942a7a7abaf8f8cff99775539b Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:59:07 +0800 Subject: [PATCH 35/55] update deploy-pages.yml to clone from `translated_xmls` clone into the xml directory and run yarn json etc to generate the files --- .github/workflows/deploy-pages.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 595bf9166..634860626 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -30,6 +30,11 @@ jobs: sudo apt-get install -y texlive texlive-fonts-extra latexmk - name: Fetch Yarn dependencies run: yarn install + - name: Clone translated_xmls + run: | + git clone -b translated_xmls https://github.com/source-academy/sicp.git translated_xmls + mv translated_xmls/* xml/ + rm -r translated_xmls - name: Build run: | set -euxo pipefail From 63e0d213777245ca8be0ff977a3b5d196bfec568 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Mon, 14 Apr 2025 20:38:23 +0800 Subject: [PATCH 36/55] update the CLI of yarn trans --- .gitignore | 3 +- dictionary/short.txt | 1 - i18n/.gitignore | 3 +- i18n/ai_files/zh_CN/dictionary.txt | 1 + .../ai_files/zh_CN/dictionary_.txt | 0 i18n/controllers/gitComm.ts | 57 ---- i18n/controllers/recurTranslate.ts | 11 +- i18n/controllers/translate.ts | 250 ------------------ i18n/index.ts | 188 ++++++------- i18n/initializers/initialize.ts | 2 +- 10 files changed, 98 insertions(+), 418 deletions(-) delete mode 100644 dictionary/short.txt create mode 100644 i18n/ai_files/zh_CN/dictionary.txt rename dictionary/cn.txt => i18n/ai_files/zh_CN/dictionary_.txt (100%) delete mode 100644 i18n/controllers/gitComm.ts delete mode 100644 i18n/controllers/translate.ts diff --git a/.gitignore b/.gitignore index a80ea8faf..bfb978f53 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ test_node_env/node_modules .env *.icloud -/xml_* \ No newline at end of file +/xml_* +/logs diff --git a/dictionary/short.txt b/dictionary/short.txt deleted file mode 100644 index fb1559f53..000000000 --- a/dictionary/short.txt +++ /dev/null @@ -1 +0,0 @@ -function: 睡觉 \ No newline at end of file diff --git a/i18n/.gitignore b/i18n/.gitignore index 30bc16279..94d2067e3 100644 --- a/i18n/.gitignore +++ b/i18n/.gitignore @@ -1 +1,2 @@ -/node_modules \ No newline at end of file +/node_modules +/translation_output diff --git a/i18n/ai_files/zh_CN/dictionary.txt b/i18n/ai_files/zh_CN/dictionary.txt new file mode 100644 index 000000000..58598167b --- /dev/null +++ b/i18n/ai_files/zh_CN/dictionary.txt @@ -0,0 +1 @@ +function: 函数 diff --git a/dictionary/cn.txt b/i18n/ai_files/zh_CN/dictionary_.txt similarity index 100% rename from dictionary/cn.txt rename to i18n/ai_files/zh_CN/dictionary_.txt diff --git a/i18n/controllers/gitComm.ts b/i18n/controllers/gitComm.ts deleted file mode 100644 index ccb4fb5f0..000000000 --- a/i18n/controllers/gitComm.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Octokit } from "octokit"; -import dotenv from "dotenv"; -import fs from "fs"; -import path from "path"; -dotenv.config(); - -if ( - process.env.GITHUB_OWNER === undefined || - process.env.GITHUB_REPO === undefined -) { - throw Error("Please specify GITHUB_OWNER, GITHUB_REPO to pull EN XML from!"); -} - -// initialize GitHub API -const octokit = new Octokit(); - -async function getSource(filePath: string): Promise { - let toTranslate; - - try { - const result = await octokit.request( - "GET /repos/{owner}/{repo}/contents/{path}", - { - owner: process.env.GITHUB_OWNER!, - repo: process.env.GITHUB_REPO!, - path: filePath, - headers: { - accept: "application/vnd.github.raw+json" - } - } - ); - - toTranslate = result.data; - const output_dir = path.join(import.meta.dirname, "../../ori"); - - // Ensure directory exists - const dir = path.dirname(path.join(output_dir, filePath)); - fs.mkdirSync(dir, { recursive: true }); - - const fullPath = path.resolve(path.join(output_dir, filePath)); - fs.writeFileSync(fullPath, toTranslate); - - console.log( - `Successfully retrieved ${filePath} from GitHub, retrieval status: ${result.status}` - ); - } catch (error) { - console.log( - `Error retrieving ${filePath} from GitHub.\n Status: ${error.status}.\n Rate limit remaining: ${error.response.headers["x-ratelimit-remaining"]}.\n Message: ${error.response.data.message}` - ); - } - - return toTranslate as string; -} - -async function commitTranslated() {} - -export { getSource, commitTranslated }; diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 46ae3fc73..a16764c42 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -81,22 +81,19 @@ async function translate(langCode: string, filePath: string): Promise { try { // Use the provided file path directly without modification const input_path = filePath; - const language = languageNames.of(langCode); + const language = languageNames.of(langCode.replace('_', '-')); if (language === undefined) { throw new Error('Undefined language'); } - if (!troubleshoot) assistant = await createAssistant(language, ai as any); + if (!troubleshoot) assistant = await createAssistant(langCode, ai as any); - - // Generate output path by replacing "/en/" with "/cn/" in the path + // Generate output path by replacing "/en/" with "/../i18n/translation_output/zh_CN/" in the path const output_path = filePath.replace( path.sep + "en" + path.sep, - path.sep + langCode + path.sep + path.sep + ".." + path.sep + "i18n" + path.sep + "translation_output" + path.sep + langCode + path.sep ); - - const translated: string = await recursivelyTranslate( language, diff --git a/i18n/controllers/translate.ts b/i18n/controllers/translate.ts deleted file mode 100644 index 375c5b8ab..000000000 --- a/i18n/controllers/translate.ts +++ /dev/null @@ -1,250 +0,0 @@ -import fs from "fs"; -import OpenAI from "openai"; -import path from "path"; -import createAssistant from "../initializers/initialize"; -import dotenv from "dotenv"; -import sax from "sax"; -import { Readable } from "stream"; -import { fileURLToPath } from "url"; - -dotenv.config(); - -if (process.env.AI_MODEL === undefined || process.env.API_KEY === undefined) { - throw Error("Please specify AI_MODEL and API_KEY!"); -} - -// initialize OpenAI API -const ai = new OpenAI({ - apiKey: process.env.API_KEY, - baseURL: process.env.AI_BASEURL -}); - -// TODO: change the toTranslate to a file path, read the file and translate the content -async function translate(language: string, filePath: string) { - // Create a SAX parser in strict mode to split source into chunks. - const parser = (sax as any).createStream(true, { trim: false }); - - // const assistant = await createAssistant(language, ai); - const assistant_id = "asst_BLVYfog5DpWrbu3fW3o2oD4r"; - const thread = await ai.beta.threads.create(); - let translated = ""; - - console.dir(thread); - // Variables to track current depth and segments. - let currentDepth = 0; - let currentSegment = ""; - const segments: [boolean, string][] = []; - - // In this context: - // - Depth 0: Before any element is opened. - // - Depth 1: The root element (). - // - Depth 2: Each direct child of the root that we want to capture. - let isRecording = false; - - parser.on("opentag", node => { - currentDepth++; - - // If we're at depth 2, this is the start of a new segment. - if (currentDepth === 2 || isRecording) { - isRecording = true; - currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; - } else { - segments.push([ - false, - `<${node.name}${formatAttributes(node.attributes)}>` - ]); - } - }); - - parser.on("text", text => { - if (isRecording) { - currentSegment += strongEscapeXML(text); - } else { - segments.push([false, text]); - } - }); - - parser.on("cdata", cdata => { - if (isRecording) { - currentSegment += ``; - } - }); - - parser.on("closetag", tagName => { - if (isRecording) { - currentSegment += ``; - } - - if (currentDepth === 2) { - // We are closing a segment element. - segments.push([true, currentSegment]); - currentSegment = ""; - isRecording = false; - } - - if (currentDepth === 1) { - // We are closing the root element. - segments.push([false, ``]); - } - - currentDepth--; - }); - - parser.on("comment", comment => { - if (isRecording) { - currentSegment += ``; - } else { - segments.push([false, ``]); - } - }); - - parser.on("end", async () => { - for (const segment of segments) { - if (segment[0]) { - translated += await translateChunk(segment[1]); - } else { - translated += segment[1]; - } - } - console.log(`Done translating all segments.`); - const output_path = fileURLToPath( - import.meta.resolve("../../xml_cn" + filePath) - ); - - // Ensure directory exists - const dir = path.dirname(output_path); - fs.mkdirSync(dir, { recursive: true }); - - fs.writeFileSync(output_path, translated); - console.log(`Translation saved to ${output_path}`); - }); - - try { - // Pipe the XML file into the parser. - const input_dir = fileURLToPath( - import.meta.resolve("../../xml" + filePath) - ); - console.log(input_dir); - fs.createReadStream(input_dir).pipe(parser); - } catch (parseErr) { - console.error("Error parsing XML:", parseErr); - } - - async function translateChunk(chunk: string) { - // console.log("translating chunk: " + chunk); - // Create a SAX parser in strict mode for cleaning up translations. - const clean = (sax as any).createStream(true, { trim: false }); - - // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags - let currDepth = -1; - - clean.on("text", text => { - if (currDepth >= 1) { - translated += strongEscapeXML(text); - } - }); - - clean.on("opentag", node => { - currDepth++; - if (node.name != "WRAPPER") { - translated += `<${node.name}${formatAttributes(node.attributes)}>`; - } - }); - - clean.on("closetag", tagName => { - if (tagName != "WRAPPER") { - translated += ``; - } - currDepth--; - }); - - clean.on("cdata", cdata => { - translated += ``; - }); - - clean.on("comment", comment => { - translated += ``; - }); - - clean.on("error", error => { - console.log( - "error encountered when validating XML: " + - error + - "\nvalidating section: " + - chunk.substring(0, 100) + - "..." - ); - - // Attempt to recover using the internal parser - try { - clean._parser.resume(); - } catch (e) { - console.log("Failed to resume parser:", e); - } - }); - - let translated = ""; - - try { - await ai.beta.threads.messages.create(thread.id, { - role: "user", - content: `Translate this content to ${language}. - ${chunk}` - }); - const run = await ai.beta.threads.runs.createAndPoll(thread.id, { - assistant_id: assistant_id - }); - - const messages = await ai.beta.threads.messages.list(thread.id, { - run_id: run.id - }); - const message = messages.data.pop()!; - const messageContent = message.content[0]; - - if (messageContent.type !== "text") { - throw new Error( - `Unexpected message content type: ${messageContent.type}` - ); - } - - const text = messageContent.text; - // console.log(text.value); - - const safeText: String = escapeXML(text.value); - const textStream = Readable.from("" + safeText + ""); - - await new Promise((resolve, reject) => { - clean.once("end", resolve); - clean.once("error", reject); - textStream.pipe(clean); - }); - - return translated; - } catch (err) { - console.log(`Error occured while translating ${filePath}:\n ` + err); - } - } -} - -export default translate; - -// Helper function to format attributes into a string. -function formatAttributes(attrs) { - const attrStr = Object.entries(attrs) - .map(([key, val]) => `${key}="${val}"`) - .join(" "); - return attrStr ? " " + attrStr : ""; -} - -function escapeXML(str: string): string { - return str.replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&"); -} - -function strongEscapeXML(str: string): string { - return str - .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} diff --git a/i18n/index.ts b/i18n/index.ts index 4173f28d0..8cf4cac93 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -220,135 +220,123 @@ async function needsTranslation(enFilePath: string, lang: string): Promise { await setupCleanupHandlers(); try { - if ((process.argv[2], process.argv[3])) { - fancyName(process.argv[2], process.argv[3]); - return; - } - - let languages: string[] = []; - - if (process.argv[2] === "all") { - languages = await getDirectories(path.join(__dirname, "../xml")); - console.dir(languages); - } else { - languages.push(process.argv[2]); - } + const languages: string[] = await getDirectories(path.join(__dirname, "ai_files")); + console.dir(languages); // Get the absolute path to the xml/en directory using proper path resolution const enDirPath = path.resolve(__dirname, "../xml/en"); - console.log(`Scanning directory: ${enDirPath}`); - // Find all XML files - xmlFiles = await findAllXmlFiles(enDirPath); + if (process.argv[2] === "all" || process.argv.length <= 2) { + console.log(`Scanning directory: ${enDirPath}`); + xmlFiles = await findAllXmlFiles(enDirPath); + } else { + const [, , ..._xmlFiles] = process.argv; + xmlFiles = _xmlFiles.map(file => path.join(__dirname, "..", file)); + } console.log(`Found ${xmlFiles.length} XML files to check for translation`); - + for (const lang of languages) { - // Filter files that need translation - filesToTranslate = []; - for (const file of xmlFiles) { - if (await needsTranslation(file, lang)) { - filesToTranslate.push(file as never); + // Filter files that need translation + filesToTranslate = []; + for (const file of xmlFiles) { + if (await needsTranslation(file, lang)) { + filesToTranslate.push(file as never); + } } - } - - console.log(`${filesToTranslate.length} files need translation`); - - if (filesToTranslate.length === 0) { - console.log(`No files need translation for ${lang}.`); - return; - } - - // Process files in batches to avoid overwhelming the system - const batchSize: number = max_trans_num; - - // Track all failed translations with their errors - failures = []; - - for (let i = 0; i < filesToTranslate.length; i += batchSize) { - const batch = filesToTranslate.slice(i, i + batchSize); - console.log( - `Starting batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` - ); - // Process each file in the batch, but handle errors individually - const results = await Promise.allSettled( - batch.map(async file => { - try { - console.log(`Starting translation for ${file}`); - await translate(lang, file); - return { file, success: true }; - } catch (error) { - // Return failure with error but don't log yet - return { file, success: false, error }; - } - }) - ); + console.log(`${filesToTranslate.length} files need translation`); - // Count successes and failures - for (const result of results) { - processedCount++; + if (filesToTranslate.length === 0) { + console.log(`No files need translation for ${lang}.`); + return; + } - if (result.status === "fulfilled") { - if (result.value.success) { - successCount++; + // Process files in batches to avoid overwhelming the system + const batchSize: number = max_trans_num; + + // Track all failed translations with their errors + failures = []; + + for (let i = 0; i < filesToTranslate.length; i += batchSize) { + const batch = filesToTranslate.slice(i, i + batchSize); + console.log( + `Starting batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` + ); + + // Process each file in the batch, but handle errors individually + const results = await Promise.allSettled( + batch.map(async file => { + try { + console.log(`Starting translation for ${file}`); + await translate(lang, file); + return { file, success: true }; + } catch (error) { + // Return failure with error but don't log yet + return { file, success: false, error }; + } + }) + ); + + // Count successes and failures + for (const result of results) { + processedCount++; + + if (result.status === "fulfilled") { + if (result.value.success) { + successCount++; + } else { + failureCount++; + failures.push({ + file: result.value.file, + error: result.value.error + }); + console.error( + `Translation failed for ${result.value.file}: ${result.value.error}` + ); + } } else { + // This is for Promise rejections (should be rare with our error handling) failureCount++; failures.push({ - file: result.value.file, - error: result.value.error + file: "Unknown file in batch", + error: result.reason }); - console.error( - `Translation failed for ${result.value.file}: ${result.value.error}` - ); + console.error(`Promise rejected for file: ${result.reason}`); } - } else { - // This is for Promise rejections (should be rare with our error handling) - failureCount++; - failures.push({ - file: "Unknown file in batch", - error: result.reason - }); - console.error(`Promise rejected for file: ${result.reason}`); } + + console.log( + `Completed batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` + ); + console.log( + `Progress: ${successCount} successful, ${failureCount} failed, ${processedCount} processed out of ${filesToTranslate.length} files` + ); } + console.log("All translations completed!"); console.log( - `Completed batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(filesToTranslate.length / batchSize)}` - ); - console.log( - `Progress: ${successCount} successful, ${failureCount} failed, ${processedCount} processed out of ${filesToTranslate.length} files` + `Final results: ${successCount} successful, ${failureCount} failed out of ${filesToTranslate.length} files` ); - } - console.log("All translations completed!"); - console.log( - `Final results: ${successCount} successful, ${failureCount} failed out of ${filesToTranslate.length} files` - ); + // If there are failures, report them all at the end + if (failures.length > 0) { + console.log("\n===== FAILED TRANSLATIONS ====="); + failures.forEach((failure, index) => { + console.log(`${index + 1}. Failed file: ${failure.file}`); + console.log(` Error: ${failure.error}`); + }); + console.log("==============================\n"); + } - // If there are failures, report them all at the end - if (failures.length > 0) { - console.log("\n===== FAILED TRANSLATIONS ====="); - failures.forEach((failure, index) => { - console.log(`${index + 1}. Failed file: ${failure.file}`); - console.log(` Error: ${failure.error}`); - }); - console.log("==============================\n"); + // Save a detailed summary to a log file + await saveSummaryLog(); } - - // Save a detailed summary to a log file - await saveSummaryLog(); - } } catch (e) { console.error("Error during translation process:", e); } diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index 27f8956c2..38763cb13 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -23,7 +23,7 @@ export default async function createAssistant(language: string, ai: OpenAI) { tools: [{ type: "file_search" }] }); - const fileStreams = [path.resolve(__dirname, "../../dictionary/short.txt")].map( + const fileStreams = [path.join(__dirname, "../ai_files", language, "dictionary.txt")].map( path => fs.createReadStream(path) ); From 9caca4bd5fafc41d7221327e886c5c49fb681ba4 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Mon, 14 Apr 2025 22:20:17 +0800 Subject: [PATCH 37/55] add yarn trans test and fixed other bugs --- i18n/controllers/recurTranslate.ts | 2 +- i18n/index.ts | 68 ++++++++++-------------------- i18n/initializers/initialize.ts | 4 +- 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index a16764c42..45459f4b4 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -87,7 +87,7 @@ async function translate(langCode: string, filePath: string): Promise { throw new Error('Undefined language'); } - if (!troubleshoot) assistant = await createAssistant(langCode, ai as any); + if (!troubleshoot) assistant = await createAssistant(langCode, language, ai as any); // Generate output path by replacing "/en/" with "/../i18n/translation_output/zh_CN/" in the path const output_path = filePath.replace( diff --git a/i18n/index.ts b/i18n/index.ts index 8cf4cac93..8d19f4eed 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -196,30 +196,6 @@ async function findAllXmlFiles(directory: string): Promise { return xmlFiles; } -// Function to check if a file needs translation -async function needsTranslation(enFilePath: string, lang: string): Promise { - // Generate the corresponding cn file path - const cnFilePath = enFilePath.replace( - path.sep + "en" + path.sep, - path.sep + lang + path.sep - ); - - try { - // Check if CN file exists - const cnStats = await fs.promises.stat(cnFilePath); - if (!cnStats.isFile()) { - return true; // CN path exists but is not a file (unusual case) - } - - // Compare modification times - const enStats = await fs.promises.stat(enFilePath); - return enStats.mtime > cnStats.mtime; // Return true if EN file is newer - } catch (error) { - // If we get an error, it's likely because the CN file doesn't exist - return true; // Need to translate since CN file doesn't exist - } -} - (async () => { await setupCleanupHandlers(); @@ -230,33 +206,35 @@ async function needsTranslation(enFilePath: string, lang: string): Promise "); + return; + } + try { + console.log('start translating, may take a while ...'); + const fullPath = PathGenerator(process.argv[3]); + await translate(process.argv[4], fullPath); + } catch (e) { + console.error('test error: ', e); + } + return; + } if (process.argv[2] === "all" || process.argv.length <= 2) { + // Find all XML files console.log(`Scanning directory: ${enDirPath}`); - xmlFiles = await findAllXmlFiles(enDirPath); + filestoTranslate = await findAllXmlFiles(enDirPath); } else { - const [, , ..._xmlFiles] = process.argv; - xmlFiles = _xmlFiles.map(file => path.join(__dirname, "..", file)); + const [, , ...xmlFiles] = process.argv; + filesToTranslate = xmlFiles.map(file => path.join(__dirname, "..", file)); } - console.log(`Found ${xmlFiles.length} XML files to check for translation`); + if (filesToTranslate.length === 0) { + console.log(`No files to translate.`); + return; + } + console.log(`${filesToTranslate.length} files need translation`); for (const lang of languages) { - // Filter files that need translation - filesToTranslate = []; - for (const file of xmlFiles) { - if (await needsTranslation(file, lang)) { - filesToTranslate.push(file as never); - } - } - - console.log(`${filesToTranslate.length} files need translation`); - - if (filesToTranslate.length === 0) { - console.log(`No files need translation for ${lang}.`); - return; - } - // Process files in batches to avoid overwhelming the system const batchSize: number = max_trans_num; diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index 38763cb13..062706d76 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -7,7 +7,7 @@ import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -export default async function createAssistant(language: string, ai: OpenAI) { +export default async function createAssistant(langCode: string, language: string, ai: OpenAI) { const assistant = await ai.beta.assistants.create({ name: "SICP Translator", instructions: `You are a professional translator with high technical skills in computer science. @@ -23,7 +23,7 @@ export default async function createAssistant(language: string, ai: OpenAI) { tools: [{ type: "file_search" }] }); - const fileStreams = [path.join(__dirname, "../ai_files", language, "dictionary.txt")].map( + const fileStreams = [path.join(__dirname, "../ai_files", langCode, "dictionary.txt")].map( path => fs.createReadStream(path) ); From dec7c1de33d90d2c89f34cf89cf6c5799038eede Mon Sep 17 00:00:00 2001 From: yihao Date: Tue, 15 Apr 2025 11:28:07 +0800 Subject: [PATCH 38/55] updated .env.example to reflect new parameters --- i18n/.env.example | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/i18n/.env.example b/i18n/.env.example index 4e6029a71..33e15b3cd 100644 --- a/i18n/.env.example +++ b/i18n/.env.example @@ -1,8 +1,28 @@ API_KEY= -AI_MODEL=gpt-4o -# maximum number of characters to be sent for translation per batch -MAX_LEN=2000 -#Optional + +# Model to be used for translation +# ensure that selected assistant supports file search API +# models that are tried and shown to work includes: o3-mini, 4o and 4o mini +AI_MODEL=gpt-4.1-nano + +# maximum number of characters to be sent for translation per batch, default: 3000 +# smaller number of characters could lead to greater translation quality +# but worse continuity +MAX_LEN=5000 + +#Optional, only specify if calling alternative LLM provider +# Note: We require the Assistant API which only OpenAI provides as of 13 April 2025 AI_BASEURL= + # maximum number of concurent translations that are allowed -MAX_TRANSLATION_NO=10 \ No newline at end of file +# if unspecified default to 5 +MAX_TRANSLATION_NO=10 + +# Number of previous messages to be referred to in a thread for LLM Translation, if unspecified, default to 3 +# Higher context size might lead to greater continuity in translation but higher cost and lower speed. +CONTEXT=3 + +# Number of token per query which when exceeded will log an error +# Formula to derive an adequate value: MAX_LEN * CONTEXT * k, where k is a constant +# Recommended k = 1.5 +TOKEN_WARNING=7000 \ No newline at end of file From 4ca88aa781ad3e45fa4acb4729265b50b1e04c03 Mon Sep 17 00:00:00 2001 From: yihao Date: Tue, 15 Apr 2025 11:53:44 +0800 Subject: [PATCH 39/55] added new ignored tags --- i18n/controllers/recurTranslate.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 46ae3fc73..3299a53db 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -26,7 +26,10 @@ const ignoredTags = [ "SCHEME", "LONG_PAGE", "LABEL", - "HISTORY" + "HISTORY", + "REF", + "FIGURE", + ]; const MAXLEN = Number(process.env.MAX_LEN) || 3000; From 8989ffac6f867973ed90393fca20f2e5091f6b63 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:34:26 +0800 Subject: [PATCH 40/55] add workflows for ai translations translate-changed.yml and translate-everything.yml --- .github/workflows/translate-changed.yml | 51 ++++++++++++++++++++++ .github/workflows/translate-everything.yml | 39 +++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 .github/workflows/translate-changed.yml create mode 100644 .github/workflows/translate-everything.yml diff --git a/.github/workflows/translate-changed.yml b/.github/workflows/translate-changed.yml new file mode 100644 index 000000000..73292ca28 --- /dev/null +++ b/.github/workflows/translate-changed.yml @@ -0,0 +1,51 @@ +on: + workflow_dispatch: + workflow_call: + +concurrency: + group: translate-changed + cancel-in-progress: true + +jobs: + translate-changed: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - name: cd + run: | + cd i18n + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + - name: Fetch Yarn Dependencies + run: | + yarn install + - name: Clone translated_xmls + run: | + git clone -b translated_xmls https://github.com/source-academy/sicp.git translation_output + - name: Get Changed Files + id: changed-files + uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46 + with: + files: | + xml/**.xml + - name: Create .env + run: | + touch .env + echo API_MODEL=gpt-4.1-mini >> .env + echo API_KEY=${{ secrets.OPENAI_KEY }} >> .env + # echo API_KEY=${{ secrets.OPENAI_KEY2 }} >> .env + - name: Translate Changed Files + if: steps.changed-files.outputs.anychanged == 'true' + env: + CHANGED_FILES: ${{ steps.changed_files.outputs.all_changed_files }} + run: | + npx tsx index.ts "${CHANGED_FILES[@]}" + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./translation_output + force_orphan: false # leave the possibility for direct modification on translated xmls diff --git a/.github/workflows/translate-everything.yml b/.github/workflows/translate-everything.yml new file mode 100644 index 000000000..4df394a86 --- /dev/null +++ b/.github/workflows/translate-everything.yml @@ -0,0 +1,39 @@ +on: + workflow_dispatch: + worflow_call: + +concurrency: + group: translate-everything + cancel-in-progress: true + +jobs: + translate-everything: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - name: cd + run: | + cd i18n + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + - name: Fetch Yarn Dependencies + run: | + yarn install + - name: Create .env + run: | + touch .env + echo API_MODEL=gpt-4.1-mini >> .env + echo API_KEY=${{ secrets.OPENAI_KEY }} >> .env + # echo API_KEY=${{ secrets.OPENAI_KEY2 }} >> .env + - name: Run Translation + run: | + npx tsx index.ts + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./translation_output + force_orphan: false # leave the possiblity for direct modification on translated xmls From f87d8afb6da216cc6269babe11af9ffdd5c72b94 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:49:46 +0800 Subject: [PATCH 41/55] add back needsTranslation which is invoked by yarn trans abs --- i18n/index.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/i18n/index.ts b/i18n/index.ts index 8d19f4eed..168205056 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -174,6 +174,24 @@ async function setupCleanupHandlers() { }); } +function needsTranslation(enFilePath: string, lang: string): boolean { + const cnFilePath = enFilePath.replace( + enFilePath.sep + "en" + enFilePath.sep, + enFilePath.sep + ".." + enFilePath.sep + "i18n" + enFilePath.sep + "translation_output" + enFilePath.sep + lang + enFilePath.sep + ); + try { + const cnStats = await fs.promise.stat(cnFilePath); + if (!cnStats.isFile()) { + return true; + } + + const enStats = await fs.promise.stat(enFilePath); + return enStats.mtime > cnStats.mtime; + } catch (error) { + return true; + } +} + // Function to recursively find all XML files in a directory async function findAllXmlFiles(directory: string): Promise { const files = await fs.promises.readdir(directory); @@ -205,6 +223,7 @@ async function findAllXmlFiles(directory: string): Promise { // Get the absolute path to the xml/en directory using proper path resolution const enDirPath = path.resolve(__dirname, "../xml/en"); + let absent: boolean = false; if (process.argv[2] === "test") { if (process.argv.length !== 5) { @@ -219,7 +238,10 @@ async function findAllXmlFiles(directory: string): Promise { console.error('test error: ', e); } return; - } if (process.argv[2] === "all" || process.argv.length <= 2) { + } if (process.argv[2] === "all" || process.argv.length <= 2 || process.argv[2] === "abs") { + if (process.argv[2] === "abs") { + absent = true; + } // Find all XML files console.log(`Scanning directory: ${enDirPath}`); filestoTranslate = await findAllXmlFiles(enDirPath); @@ -250,6 +272,12 @@ async function findAllXmlFiles(directory: string): Promise { // Process each file in the batch, but handle errors individually const results = await Promise.allSettled( batch.map(async file => { + if (absent) { + if (!needsTranslation(file, lang)) { + console.log(`Skipped translation for ${file} to language ${lang} (yarn trans abs)`); + return { file, success: true }; + } + } try { console.log(`Starting translation for ${file}`); await translate(lang, file); From 3637fef1cce3c867defca66ead09af5d1ec38bf1 Mon Sep 17 00:00:00 2001 From: yihao Date: Wed, 16 Apr 2025 14:00:22 +0800 Subject: [PATCH 42/55] replaced problematic translations --- i18n/index.ts | 7 +- section2/section2.xml | 110 ++ section2/subsection1.xml | 820 ++++++++++ section2/subsection2.xml | 777 +++++++++ section2/subsection3.xml | 402 +++++ section2/subsection4.xml | 662 ++++++++ section2/subsection5.xml | 306 ++++ section2/subsection6.xml | 1166 ++++++++++++++ xml/zh/chapter1/section1/subsection6.xml | 948 ++++++----- xml/zh/chapter1/section1/subsection7.xml | 570 +++---- xml/zh/chapter1/section1/subsection8.xml | 1334 +++++++-------- xml/zh/chapter1/section2/section2.xml | 78 +- xml/zh/chapter1/section2/subsection1.xml | 769 ++++----- xml/zh/chapter1/section2/subsection2.xml | 709 ++++---- xml/zh/chapter1/section2/subsection3.xml | 451 ++---- xml/zh/chapter1/section2/subsection4.xml | 429 +++-- xml/zh/chapter1/section2/subsection5.xml | 347 ++-- xml/zh/chapter1/section2/subsection6.xml | 935 +++++------ xml/zh/chapter3/section2/subsection4.xml | 609 +++++++ xml/zh/chapter3/section3/subsection1.xml | 1875 +++++++++++++++++++++ xml/zh/chapter3/section3/subsection3.xml | 1065 ++++++++++++ xml/zh/chapter3/section3/subsection4.xml | 1881 ++++++++++++++++++++++ 22 files changed, 12677 insertions(+), 3573 deletions(-) create mode 100644 section2/section2.xml create mode 100644 section2/subsection1.xml create mode 100644 section2/subsection2.xml create mode 100644 section2/subsection3.xml create mode 100644 section2/subsection4.xml create mode 100644 section2/subsection5.xml create mode 100644 section2/subsection6.xml create mode 100644 xml/zh/chapter3/section2/subsection4.xml create mode 100644 xml/zh/chapter3/section3/subsection1.xml create mode 100644 xml/zh/chapter3/section3/subsection3.xml create mode 100644 xml/zh/chapter3/section3/subsection4.xml diff --git a/i18n/index.ts b/i18n/index.ts index 4173f28d0..c08bbff5f 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -6,7 +6,6 @@ import path from "path"; import { fileURLToPath } from "url"; import { dirname } from "path"; import OpenAI from "openai"; -import { permission } from "process"; // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); @@ -220,8 +219,8 @@ async function needsTranslation(enFilePath: string, lang: string): Promise + + + 程序 + 函数 + + 及其生成的过程 + + + + + + + + 我们现在已经考虑了编程的元素:我们使用了 + 原始算术操作,组合了这些操作,并且 + 通过 + + 将其定义为复合程序。 + 将其声明为复合函数。 + + 但这还不足以让我们说我们知道如何编程。我们的情况类似于一个已经 + 学会了国际象棋棋子如何移动规则的人,但对典型开局、战术或策略一无所知。像初学者的棋手一样, + 我们尚不知道该领域的常见使用模式。 + 我们缺乏对哪些移动是值得做的知识 + + (哪些程序值得定义) + (哪些函数值得声明) + + 我们缺乏预测移动后果的经验 + + (执行一个程序)。 + (执行一个函数)。 + + + + + 视觉化所考虑的行动后果的能力对于成为一名专家程序员至关重要,正如在任何合成的创造性活动中一样。例如,成为一名专家摄影师,必须学会如何观察一个场景,并知道每个可能的曝光选择下,图像中每个区域在打印时将显得多么黑暗,以及 + + 显影。 + 处理选项。 + + + 术语显影可能对数字时代的主流受众并不熟悉。 + + 只有这样,人才能向后推理,规划构图、照明、曝光和 + + 显影 + 处理 + + 以获得所需的效果。因此编程也是如此,我们在规划一个过程的行动方案,并通过程序控制该过程。要成为专家,我们必须学会可视化各种类型生成的 + + 程序。 + 函数。 + + 只有在我们培养出这样的技能之后,才能可靠地构造出表现出所需行为的程序。 + + + + 一个 + + 程序 + 函数 + + 是一个 + 模式作为过程的局部演化模式 + 用于计算过程的局部演化的模式。它指定了过程的每个阶段如何建立在上一个阶段之上。我们希望能够对局部 + 过程的局部演化 + 过程局部演化 + 已由 + + 程序 + 函数 + + 指定的过程的整体或全局行为做出陈述。一般来说,这很难做到,但我们至少可以尝试描述一些典型的过程演化模式。 + + + + 在本节中,我们将检查一些由简单 + + 程序 + 函数 + + 生成的常见形状。我们还将研究这些过程消耗时间和空间这两种重要计算资源的速率。我们将考虑的 + + 程序 + 函数 + + 非常简单。它们的作用类似于摄影中的测试模式:作为过于简化的原型模式,而不是实际的例子。 + + + + &subsection1.2.1; + + + &subsection1.2.2; + + + &subsection1.2.3; + + + &subsection1.2.4; + + + &subsection1.2.5; + + + &subsection1.2.6; + + diff --git a/section2/subsection1.xml b/section2/subsection1.xml new file mode 100644 index 000000000..34ad06554 --- /dev/null +++ b/section2/subsection1.xml @@ -0,0 +1,820 @@ + + Linear Recursion and Iteration + + + iterative processrecursive process vs. + recursive processiterative process vs. + + + We begin by considering the + factorial + factorial function, defined by + + \[ + \begin{array}{lll} + n! &=& n\cdot(n-1)\cdot(n-2)\cdots3\cdot2\cdot1 + \end{array} + \] + + There are many ways to compute factorials. One way is to make use of + the observation that $n!$ is equal to + $n$ times $(n-1)!$ for + any positive integer$n$: + + \[ + \begin{array}{lll} + n! &=& n\cdot\left[(n-1)\cdot(n-2)\cdots3\cdot2\cdot1\right] \quad = \quad n \cdot(n-1)! + \end{array} + \] + + Thus, we can compute $n!$ by computing + $(n-1)!$ and multiplying the + result by $n$. If we add the stipulation that 1! + is equal to 1, + this observation translates directly into a + + procedure: + computer function: + + + factoriallinear recursive version + factorial_definition + factorial_example + 120 + +(define (factorial n) + (if (= n 1) + 1 + (* n (factorial (- n 1))))) + + +function factorial(n) { + return n === 1 + ? 1 + : n * factorial(n - 1); +} + + + + + factorial_example + +(factorial 5) + + +factorial(5); + + + + + + We can use the substitution model of + section to watch this + substitution model of procedure applicationshape of process + procedure in action computing 6!, as shown in + figure. +
    + + A linear recursive process for computing 6!. + +
    + + + We can use the substitution model of + section to watch this + substitution model of function applicationshape of process + function in action computing 6!, as shown in + figure. + + + +
    +
    + + A linear recursive process for computing 6!. + +
    +
    +
    +
    +
    + + + Now lets take a different perspective on computing factorials. We + could describe a rule for computing $n!$ by + specifying that we first multiply 1 by 2, then multiply the result by 3, + then by 4, and so on until we reach $n$. + More formally, we maintain a running product, together with a counter + that counts from 1 up to $n$. We can describe + the computation by saying that the counter and the product simultaneously + change from one step to the next according to the rule + + \[ + \begin{array}{lll} + \textrm{product} & \leftarrow & \textrm{counter} \cdot \textrm{product}\\ + \textrm{counter} & \leftarrow & \textrm{counter} + 1 + \end{array} + \] + +and stipulating that $n!$ is the value of the + product when the counter exceeds $n$. + + + + Once again, we can recast our description as a + + procedure + function + + for computing + factorials:In a real program we would probably use the + block structure introduced in the last section to hide the + + + definition of fact-iter: + + + declaration of fact_iter: + + + + + factorial_example + 120 + +(define (factorial n) + (define (iter product counter) + (if (> counter n) + product + (iter (* counter product) + (+ counter 1)))) + (iter 1 1)) + + +function factorial(n) { + function iter(product, counter) { + return counter > n + ? product + : iter(counter * product, + counter + 1); + } + return iter(1, 1); +} + + + We avoided doing this here so as to minimize the number of things to + think about at + once. + + factoriallinear iterative version + factorial_iterative_definition + factorial_example + +(define (factorial n) + (fact-iter 1 1 n)) + +(define (fact-iter product counter max-count) + (if (> counter max-count) + product + (fact-iter (* counter product) + (+ counter 1) + max-count))) + + +function factorial(n) { + return fact_iter(1, 1, n); +} +function fact_iter(product, counter, max_count) { + return counter > max_count + ? product + : fact_iter(counter * product, + counter + 1, + max_count); +} + + + As before, we can use the substitution model to visualize the process + + +
    + + A linear iterative process for computing + $6!$. + +
    + of computing $6!$, as shown in + figure. + + + + + +
    +
    + + A linear recursive process for computing 6!. + +
    +
    +
    +
    + + A linear iterative process for computing + $6!$. + +
    + of computing $6!$, as shown in + figure. +
    +
    +
    + + + Compare the two processes. From one point of view, they seem hardly + different at all. Both compute the same mathematical function on the + same domain, and each requires a number of steps proportional to + $n$ + to compute $n!$. Indeed, both processes even + carry out the same sequence of multiplications, obtaining the same sequence + of partial products. On the other hand, when we consider the + shape of a process + processshape of + shapes of the two processes, we find that they evolve quite + differently. + + + + Consider the first process. The substitution model reveals a shape of + expansion followed by contraction, indicated by the arrow in + figure. + The expansion occurs as the process builds up a chain of + deferred operations + deferred operations (in this case, a chain of multiplications). + The contraction occurs as the operations are actually performed. This + type of process, characterized by a chain of deferred operations, is called a + recursive process + processrecursive + recursive process. Carrying out this process requires that the + interpreter keep track of the operations to be performed later on. In the + computation of $n!$, the length of the chain of + deferred multiplications, and hence the amount of information needed to + keep track of it, + linear growth + grows linearly with $n$ (is proportional to + $n$), just like the number of steps. + Such a process is called a + recursive processlinear + linear recursive process + processlinear recursive + linear recursive process. + + + + By contrast, the second process does not grow and shrink. At each + step, all we need to keep track of, for any $n$, + are the current values of the + + variables + names + + product, counter, + and + + max-count. + max_count. + + We call this an + iterative process + processiterative + iterative process. In general, an iterative process is one whose + state can be summarized by a fixed number of + state variable + state variables, together with a fixed rule that describes how + the state variables should be updated as the process moves from state to + state and an (optional) end test that specifies conditions under which the + process should terminate. In computing $n!$, the + number of steps required grows linearly with $n$. + Such a process is called a + iterative processlinear + linear iterative process + processlinear iterative + linear iterative process. + + + + + The contrast between the two processes can be seen in another way. + In the iterative case, the state variables provide a complete description of + the state of the process at any point. If we stopped the computation between + steps, all we would need to do to resume the computation is to supply the + interpreter with the values of the three state variables. Not so with the + recursive process. In this case there is some additional + hidden information, maintained by the interpreter and not + contained in the state variables, which indicates where the process + is in negotiating the chain of deferred operations. The longer the + chain, the more information must be maintained.When we discuss the + implementation of + + procedures + functions + + on register machines in chapter, we will see that any iterative + process can be realized in hardware as a machine that has a + fixed set of registers and no auxiliary memory. In contrast, realizing a + recursive process requires a machine that uses an + auxiliary data structure known as a + stack + stack. + substitution model of procedurefunction applicationshape of process + + + + In contrasting iteration and recursion, we must be careful not to + confuse the notion of a + recursive procedurefunctionrecursive process vs. + recursive processrecursive procedurefunction vs. + recursive process with the notion of a recursive + + + procedure. + + + function. + + + When we describe a + + procedure + function + + as recursive, we are referring to the syntactic fact that the + + procedure definition + function declaration + + refers (either directly or indirectly) to the + + procedure + function + + itself. But when we describe a process as following a pattern that is, say, + linearly recursive, we are speaking about how the process evolves, not + about the syntax of how a + + procedure + function + + is written. It may seem disturbing that we refer to a recursive + + procedure + function + + such as + + fact-iter + fact_iter + + as generating an iterative process. However, the process really is + iterative: Its state is captured completely by its three state variables, + and an interpreter need keep track of only three + + variables + names + + in order to execute the process. + + + + One reason that the distinction between process and + + procedure + function + + may be confusing is that most implementations of common languages + + + (including + Adarecursive procedures + Pascalrecursive procedures + Crecursive procedures + Ada, Pascal, and C) + + + (including + Crecursive functions in + C, + Java, recursive functions in + Java, and + Python, recursive functions in + Python) + + + are designed in such a way that the interpretation of + any recursive + + procedure + function + + consumes an amount of memory that grows with the number of + + procedure + function + + calls, even when the process described is, in principle, iterative. + As a consequence, these languages can describe iterative processes only + by resorting to special-purpose + looping constructs + looping constructs such as + $\texttt{do}$, + $\texttt{repeat}$, + $\texttt{until}$, + $\texttt{for}$, and + $\texttt{while}$. + The implementation of + + Scheme + JavaScript + + we shall consider in chapter does not share this defect. It will + execute an iterative process in constant space, even if the iterative + process is described by a recursive + + procedure. + function. + + + + An implementation with this property is called + tail recursion + tail-recursive. With a tail-recursive implementation, + iteration can be expressed using the ordinary procedure + iterative processimplemented by procedure call + call mechanism, so that special iteration constructs are useful only as + syntactic sugarlooping constructs as + syntactic sugar.Tail recursion has long been + known as a compiler optimization trick. A coherent semantic basis for + tail recursion was provided by + Hewitt, Carl Eddie + Carl Hewitt (1977), who explained it in + message passingtail recursion and + terms of the message-passing model of computation that we + shall discuss in chapter. Inspired by this, Gerald Jay Sussman + and + Steele, Guy Lewis Jr. + Guy Lewis Steele Jr.(see Steele 1975) + constructed a tail-recursive interpreter for Scheme. Steele later showed + how tail recursion is a consequence of the natural way to compile + procedure + Sussman, Gerald Jay + calls (Steele 1977). + The IEEE standard for Scheme requires that Scheme implementations + tail recursionSchemein Scheme + be tail-recursive. + + + An implementation with this property is called + tail recursion + tail-recursive.Tail recursion has long been + known as a compiler optimization trick. A coherent semantic basis for + tail recursion was provided by + Hewitt, Carl Eddie + Carl Hewitt (1977), who explained it in + message passingtail recursion and + terms of the message-passing model of computation that we + shall discuss in chapter. Inspired by this, Gerald Jay Sussman + and + Steele, Guy Lewis Jr. + Guy Lewis Steele Jr.(see Steele 1975) + constructed a tail-recursive interpreter for Scheme. Steele later showed + how tail recursion is a consequence of the natural way to compile + function calls + Sussman, Gerald Jay + (Steele 1977). + The IEEE standard for Scheme requires that Scheme implementations + tail recursionSchemein Scheme + tail recursionJavaScriptin JavaScript + Schemetail recursion in + JavaScripttail recursion in + be tail-recursive. The ECMA standard for JavaScript eventually followed + suit with ECMAScript 2015 (ECMA 2015). Note, however, + that as of this writing (2021), most implementations of JavaScript do + not comply with this standard with respect to tail recursion. + With a tail-recursive implementation, + iterative processimplemented by function call + iteration can be expressed using the ordinary function + call mechanism, so that special iteration constructs are useful only as + syntactic sugarlooping constructs as + syntactic sugar.Exercise + explores JavaScript's while loops as syntactic + sugar for functions that give rise to iterative processes. + The full language JavaScript, like other conventional languages, + features a plethora of syntactic + forms, all of which can be expressed more uniformly in the + language Lisp. + This, together with the fact that these constructs typically involve + semicolons whose placement rules are sometimes not obvious, + led Alan Perlis to quip: Syntactic sugar causes + cancer of the semicolon. + syntactic sugar + Perlis, Alan J.quips by + semicolon (;)cancer of + + + + iterative processrecursive process vs. + recursive processiterative process vs. + + + + + Each of the following two + + procedures + functions + + defines a method for adding two positive integers in terms of the + + procedures + functions + + inc, which increments its argument by 1, + and dec, which decrements its argument by 1. + + + inc_dec_definition + +(define (inc x) + (- x -1)) +(define (dec x) + (- x 1)) + + +function inc(x) { + return x + 1; +} +function dec(x) { + return x - 1; +} + + + + + plus_example + +(+ 4 5) + + +plus(4, 5); + + + + + inc_dec_definition + 9 + plus_example + +(define (+ a b) + (if (= a 0) + b + (inc (+ (dec a) b)))) + + +function plus(a, b) { + return a === 0 ? b : inc(plus(dec(a), b)); +} + + + + + inc_dec_definition + 9 + plus_example + +(define (+ a b) + (if (= a 0) + b + (+ (dec a) (inc b)))) + + +function plus(a, b) { + return a === 0 ? b : plus(dec(a), inc(b)); +} + + + + Using the substitution model, illustrate the process generated by each + + procedure + function + + in evaluating + + (+ 4 5). + plus(4, 5);. + + Are these processes iterative or recursive? + + + + + The process generated by the first function is recursive. + + +plus(4, 5) +4 === 0 ? 5 : inc(plus(dec(4), 5)) +inc(plus(dec(4), 5)) +... +inc(plus(3, 5)) +... +inc(inc(plus(2, 5))) +... +inc(inc(inc(plus(1, 5)))) +... +inc(inc(inc(inc(plus(0, 5))))) +inc(inc(inc(inc( 0 === 0 ? 5 : inc(plus(dec(0), 5)))))) +inc(inc(inc(inc( 5 )))) +inc(inc(inc( 6 ))) +inc(inc( 7 )) +inc( 8 ) +9 + + + The process generated by the second function is iterative. + + +plus(4, 5) +4 === 0 ? 5 : plus(dec(4), inc(5)) +plus(dec(4), inc(5)) +... +plus(3, 6) +... +plus(2, 7) +... +plus(1, 8) +... +plus(0, 9) +0 === 0 ? 9 : plus(dec(0), inc(9)) +9 + + + + + + + + + The following + + procedure + function + + computes a mathematical function called + Ackermanns function + function (mathematical)Ackermanns + Ackermanns function. + + + ackermann_definition + ackermann_example + +(define (A x y) + (cond ((= y 0) 0) + ((= x 0) (* 2 y)) + ((= y 1) 2) + (else (A (- x 1) + (A x (- y 1)))))) + + +function A(x, y) { + return y === 0 + ? 0 + : x === 0 + ? 2 * y + : y === 1 + ? 2 + : A(x - 1, A(x, y - 1)); +} + + + + What are the values of the following + + + expressions? + + + statements? + + + + ackermann_example + ackermann_definition + 1024 + +(A 1 10) + + +A(1, 10); + + + + + ackermann_definition + 65536 + +(A 2 4) + + +A(2, 4); + + + + + ackermann_definition + 65536 + +(A 3 3) + + +A(3, 3); + + + + Consider the following + + procedures, + functions, + + where A is the + + procedure defined + function declared + + above: + + fghk_definition + fghk_example + ackermann_definition + +(define (f n) (A 0 n)) + +(define (g n) (A 1 n)) + +(define (h n) (A 2 n)) + +(define (k n) (* 5 n n)) + + +function f(n) { + return A(0, n); +} +function g(n) { + return A(1, n); +} +function h(n) { + return A(2, n); +} +function k(n) { + return 5 * n * n; +} + + + + + fghk_example + 80 + fghk_definition + +(k 4) + + +k(4); + + + Give concise mathematical definitions for the functions computed by + the + + procedures + functions + + f, g, and + h for positive integer values of + $n$. For example, + $k(n)$ computes + $5n^2$. + + + The function $f(n)$ computes + $2 n$, + the function $g(n)$ computes + $2^n$, and + the function $h(n)$ computes + $2^{2^{\cdot^{\cdot^{\cdot^2}}}}$ + where the number of 2s in the chain of exponentiation is + $n$. + + + + + +
    diff --git a/section2/subsection2.xml b/section2/subsection2.xml new file mode 100644 index 000000000..65d191ac1 --- /dev/null +++ b/section2/subsection2.xml @@ -0,0 +1,777 @@ + + Tree Recursion + + + tree-recursive process + processtree-recursive + recursive processtree + + + Another common pattern of computation is called tree recursion. + As an example, consider computing the sequence of + Fibonacci numbers + Fibonacci numbers, + in which each number is the sum of the preceding two: + + \[\begin{array}{l} + 0, 1, 1, 2, 3, 5, 8, 13, 21, \ldots + \end{array}\] + + In general, the Fibonacci numbers can be defined by the rule + + \[\begin{array}{lll} + \textrm{Fib}(n) & = & \left\{ \begin{array}{ll} + 0 & \mbox{if $n=0$}\\ + 1 & \mbox{if $n=1$}\\ + \textrm{Fib}(n-1)+\textrm{Fib}(n-2) & \mbox{otherwise} + \end{array} + \right. + \end{array}\] + + We can immediately translate this definition into a recursive + + procedure + function + + for computing Fibonacci numbers: + + fibtree-recursive version + fib_definition + fib_example + +(define (fib n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else (+ (fib (- n 1)) + (fib (- n 2)))))) + + +function fib(n) { + return n === 0 + ? 0 + : n === 1 + ? 1 + : fib(n - 1) + fib(n - 2); +} + + + + + fib_example + fib_definition + 8 + +(fib 6) + + +fib(6); + + + + + +
    +
    + + The tree-recursive process generated in computing + (fib 5). + +
    + + +
    +
    + + The tree-recursive process generated in computing + fib(5). + +
    +
    +
    +
    + + + Consider the pattern of this computation. To compute + + (fib 5), + fib(5), + + we compute + + (fib 4) + fib(4) + + and + + (fib 3). + fib(3). + + To compute + + (fib 4), + fib(4), + + we compute + + (fib 3) + fib(3) + + and + + (fib 2). + fib(2). + + In general, the evolved process looks like a tree, as shown in + + + figure. + + + figure. + + + Notice that the branches split into + two at each level (except at the bottom); this reflects the fact that the + fib + + procedure + function + + calls itself twice each time it is invoked. + + + + This + + procedure + function + + is instructive as a prototypical tree recursion, but it is a terrible way to + compute Fibonacci numbers because it does so much redundant computation. + Notice in + + + figure + + + figure + + + that the entire + computation of + + + (fib 3)almost + half the workis + + + fib(3)almost + half the workis + + + duplicated. In fact, it is not hard to show that the number of times the + + procedure + function + + will compute + + (fib 1) + fib(1) + + or + + + (fib 0) + + + fib(0) + + + (the number of leaves in the above tree, in general) is precisely + $\textrm{Fib}(n+1)$. To get an idea of how + bad this is, one can show that the value of + $\textrm{Fib}(n)$ + exponential growthtree-recursiveof tree-recursive Fibonacci-number computation + grows exponentially with $n$. More precisely + (see exercise), + $\textrm{Fib}(n)$ is the closest integer to + $\phi^{n} /\sqrt{5}$, where + + \[\begin{array}{lllll} + \phi&=&(1+\sqrt{5})/2 & \approx & 1.6180 + \end{array}\] + + is the + golden ratio + golden ratio, which satisfies the equation + + \[\begin{array}{lll} + \phi^{2} &=&\phi + 1 + \end{array}\] + + Thus, the process uses a number of steps that grows exponentially with the + input. On the other hand, the space required grows only linearly with the + input, because we need keep track only of which nodes are above us in the + tree at any point in the computation. In general, the number of steps + required by a tree-recursive process will be proportional to the number of + nodes in the tree, while the space required will be proportional to the + maximum depth of the tree. + + + + We can also formulate an iterative process for computing the Fibonacci + numbers. The idea is to use a pair of integers $a$ + and $b$, initialized to + $\textrm{Fib}(1)=1$ and + $\textrm{Fib}(0)=0$, and to repeatedly apply the + simultaneous transformations + + \[\begin{array}{lll} + a & \leftarrow & a+b \\ + b & \leftarrow & a + \end{array}\] + + It is not hard to show that, after applying this transformation + $n$ times, $a$ and + $b$ will be equal, respectively, to + $\textrm{Fib}(n+1)$ and + $\textrm{Fib}(n)$. Thus, we can compute + Fibonacci numbers iteratively using the + + procedure + function + + + fiblinear iterative version + fib_example + 8 + +(define (fib n) + (fib-iter 1 0 n)) + +(define (fib-iter a b count) + (if (= count 0) + b + (fib-iter (+ a b) a (- count 1)))) + + +function fib(n) { + return fib_iter(1, 0, n); +} +function fib_iter(a, b, count) { + return count === 0 + ? b + : fib_iter(a + b, a, count - 1); +} + + + This second method for computing $\textrm{Fib}(n)$ + is a linear iteration. The difference in number of steps required by the two + methodsone linear in $n$, one growing as + fast as $\textrm{Fib}(n)$ itselfis + enormous, even for small inputs. + + + + One should not conclude from this that tree-recursive processes are useless. + When we consider processes that operate on hierarchically structured data + rather than numbers, we will find that tree recursion is a natural and + powerful tool.An example of this was hinted at in + section: The interpreter + itself evaluates expressions using a tree-recursive process. But + even in numerical operations, tree-recursive processes can be useful in + helping us to understand and design programs. For instance, although the + first + fib + + procedure + function + + is much less efficient than the second one, it is more straightforward, + being little more than a translation into + + + Lisp + + + JavaScript + + + of the definition of the Fibonacci sequence. To formulate the iterative + algorithm required noticing that the computation could be recast as an + iteration with three state variables. + + + + Example: Counting change + + counting change + + + It takes only a bit of cleverness to come up with the iterative Fibonacci + algorithm. In contrast, consider the following problem: + How many different ways can we make change of + + + \$1.00, + + + 1.00 (100 cents), + + + given half-dollars, quarters, dimes, nickels, and pennies + (50 cents, 25 cents, 10 cents, 5 cents, and 1 cent, respectively)? + More generally, can + we write a + + procedure + function + + to compute the number of ways to change any given amount of money? + + + + This problem has a simple solution as a recursive + + procedure. + function. + + Suppose we think of the types of coins available as arranged in some order. + Then the following relation holds: +
    + The number of ways to change amount $a$ using + $n$ kinds of coins equals +
      +
    • + the number of ways to change amount $a$ + using all but the first kind of coin, plus +
    • +
    • + the number of ways to change amount $a-d$ + using all $n$ kinds of coins, where + $d$ is the denomination of the first kind + of coin. +
    • +
    +
    +
    + + + To see why this is true, observe that the ways to make change can be divided + into two groups: those that do not use any of the first kind of coin, and + those that do. Therefore, the total number of ways to make change for some + amount is equal to the number of ways to make change for the amount without + using any of the first kind of coin, plus the number of ways to make change + assuming that we do use the first kind of coin. But the latter number is + equal to the number of ways to make change for the amount that remains after + using a coin of the first kind. + + + + Thus, we can recursively reduce the problem of changing a given amount to + problems of changing smaller amounts or using fewer kinds of coins. Consider + this reduction rule carefully, and convince yourself that we can use it to + describe an algorithm if we specify the following degenerate + cases:For example, work through in detail how the reduction rule + applies to the problem of making change for 10 cents using pennies and + nickels. + +
      +
    • + If $a$ is exactly 0, we should count that + as 1 way to make change. +
    • +
    • + If $a$ is less than 0, we should count + that as 0 ways to make change. +
    • +
    • If $n$ is 0, we should count that + as 0 ways to make change. +
    • +
    + We can easily translate this description into a recursive + + procedure: + function: + + + + count_change + count_change_definition + count_change_example + +(define (count-change amount) + (cc amount 5)) + +(define (cc amount kinds-of-coins) + (cond ((= amount 0) 1) + ((or (< amount 0) + (= kinds-of-coins 0)) 0) + (else (+ (cc amount + (- kinds-of-coins 1)) + (cc (- amount + (first-denomination + kinds-of-coins)) + kinds-of-coins))))) + +(define (first-denomination kinds-of-coins) + (cond ((= kinds-of-coins 1) 1) + ((= kinds-of-coins 2) 5) + ((= kinds-of-coins 3) 10) + ((= kinds-of-coins 4) 25) + ((= kinds-of-coins 5) 50))) + + +function count_change(amount) { + return cc(amount, 5); +} + +function cc(amount, kinds_of_coins) { + return amount === 0 + ? 1 + : amount < 0 || kinds_of_coins === 0 + ? 0 + : cc(amount, kinds_of_coins - 1) + + + cc(amount - first_denomination(kinds_of_coins), + kinds_of_coins); +} + +function first_denomination(kinds_of_coins) { + return kinds_of_coins === 1 ? 1 + : kinds_of_coins === 2 ? 5 + : kinds_of_coins === 3 ? 10 + : kinds_of_coins === 4 ? 25 + : kinds_of_coins === 5 ? 50 + : 0; +} + + + (The + + + first-denomination procedure + + + first_denomination function + + + takes as input the number of kinds of coins available and returns the + denomination of the first kind. Here we are thinking of the coins as + arranged in order from largest to smallest, but any order would do as well.) + We can now answer our original question about changing a dollar: + + + count_change_example + count_change_definition + 292 + +(count-change 100) + + +292 + + +count_change(100); + + +292 + + +
    + + + + Count-change + + The function count_change + + + generates a tree-recursive process with redundancies similar to those in + our first implementation of fib. + + + (It will take + quite a while for that + + 292 + 293 + + to be computed.) + + + On the other hand, it is not + obvious how to design a better algorithm for computing the result, and we + leave this problem as a challenge. The observation that a + efficiencytreeof tree-recursive process + tree-recursive process may be highly inefficient but often easy to specify + and understand has led people to propose that one could get the best of both + worlds by designing a smart compiler that could transform + tree-recursive + + procedures + functions + + into more efficient + + procedures + functions + + that compute the same result.One approach to coping with redundant + computations is to arrange matters so that we automatically construct a + table of values as they are computed. Each time we are asked to apply the + + procedure + function + + to some argument, we first look to see if the value is already stored in the + table, in which case we avoid performing the redundant computation. This + strategy, known as + tabulation + tabulation or + memoization + memoization, can be implemented in a + straightforward way. Tabulation can sometimes be used to transform processes + that require an exponential number of steps + + + (such as count-change) + + + (such as count_change) + + + into processes whose space and time requirements grow linearly with the + input. See exercise. + tree-recursive process + processtree-recursive + recursive processtree + counting change + + + + A function $f$ is defined by the + + + rule that + + + rules + + + $f(n)=n$ if $n < 3$ + and $f(n)={f(n-1)}+2f(n-2)+3f(n-3)$ if + $n\ge 3$. Write a + + procedure + JavaScript function + + that computes $f$ by means of a recursive process. + Write a + + procedure + function + + that computes $f$ by means of an iterative + process. + + + example_1.12_1 + 25 + +// iterative function +function f_iterative(n) { + return n < 3 + ? n + : f_iterative_impl(2, 1, 0, n - 2); +} +function f_iterative_impl(a, b, c, count) { + return count === 0 + ? a + : f_iterative_impl(a + 2 * b + 3 * c, a, b, count - 1); +} + + + + + example_1.12_2 + 25 + +//recursive function +function f_recursive(n) { + return n < 3 + ? n + : f_recursive(n - 1) + + 2 * f_recursive(n - 2) + + 3 * f_recursive(n - 3); +} + + + + + example_1.12_1 + +f_iterative(5); + + + + example_1.12_2 + +f_recursive(5); + + + + + + + The following pattern of numbers is called + Pascals triangle + Pascals triangle. + + \[ + { + \begin{array}{rrrrcrrrr} + & & & & 1 & & & & \\ + & & &1 & &1 & & & \\ + & &1 & & 2 & &1 & & \\ + &1 & &3 & &3 & &1 & \\ + 1 & & 4 & & 6 & & 4 & & 1 \\ + & & & & \ldots & & & & + \end{array}} + \] + + The numbers at the edge of the triangle are all 1, and each number inside + the triangle is the sum of the two numbers above it.The elements + of Pascals triangle are called the binomial coefficients, + because the $n$th row consists of + binomial coefficients + the coefficients of the terms in the expansion of + $(x+y)^n$. This pattern for computing the + coefficients + appeared in + PascalPascal, Blaise + Blaise Pascals 1653 seminal work on probability theory, + Trait du triangle arithmtique. + According to + Edwards, Anthony William Fairbank + Edwards (2019), the same pattern appears + in the works of + the eleventh-century Persian mathematician + Al-Karaji + Al-Karaji, + in the works of the twelfth-century Hindu mathematician + Bhaskara + Bhaskara, and + in the works of the + thirteenth-century Chinese mathematician + Yang Hui + Yang Hui. + + Write a + + procedure + function + + that computes elements of Pascals triangle by means of a recursive + process. + + + pascal_triangle + example_1.13 + +function pascal_triangle(row, index) { + return index > row + ? false + : index === 1 || index===row + ? 1 + : pascal_triangle(row - 1, index - 1) + + + pascal_triangle(row - 1, index); +} + + + + + + + example_1.13 + pascal_triangle + 4 + +pascal_triangle(5, 4); + + + + + + + + + Prove that $\textrm{Fib}(n)$ is the closest + integer to $\phi^n/\sqrt{5}$, where + $\phi= (1+\sqrt{5})/2$. + + + Hint: Let + $\psi= (1-\sqrt{5})/2$. Use induction and the + definition of the Fibonacci numbers (see + section) to prove that + $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$. + + + Hint: Use induction and the + definition of the Fibonacci numbers to prove that + $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$, + where + $\psi= (1-\sqrt{5})/2$. + + + + + First, we show that + $\textrm{Fib}(n) = + \dfrac{\phi^n-\psi^n}{\sqrt{5}}$, + where + $\psi = \dfrac{1-\sqrt{5}}{2}$ + using strong induction. +

    + $\textrm{Fib}(0) = 0$ + and + $\dfrac{\phi^0-\psi^0}{\sqrt{5}} = 0$ +

    + $\textrm{Fib}(1) = 1$ + and + $\dfrac{\phi^1-\psi^1}{\sqrt{5}} = + \dfrac{\dfrac{1}{2}\left(1+\sqrt{5} - 1 + \sqrt{5}\right)}{\sqrt{5}} = 1$ + +

    + So the statement is true for $n=0,1$. + Given $n \geq 1$, assume the proposition + to be true for $0, 1, \dots , n$. +

    + $\textrm{Fib}(n+1) = + \textrm{Fib}(n) + \textrm{Fib}(n-1) = + \dfrac{\phi^n-\psi^n + \phi^{n-1} - \psi^{n-1}}{\sqrt{5}}$ +

    + $= \dfrac{\phi^{n-1}(\phi + 1) - + \psi^{n-1}(\psi + 1)}{\sqrt{5}}$ + +

    + $=\dfrac{\phi^{n-1}(\phi^2) - \psi^{n-1}(\psi^2)}{\sqrt{5}} + = \dfrac{\phi^{n+1} - \psi^{n+1}}{\sqrt{5}}$, + so the statement is true. +

    + Notice that since $|\psi| < 1$ and + $\sqrt{5} > 2$, one has + $\left|\dfrac{\psi^n}{\sqrt{5}}\right| < + \dfrac{1}{2}$ +

    + Then the integer closest to + $\textrm{Fib}(n) + \dfrac{\psi^n}{\sqrt{5}} = + \dfrac{\phi^n}{\sqrt{5}}$ is + $\textrm{Fib}(n)$. +
    +
    + +
    diff --git a/section2/subsection3.xml b/section2/subsection3.xml new file mode 100644 index 000000000..72c92c0d0 --- /dev/null +++ b/section2/subsection3.xml @@ -0,0 +1,402 @@ + + + Orders of Growth + + + order of growth + + + The previous examples illustrate that processes can differ + considerably in the rates at which they consume computational + resources. One convenient way to describe this difference is to use + the notion of + processorder of growth of + order of growth to obtain a gross measure of the + processresources required by + resources required by a process as the inputs become larger. + + + + Let $n$ be a parameter that measures the size of + the problem, and let $R(n)$ be the amount + of resources the process requires for a problem of size + $n$. In our previous examples we took + $n$ to be the number for which a given + function is to be computed, but there are other possibilities. + For instance, if our goal is to compute an approximation to the + square root of a number, we might take + $n$ to be the number of digits accuracy required. + For matrix multiplication we might take $n$ to + be the number of rows in the matrices. In general there are a number of + properties of the problem with respect to which it will be desirable to + analyze a given process. Similarly, $R(n)$ + might measure the number of internal storage registers used, the + number of elementary machine operations performed, and so on. In + computers that do only a fixed number of operations at a time, the + time required will be proportional to the number of elementary machine + operations performed. + + + + We say that $R(n)$ has order of growth + 0e$\theta(f(n))$ (theta of $f(n)$) + theta$\theta(f(n))$ (theta of $f(n)$) + order notation + $\Theta(f(n))$, written + $R(n)=\Theta(f(n))$ (pronounced + theta of $f(n)$), if there are + positive constants $k_1$ and + $k_2$ independent of + $n$ such that + + \[ + \begin{array}{lllll} + k_1\,f(n) & \leq & R(n) & \leq & k_2\,f(n) + \end{array} + \] + + for any sufficiently large value of $n$. + (In other words, for large $n$, + the value $R(n)$ is sandwiched between + $k_1f(n)$ and + $k_2f(n)$.) + + + + order of growthlinear recursive process + linear recursive processorder of growth + recursive processlinear + For instance, with the linear recursive process for computing factorial + described in section the + number of steps grows proportionally to the input + $n$. Thus, the steps required for this process + grows as $\Theta(n)$. We also saw that the space + required grows as $\Theta(n)$. For the + order of growthlinear iterative process + linear iterative processorder of growth + iterative processlinear + iterative factorial, the number of steps is still + $\Theta(n)$ but the space is + $\Theta(1)$that is, + constant.These statements mask a great deal of oversimplification. + For instance, if we count process steps as machine operations + we are making the assumption that the number of machine operations needed to + perform, say, a multiplication is independent of the size of the numbers to + be multiplied, which is false if the numbers are sufficiently large. + Similar remarks hold for the estimates of space. Like the design and + description of a process, the analysis of a process can be carried out at + various levels of abstraction. + The + order of growthtree-recursive process + tree-recursive processorder of growth + recursive processtree + tree-recursive Fibonacci computation requires + $\Theta(\phi^{n})$ steps and space + $\Theta(n)$, where + $\phi$ is the golden ratio described in + section. + + + + Orders of growth provide only a crude description of the behavior of a + process. For example, a process requiring $n^2$ + steps and a process requiring $1000n^2$ steps and + a process requiring $3n^2+10n+17$ steps all have + $\Theta(n^2)$ order of growth. On the other hand, + order of growth provides a useful indication of how we may expect the + behavior of the process to change as we change the size of the problem. + For a + linear growth + $\Theta(n)$ (linear) process, doubling the size + will roughly double the amount of resources used. For an + exponential growth + exponential process, each increment in problem size will multiply the + resource utilization by a constant factor. In the remainder of + section + we will examine two algorithms whose order of growth is + logarithmic growth + logarithmic, so that doubling the problem size increases the resource + requirement by a constant amount. + order of growth + + + + Draw the tree illustrating the process generated by the + + count-change procedure + + count_change function + + + of section in making change for + 11 cents. What are the orders of growth of the space and number of steps + used by this process as the amount to be changed increases? + + The tree-recursive process generated in computing + cc(11, 5) is illustrated by the + image below, due to Toby Thain, assuming that the coin values in + first_denomination are + $\mathbb{C}_{1} = 1$, + $\mathbb{C}_{2} = 5$, + $\mathbb{C}_{3} = 10$, + $\mathbb{C}_{4} = 25$ and + $\mathbb{C}_{5} = 50$. + +
    + +
    + + Let us consider the process for evaluating + cc(n, k), which means the amount to + be changed is n and the number of + kinds of coins is k. Let us assume + the coin values are constants, not dependent on + n or + k. +

    + The space required for a tree-recursive process isas discussed in + sectionproportional to the + maximum depth of the tree. At each step from a parent to a child in the + tree, either n strictly decreases + (by a constant coin value) or k + decreases (by 1), and leaf nodes have an amount of at most 0 or a number + of kinds of coins of 0. Thus, every path has a length of + $\Theta(n + k)$, which is also the order of + growth of the space required for + cc(n, k). +

    + + Let us derive a function $T(n, k)$ such that + the time required for calculating + cc(n, k) has an order of growth of + $\Theta(T(n, k))$. The following argument is + due to Yati Sagade, including the illustrations + (Sagade 2015). + Let us start with the call tree for changing some amount + $n$ with just 1 kind of coin, i.e., + the call tree for cc(n, 1). + +
    + +
    + + We are only allowed here to use one kind of coin, with value + $\mathbb{C}_{1} = 1$. The red nodes are + terminal nodes that yield 0, the green node is a terminal node that + yields 1 (corresponding to the first condition in the declaration of + cc). Each nonterminal node spawns + two calls to cc, one (on the left) + with the same amount, but fewer kinds of coins, and the other (on the + right) with the amount reduced by 1 and equal kinds of coins. +

    + Excluding the root, each level has exactly 2 nodes, and there are + $n$ such levels. This means, the number of + cc calls generated by a single + cc(n, 1) call (including the original + call) is: + + \[ + T(n,1) = 2n + 1 = \Theta(n) + \] + + Next, we will look at the call tree of + cc(n, 2) + to calculate $T(n,2)$: + +
    + +
    + + Here, we are allowed to use two denominations of coins: + $\mathbb{C}_{2} = 5$ + and $\mathbb{C}_{1} = 1$. +

    + Each black node spawns a cc(m, 1) + subtree (blue), which we’ve already analyzed, and a + cc(m - 5, 2) subtree. The node + colored in red and green is a terminal node, but yields 0 if the amount + is less than zero and 1 if the amount is exactly zero. Sagade denotes + this final amount as $\epsilon$, which can + be $\le0$. +

    + Excluding the root and and the last level in this tree which contains the + red-green terminal node, there will be exactly + $\big\lfloor {\frac {n} {5}} \big\rfloor$ + levels. Now each of these levels contains a call to + cc(m, 1) (the blue nodes), each of + which, in turn, is $\Theta(n)$ in time. So each + of these levels contains $T(n,1) + 1$ calls to + cc. Therefore, the total number of + nodes (including the terminal node and the root) in the call tree for + cc(n, 2) is: + + \[ + T(n,2) = \big\lfloor {\frac {n} {5} } \big\rfloor ( T(n,1) + 1) + 2 = \big\lfloor {\frac {n} {5} } \big\rfloor ( 2n + 2 ) + 2 = \Theta(n^2) + \] + + Moving ahead, let’s take a look at the call tree of + cc(n, 3), i.e., we are now allowed + to use three denominations of coins, the new addition being + $\mathbb{C}_{3} = 10$: + +
    + +
    + + Here also, we see, similar to the previous case, that the total number of + calls to cc will be + + \[ + T(n,3) = \big\lfloor {\frac {n} {10} } \big\rfloor ( T(n,2) + 1) + 2 = \big\lfloor {\frac {n} {10} } \big\rfloor \times \Theta(n^2) + 2 = \Theta(n^3) + \] + + We can see a pattern here. For some $k$, + $k \gt 1$, we have, + + \[ + T(n,k) = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor ( T(n, k-1) + 1) + 2 + \] + + Here, $\mathbb{C}_{k}$ is the + $k^{th}$ coin denomination. We can expand this + further: + + \[ + T(n,k) + = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor ( T(n, k-1) + 1 ) + 2 + = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor + ( \big\lfloor {\frac {n} { \mathbb{C}_{k-1} } } \big\rfloor + (... \big\lfloor \frac {n} { \mathbb{C}_{2} } \big\rfloor (2n+1) ...) + ) + 2 + = \Theta(n^k) + \] + + Note that the actual values of the coin denominations have no effect on + the order of growth of this process, if we assume they are constants that + do not depend on n and + k. +
    + +
    + + + sineapproximation for small angle + The sine of an angle (specified in radians) can be computed by making use + of the approximation $\sin x\approx x$ + if $x$ is sufficiently small, and the + trigonometric identity + + \[ + \begin{array}{lll} + \sin x &=& 3\sin {\dfrac{x}{3}}-4\sin^3{\dfrac{x}{3}} + \end{array} + \] + + to reduce the size of the argument of $\sin$. + (For purposes of this exercise an angle is considered sufficiently + small if its magnitude is not greater than 0.1 radians.) These + ideas are incorporated in the following + + procedures: + functions: + + + cube + sine_definition + sine_example + abs_definition + +(define (cube x) (* x x x)) + +(define (p x) + (- (* 3 x) (* 4 (cube x)))) + +(define (sine angle) + (if (not (> (abs angle) 0.1)) + angle + (p (sine (/ angle 3.0))))) + + +function cube(x) { + return x * x * x; +} +function p(x) { + return 3 * x - 4 * cube(x); +} +function sine(angle) { + return ! (abs(angle) > 0.1) + ? angle + : p(sine(angle / 3)); +} + + + + + 0.9999996062176211 + sine_example + sine_definition + +(define pi 3.14159) +(sine (/ pi 2)) + + +sine(math_PI / 2); + + + +
      +
    1. How many times is the + + procedure + function + + p + applied when + + (sine 12.15) + sine(12.15) + + is evaluated? +
    2. +
    3. + What is the order of growth in space and number of steps (as a function + of$a$) used by the process generated + by the sine + + procedure + function + + when + + (sine a) + sine(a) + + is evaluated? +
    4. +
    + + +
      +
    1. The function p + will call itself recursively as long as the angle value is greater + than 0.1. There will be altogether 5 calls of + p, with arguments 12.15, 4.05, + 1.35, 0.45, 0.15 and 0.05. +
    2. +
    3. + The function sine gives + rise to a recursive process. In each recursive call, the + angle is divided by 3 + until its absolute value is smaller than 0.1. + Thus the number of steps and the space required has an order + of growth of $O(\log a)$. Note that the base of the logarithm + is immaterial for the order of growth because the logarithms + of different bases differ only by a constant factor. +
    4. +
    +
    +
    + +
    + +
    diff --git a/section2/subsection4.xml b/section2/subsection4.xml new file mode 100644 index 000000000..58b026303 --- /dev/null +++ b/section2/subsection4.xml @@ -0,0 +1,662 @@ + + 幂运算 + + + 幂运算 + + + 考虑计算给定数字的指数的问题。 + 我们希望有一个 + + 过程 + 函数 + + ,作为参数接受一个基数 +$b$ 和一个正整数指数 $n$ 和 + 计算 $b^n$ . 实现这一点的一种方法是通过 + 递归定义 + + \[ + \begin{array}{lll} + b^{n} &=& b\cdot b^{n-1}\\ + b^{0} &=& 1 + \end{array} + \] + + 这很容易转化为 + + 过程 + 函数 + + + exptlinear recursive version + expt_definition + expt_example + +(define (expt b n) + (if (= n 0) + 1 + (* b (expt b (- n 1))))) + + +function expt(b, n) { + return n === 0 + ? 1 + : b * expt(b, n - 1); +} + + + + expt_example + expt_definition + 81 + +(expt 3 4) + + +expt(3, 4); + + + 这是一个线性递归过程,要求 +$\Theta(n)$ 步骤和$\Theta(n)$ 空间。正如在阶乘中一样,我们 + 可以很容易地制定一个等效的线性迭代: + + exptlinear iterative version + expt_linear_definition + expt_example2 + 81 + +(define (expt b n) + (expt-iter b n 1)) + +(define (expt-iter b counter product) + (if (= counter 0) + product + (expt-iter b + (- counter 1) + (* b product)))) + + +function expt(b, n) { + return expt_iter(b, n, 1); +} +function expt_iter(b, counter, product) { + return counter === 0 + ? product + : expt_iter(b, counter - 1, b * product); +} + + + + expt_example2 + +(expt 3 4) + + +expt(3, 4); + + + + 该版本要求 $\Theta(n)$ 步骤和$\Theta(1)$ 空间。 + + + 我们可以通过使用 + 逐次平方法 + 更少的步骤来计算指数。 + 例如,不是计算 $b^8$ 为 + + \[ + \begin{array}{l} + b\cdot(b\cdot(b\cdot(b\cdot(b\cdot(b\cdot(b\cdot b)))))) + \end{array} + \] + + 我们可以使用三次乘法来计算它: + + \[ + \begin{array}{lll} + b^{2} &= & b\cdot b\\ + b^{4} &= & b^{2}\cdot b^{2}\\ + b^{8} &= & b^{4}\cdot b^{4} + \end{array} + \] + + + + + 这种方法对于以 2 为底的指数效果很好。如果我们使用规则,也可以在一般情况下利用逐次平方法来计算指数 + + \[ + \begin{array}{llll} + b^{n} &=& (b^{n/2})^{2} &\qquad\,\mbox{if}\ n\ \mbox{is even}\\ + b^{n} &=& b\cdot b^{n-1} &\qquad\mbox{if}\ n\ \mbox{is odd} + \end{array} + \] + + 我们可以将这种方法表示为 + + 过程: + 函数: + + + fast_expt + expt_log_definition + square_definition + even_definition + fast_expt_example + +(define (fast-expt b n) + (cond ((= n 0) 1) + ((even? n) + (square (fast-expt b (/ n 2)))) + (else + (* b (fast-expt b (- n 1)))))) + + + +function fast_expt(b, n) { + return n === 0 + ? 1 + : is_even(n) + ? square(fast_expt(b, n / 2)) + : b * fast_expt(b, n - 1); +} + + + + + fast_expt_example + expt_log_definition + 81 + +(fast-expt 3 4) + + +fast_expt(3, 4); + + + 用于测试一个整数是否为偶数的谓词是通过 + + + 原始过程 + 余数整数整除后的余数 + {\tt "%} (余数运算符)/// + {\tt "%} (余数)"% + 余数, + + + 余数整数整除后的余数 + {\tt "%} (余数运算符)/// + {\tt "%} (余数)/// + 运算符%, + 它计算整除后的余数, + + + 来定义的 + + is_even + even_definition + even_example + +(define (even? n) + (= (remainder n 2) 0)) + + +function is_even(n) { + return n % 2 === 0; +} + + + + + even_example + even_definition + +(even 7) + + +is_even(7); + + + 该过程通过 + + 快速幂函数 + fast_expt + + 增长阶对数 + 对数增长 + 以对数方式增长 +$n$ 在空间和步骤数量上都得到优化。为了证明这一点,可以观察到计算$b^{2n}$ 使用 + + 快速幂函数 + fast_expt + + 只需比计算 +$b^n$ . 因此,我们可以计算的指数的大小在每次允许的乘法中大约翻倍。因此,计算需要的乘法次数对于一个指数 $n$ 以接近对数的速度增长 $n$ 以 2 为底。该过程已 $\Theta(\log n)$ 增长。更准确地说,所需的乘法次数等于 $n$ 的以 2 为底的对数减去 1,加上 $n$ 的二进制表示中 1 的数量。这个总数总是小于 $n$ 的对数的两倍。定义阶次符号的任意常数 $k_1$ 和 $k_2$ 意味着,对于对数过程,所取对数的底数并不重要,因此所有这样的过程都可描述为 $\Theta(\log n)$。 + + + $\Theta(\log n)$ 增长和 $\Theta(n)$ 增长之间的差异在 $n$ 变大时变得非常明显。例如, + + 快速幂函数 + fast_expt + + 当 $n=1000$ 时,仅需 14 次乘法.你可能会想,为什么有人会关心将数字提高到 1000 次方。请参见 + section. + 同样可以利用逐次平方法的思想来设计一种迭代算法,该算法可以在以对数数量的步骤中计算指数(见 练习),尽管,正如迭代算法经常出现的那样,这种算法并没有像递归算法那样简洁地写下来.这个迭代算法是古老的。它出现在 + Chandah-sutra + Chandah-sutra 由 + Pingala, chrya + chrya,写于公元前 200 年之前。见 + Knuth, Donald E. + Knuth 1997b,第 4.6.3 节,关于此和其他幂运算方法的全面讨论和分析。 + 幂运算 + + + + 设计一个 + + 过程 + 函数 + + ,该过程演变为一个迭代的幂运算过程,使用逐次平方法,并使用对数数量的步骤,正如 + + 快速幂函数. + fast_expt. + + (提示:使用观察到的结果 +$(b^{n/2})^2 =(b^2)^{n/2}$ + ,保持与 + 指数一起 +$n$ + 和基数 +$b$ + 和一个额外的状态变量 +$a$ + ,并以这样的方式定义状态转变,使得乘积 +$a b^n$ + 状态在每个状态之间保持不变。在过程开始时 +$a$ + 被视为 1,答案由 + $n$ 的值给出 +$a$ + 在过程结束时。一般而言,定义一个 + 迭代过程的不变量 + 不变量,使其在状态之间保持不变,是思考 + 迭代过程算法设计 + 迭代算法设计的一种有效方法。) + + + + + fast_expt_iter + example_1.17 + 8 + even_definition + +function fast_expt_iter(a, b, n){ + return n === 0 + ? a + : is_even(n) + ? fast_expt_iter(a, b * b, n / 2) + : fast_expt_iter(a * b, b, n - 1); +} +function fast_expt(b, n){ + return fast_expt_iter(1, b, n); +} + + + + + example_1.17 + fast_expt_iter + +fast_expt(2, 3); + + + + + + + 本节中的幂运算算法基于通过重复乘法进行幂运算。类似地,可以通过重复加法执行整数乘法。以下乘法 + + 过程 + 函数 + + (假设我们的语言只能加法,而不能进行乘法)类似于 +expt + + + 过程: + 函数: + + + times_definition + times_example + +(define (* a b) + (if (= b 0) + 0 + (+ a (* a (- b 1))))) + + +function times(a, b) { + return b === 0 + ? 0 + : a + times(a, b - 1); +} + + + + times_example + 12 + times_definition + +(* 3 4) + + +times(3, 4); + + + 该算法所需的步骤数量是线性的 +b + 现在假设我们包括,与 + 加法一起, + + 操作 + 函数 + +double + ,它将一个整数翻倍,和 +halve + ,它将一个(偶)整数除以 2。使用这些,设计一个乘法 + + 过程 + 函数 + + 类似于 + + + 快速幂函数 + fast_expt + + 使用对数数量的步骤。 + + + example_1.18_definition + example_1.18 + even_definition + +function double(x) { + return x + x; +} + +function halve(x) { + return x / 2; +} + +function fast_times(a, b) { + return b === 1 + ? a + : a === 0 || b === 0 + ? 0 + : is_even(b) + ? double(fast_times(a, halve(b))) + : a + fast_times(a, b - 1); +} + + + + + + + example_1.18 + example_1.18_definition + 12 + +fast_times(3, 4); + + + + + + + + 使用练习的结果 + 和,设计一个 + + 过程 + 函数 + + ,生成一个迭代过程,以加法、翻倍和除以一半为基础 + 用对数数量的步骤实现两个整数的乘法。这个 + 算法,有时被称为 + 俄罗斯农民乘法法 + 俄罗斯农民法乘法 + 俄罗斯农民法 的乘法,是古老的。它的使用示例在 + 莱茵德纸草书 + 莱茵德纸草书中可以找到,这是现存的两部最古老的数学文献之一, + 约公元前 1700 年由一位名叫 + AhmoseAh-mose + Ah-mose 的埃及抄写员撰写(并复制自更古老的文献)。 + + + + fast_times_iter + example_1.19 + even_definition + +function double(x) { + return x + x; +} + +function half(x) { + return x / 2; +} + +function fast_times_iter(total, a, b) { + return b === 1 + ? total + a + : a === 0 || b===0 + ? 0 + : is_even(b) + ? fast_times_iter(total, double(a), half(b)) + : fast_times_iter(total + a, a, b - 1); +} + +function times(a, b) { + return fast_times_iter(0, a, b); +} + + + + + + example_1.19 + fast_times_iter + 12 + +times(3, 4); + + + + + + + + + 有一个聪明的算法,用于在 + fib对数版本 + 对数步骤中计算斐波那契数。回想状态变量的转换 +$a$$b$ + 在 + + + fib-iter + fib_iter + + 过程中,节: +$a\leftarrow a+b$$b\leftarrow a$ + 使用练习的结果 + 和,设计一个 + + 过程 + 函数 + + ,生成一个迭代过程,以加法、翻倍和除以一半为基础 + 用对数数量的步骤实现两个整数的乘法。这个 + 算法,有时被称为 + 俄罗斯农民乘法法 + 俄罗斯农民法乘法 + 俄罗斯农民法 的乘法,是古老的。它的使用示例在 + 莱茵德纸草书 + 莱茵德纸草书中可以找到,这是现存的两部最古老的数学文献之一, + 约公元前 1700 年由一位名叫 + AhmoseAh-mose + Ah-mose 的埃及抄写员撰写(并复制自更古老的文献)。 +$T$ + ,并观察应用 +$T$ + 一遍又一遍地 +$n$ + 次数,从 1 和 0 开始, + 产生这一对 +$\textrm{Fib}(n+1)$$\textrm{Fib}(n)$ + . 换句话说,斐波那契数是通过应用 +$T^n$ + . 也就是说,斐波那契数是通过应用 +$n$ + 次方的转换 +$T$ + 次方的转换 +$(1,0)$ + . 现在考虑 +$T$ + 特殊情况 +$p=0$$q=1$ + 在一系列变换中 +$T_{pq}$ + 在 +$T_{pq}$ + 变换序对 +$(a,b)$ + 根据 +$a\leftarrow bq+aq+ap$$b\leftarrow bp+aq$ + 应用这种变换 +$T_{pq}$ + 两次,效果与使用单一变换相同 +$T_{p'q'}$ + 的相同形式,并计算 +$p'$$q'$ + 以...的术语 +$p$ $q$ + 两次变换,这个效果与使用单一变换相同 +$T^n$ + 使用逐次平方法,如在 + + 快速幂函数 + fast_expt + + + 过程。 + 函数。 + + 将所有这些放在一起以完成以下 + + 过程, + 函数, + + 其运行在对数数量的步骤中:此练习是 + + + 提出的 + + + 由 + Stoy, Joseph E. + Joe Stoy,基于 + Kaldewaij, Anne + Kaldewaij 1990 中的一个例子。 + + fib_log_definition + even_definition + fib_example + +(define (fib n) + (fib-iter 1 0 0 1 n)) +(define (fib-iter a b p q count) + (cond ((= count 0) b) + ((even? count) + (fib-iter a + b + ?? ; compute p' + ?? ; compute q' + (/ count 2))) + (else (fib-iter (+ (* b q) (* a q) (* a p)) + (+ (* b p) (* a q)) + p + q + (- count 1))))) + + +function fib(n) { + return fib_iter(1, 0, 0, 1, n); +} +function fib_iter(a, b, p, q, count) { + return count === 0 + ? b + : is_even(count) + ? fib_iter(a, + b, + ??, // compute p' + ??, // compute q' + count / 2) + : fib_iter(b * q + a * q + a * p, + b * p + a * q, + p, + q, + count - 1); +} + + + + + fib_log_solution + example_1.20 + even_definition + +function fib(n) { + return fib_iter(1, 0, 0, 1, n); +} + +function fib_iter(a, b, p, q, count) { + return count === 0 + ? b + : is_even(count) + ? fib_iter(a, + b, + p * p + q * q, + 2 * p * q + q * q, + count / 2) + : fib_iter(b * q + a * q + a * p, + b * p + a * q, + p, + q, + count - 1); +} + + + + + + example_1.20 + fib_log_solution + 5 + +fib(5); + + + + + + + diff --git a/section2/subsection5.xml b/section2/subsection5.xml new file mode 100644 index 000000000..a29b0070d --- /dev/null +++ b/section2/subsection5.xml @@ -0,0 +1,306 @@ + + 最大公约数 + + + 最大公约数 + + + 两个整数的最大公约数 (GCD) 被定义为能够同时整除这两个数 $a$ 和 $b$ 的最大整数,并且没有余数。例如,16 和 28 的 GCD 是 4。在章中,当我们研究如何实现有理数算术时,我们将需要能够计算 GCD,以便将有理数简化为最简形式。(要将有理数简化为最简形式,我们必须将分子和分母都除以它们的 GCD。例如,16/28 简化为 4/7。) 找到两个整数 GCD 的一种方法是对它们进行因子分解并搜索共同因子,但还有一个著名的算法更为高效。 + + + + 欧几里得算法 + 算法的思想基于这样的观察:如果 +$r$ 是余数,当 +$a$ 被除以 +$b$ , 然后 的公因子 +$a$ 和 +$b$ 正是 的公因子 +$b$ 和 +$r$ . 因此,我们可以使用方程 + + \[\begin{array}{lll} + \textrm{GCD} (a, b) &=& \textrm{GCD}(b, r) + \end{array}\] + + 逐步将计算 GCD 的问题简化为计算越来越小的整数对的 GCD 的问题。例如, + + + + \[\begin{array}{lll} + \textrm{GCD}(206,40) & = & \textrm{GCD}(40,6) \\ + & = & \textrm{GCD}(6,4) \\ + & = & \textrm{GCD}(4,2) \\ + & = & \textrm{GCD}(2,0) \\ + & = & 2 + \end{array}\] + + 化简 +$\textrm{GCD}(206, 40)$ 到 +$\textrm{GCD}(2, 0)$ , 其结果是 2。可以显示,从任何两个正整数开始,进行反复化简将始终最终产生一对,其中第二个数字为 0。然后 GCD 就是这一对中的另一个数字。这个计算 GCD 的方法被称为 欧几里得算法欧几里得算法之所以如此称呼,是因为它出现在欧几里得的 欧几里得的几何原本欧几里得的 几何原本 几何原本(书 7,大约公元前 300 年)。根据 Knuth, Donald E. Knuth (1997a) 的说法,可以认为这是已知的最古老的非平凡算法。古埃及的乘法方法(练习)无疑更古老,但正如 Knuth 解释的,欧几里得算法是已知的最古老的被提出作为一般算法的方法,而不是一组插图示例。 + + + + 表达欧几里得算法很容易作为一个 + + 过程: + 函数: + + + gcd + gcd_definition + gcd_example + +(define (gcd a b) + (if (= b 0) + a + (gcd b (余数 a b)))) + + +function gcd(a, b) { + return b === 0 ? a : gcd(b, a % b); +} + + + + + gcd_example + gcd_definition + 4 + +(gcd 20 12) + + +gcd(20, 12); + + + 这生成了一个迭代过程,其步骤数量随着相关数字的对数增长。 + + + + 欧几里得算法所需步骤数量的对数增长与 + 斐波那契数欧几里得的 GCD 算法与 + 斐波那契数之间存在有趣的关系: +
    + 拉梅定理: + 拉梅拉梅定理 + 如果欧几里得的算法需要 +$k$ 步骤来计算某对的 GCD,那么对中的较小数字必须大于或等于 +$k$ +第 th 斐波那契数。 + +这个定理由 +拉梅拉梅 证明于1845年,他是一位法国数学家和工程师,主要以对数学物理的贡献而闻名。为了证明该定理,我们考虑对 +$(a_k ,b_k)$ , 其中 +$a_k\geq b_k$ , 其中欧几里得的算法在 +$k$ +步骤。证明基于这样的说法,如果 +$(a_{k+1},\ b_{k+1}) \rightarrow (a_{k},\ b_{k}) + \rightarrow (a_{k-1},\ b_{k-1})$ +是三个连续对在化简过程中,那么我们必须 +$b_{k+1}\geq b_{k} + b_{k-1}$ +要验证这一说法,考虑到化简步骤是通过应用 +$a_{k-1} = b_{k}$, + $b_{k-1} = + \textrm{remainder of}\ a_{k}\ \textrm{divided by}\ b_{k}$ +步骤。第二个方程意味着 +$a_{k} = qb_{k} + b_{k-1}$ +某个正整数 +$q$ +因为 +$q$ +至少必须为 1,我们有 +$a_{k} = qb_{k} + b_{k-1} \geq b_{k} + b_{k-1}$ +。而在之前的化简步骤中,我们有 +$b_{k+1}= a_{k}$ +。因此, +$b_{k+1} = a_{k}\geq b_{k} + b_{k-1}$ +。这验证了该声称。现在我们可以通过对 +$k$ +算法终止所需的步骤数。结果对于 +$k=1$ +,因为这仅仅要求 +$b$ +至少与 +$\text{Fib}(1)=1$ +。假设结果对于所有小于或等于 +$k$ +并建立结果 +$k+1$ +。设 +$(a_{k+1},\ b_{k+1})\rightarrow(a_{k},\ b_{k}) + \rightarrow(a_{k-1},\ b_{k-1})$ +在化简过程中是连对。根据我们的归纳假设,我们有 +$b_{k-1}\geq {\textrm{Fib}}(k-1)$ +和 +$b_{k}\geq {\textrm{Fib}}(k)$ +因此,将我们刚刚证明的结论与斐波那契数的定义结合起来得到 +$b_{k+1} \geq b_{k} + b_{k-1}\geq {\textrm{Fib}}(k) + + {\textrm{Fib}}(k-1) = {\textrm{Fib}}(k+1)$ +,这完成了拉梅定理的证明。 +
    +
    + + + + 我们可以利用这个定理来获得欧几里得算法的增长阶估计。设 $n$ 为两个输入中较小的一个 + + 过程. + 函数. + + 如果该过程需要 $k$ 步骤,则我们必须有 + $n\geq {\textrm{Fib}} (k)\approx\phi^k/\sqrt{5}$。 + 因此步骤数 $k$ 的增长与 + $n$ 的对数(以 $\phi$ 为底)相同。因此,增长阶为 + $\Theta(\log n)$。 + 最大公约数 + 欧几里得算法 + + + + + 由 + + 过程 + 函数 + + 生成的过程当然取决于解释器使用的规则。作为一个例子,考虑迭代 +gcd + +上述 + + 过程 + 函数 + + 。假设我们使用 + 正常序求值应用序 vs. + 应用序求值正常序 vs. + 正常序求值来解释这个 + + 过程 + 函数 + + ,正如在 + 一节中讨论的那样。(正常序求值规则 + + if + 条件表达式 + + 的描述见练习。) + 使用代换法(对于正常序),说明评估过程中的 + + (gcd 206 40) + gcd(206, 40) + + 生成的过程,并指示 +remainder +实际执行的操作。多少 +remainder +在正常序求值中实际执行的操作有多少 + + (gcd 206 40)? + gcd(206, 40)? + + 在应用序求值中又有多少? + + + +
      +
    1. +使用正常序求值,过程经历了 18 次余数操作。在评估条件时有 14 次,最终化简阶段有其余的操作。 + + +gcd(206, 40) +40 === 0 ? 206 : gcd(40, 206 % 40) +gcd(40, 206 % 40) +206 % 40 === 0 ? 40 : gcd(206 % 40, + 40 % (206 % 40)) +// remainder operation (1) +6 === 0 ? 40 : gcd(206 % 40, + 40 % (206 % 40)) +gcd(206 % 40, 40 % (206 % 40)) +40 % (206 % 40) === 0 + ? 206 % 40 + : gcd(40 % (206 % 40), + (206 % 40) % (40 % (206 % 40))) +// remainder operations (2) and (3) +4 === 0 + ? 206 % 40 + : gcd(40 % (206 % 40), + (206 % 40) % (40 % (206 % 40))) +gcd(40 % (206 % 40), (206 % 40) % (40 % (206 % 40))) +(206 % 40) % (40 % (206 % 40)) === 0 + ? 40 % (206 % 40) + : gcd((206 % 40) % (40 % (206 % 40)), + (40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40))) +// remainder operations (4), (5), (6), (7) +2 === 0 + ? 40 % (206 % 40) + : gcd((206 % 40) % (40 % (206 % 40)), + (40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)))) +gcd((206 % 40) % (40 % (206 % 40)), + (40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40))) +(40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40))) === 0 + ? (206 % 40) % (40 % (206 % 40)) + : gcd((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)), + ((206 % 40) % (40 % (206 % 40))) % + ((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)))) +// remainder operations + (8), (9), (10), (11), (12), (13), (14) +0 === 0 + ? (206 % 40) % (40 % (206 % 40)) + : gcd((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)), + ((206 % 40) % (40 % (206 % 40))) % + ((40 % (206 % 40)) % ((206 % 40) % (40 % + (206 % 40)))) +(206 % 40) % (40 % (206 % 40)) +// remainder operations (15), (16), (17), (18) +2 + + +
    2. + +使用应用序求值,过程执行了 4 次余数操作。 + + +gcd(206, 40) +40 === 0 ? 206 : gcd(40, 206 % 40) +gcd(40, 206 % 40) +// 余数操作 (1) +gcd(40, 6) +6 === 0 ? 40 : gcd(6, 40 % 6) +gcd(6, 40 % 6) +// 余数操作 (2) +gcd(6, 4) +4 === 0 ? 6 : gcd(4, 6 % 4) +gcd(4, 6 % 4) +// 余数操作 (3) +gcd(4, 2) +2 === 0 ? 4 : gcd(2, 4 % 2) +gcd(2, 4 % 2) +// 余数操作 (4) +gcd(2, 0) +0 === 0 ? 2 : gcd(0, 2 % 0) +2 + + + </LI> +
    +
    +
    +
    +
    +
    diff --git a/section2/subsection6.xml b/section2/subsection6.xml new file mode 100644 index 000000000..c2d4554f4 --- /dev/null +++ b/section2/subsection6.xml @@ -0,0 +1,1166 @@ + + + 示例:素性测试 + + + 素数测试 + 素数 + + + 本节描述了两种检查整数 $n$ 素性的 + 方法,一种增长阶为 $\Theta(\sqrt{n})$, + 另一种是增长阶为 $\Theta(\log n)$ 的 + 概率算法。本节末尾的练习建议了基于这些算法的编程项目。 + + + + 查找因子 + + + + 自古以来,数学家们对素数问题着迷,许多人致力于确定测试数字是否为素数的方法。测试一个数字是否为素数的一种方法是查找该数字的因子。以下程序查找给定数字$n$的最小整数因子(大于 1)。它通过从 2 开始依次测试整数来直接实现n的可除性。 + + find_divisor + divides + smallest_divisor + smallest_divisor_definition + square_definition + smallest_divisor_example + +(define (smallest-divisor n) + (find-divisor n 2)) + +(define (find-divisor n test-divisor) + (cond ((> (square test-divisor) n) n) + ((divides? test-divisor n) test-divisor) + (else (find-divisor n (+ test-divisor 1))))) + +(define (divides? a b) + (= (remainder b a) 0)) + + +function smallest_divisor(n) { + return find_divisor(n, 2); +} +function find_divisor(n, test_divisor) { + return square(test_divisor) > n + ? n + : divides(test_divisor, n) + ? test_divisor + : find_divisor(n, test_divisor + 1); +} +function divides(a, b) { + return b % a === 0; +} + + + + smallest_divisor_example + smallest_divisor_definition + 2 + +(smallest-divisor 42) + + +smallest_divisor(42); + + + + + + 我们可以如下测试一个数字是否为素数: + 当且仅当 $n$ 是其自身的最小因子时, + $n$ 才是素数。 + + is_prime + prime_definition + smallest_divisor_definition + prime_example + +(define (prime? n) + (= n (smallest-divisor n))) + + +function is_prime(n) { + return n === smallest_divisor(n); +} + + + + + prime_example + prime_definition + false + +(prime? 42) + + +is_prime(42); + + + + + + + + 终止测试 + + find-divisor + find_divisor + + 基于这样一个事实:如果 $n$ 不是素数, + 则它必须有一个小于或等于 $\sqrt{n}$ 的因子。如果 + $d$ 是 + $n$ 的因子,那么 $n/d$ 也是。 + 但 $d$ 和 $n/d$ 不能都大于 $\sqrt{n}$。 + 这意味着算法只需测试介于 1 和 $\sqrt{n}$ 之间的因子。因此,识别 $n$ 为素数所需步骤数的增长阶为 $\Theta(\sqrt{n})$。 + + + + 费马测试 + + + + The $\Theta(\log n)$ 素性测试基于数论中的一个结果,称为 + 费马素性测试 + 素数费马测试 + 费马的小定理。皮埃尔 + 费马费马,皮埃尔·德 + 费马(16011665)被认为是现代 + 数论 + 数论的创始人。他取得了许多重要的数论结果,但他通常只公布结果,没有提供证明。 + 费马的费马小定理证明 + 费马的小定理在1640年他写的一封信中被陈述。第一个已发表的证明由 + 欧拉,莱昂哈德费马的小定理的证明 + 欧拉在1736年给出(而在未发表的 + 莱布尼茨,戈特弗里德·威廉·冯费马的小定理的证明 + 莱布尼茨的手稿中找到了更早的相同证明)。费马最著名的结果被称为费马最后定理于1637年在他所持有的书籍算术(由三世纪的希腊数学家 + 丢番图算术,费马的版本 + 丢番图)中写下,并留下旁注我发现了一个真正非凡的证明, 但这段空白不够写下它。找到费马最后定理的证明成为数论中最著名的挑战之一。最终完整的解决方案由 + 威尔斯,安德鲁 + 普林斯顿大学的安德鲁·威尔斯在1995年给出。 + +
    + 费马的小定理: + 费马的费马的小定理 + 如果 $n$ 是一个素数,并且 + $a$ 是小于 $n$ 的任意正整数, + 那么 $a$ 的 $n$ 次幂同余于 + $a$ 模$n$。 +
    + (两个数字被称为 + 模 $n$ 同余 + 同余模 + $n$ 如果它们在除以 时有相同的余数 $n$ 。 一个数字的余数 $a$ 被除以 $n$ 也被称为 + 模 $n$ 余数 + 模 $n$ + 的余数 $a$ + $n$ ,或简称为 $a$ + $n$ 。) +
    + + + 如果 $n$ 不是素数,那么通常大多数小于 + $n$ 的数字 $a$ 将不满足上述关系。这导致了以下用于测试素性的算法: + 给定一个数 $n$,选择一个 + 随机数生成器素性素性测试中使用 + 随机数 $a < n$ 并计算 $a^n$ 模 + $n$ 的余数。如果结果不等于 + $a$,则 $n$ 一定不是素数。如果等于 + $a$,那么 $n$ 很可能是素数。现在选择另一个 + 随机数 $a$ 并用同样的方法测试它。如果它也满足这个方程,那么可以更有信心地认为 + $n$ 是素数。通过尝试越来越多的 $a$ 值,可以增加对结果的信心。这个算法被称为费马测试。 + + + + 要实现费马测试,我们需要一个 + + 过程 + 函数 + + 来计算一个数的 + 幂运算模 $n$ + : + + expmod + expmod_definition + expmod_example + even_definition + square_definition + +(define (expmod base exp m) + (cond ((= exp 0) 1) + ((even? exp) + (remainder + (square (expmod base (/ exp 2) m)) + m)) + (else + (remainder + (* base (expmod base (- exp 1) m)) + m)))) + + +function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? square(expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; +} + + + + + expmod_example + expmod_definition + 4 + +(expmod 4 3 5) + + +expmod(4, 3, 5); + + + 这与 + + fast-expt + fast_expt + + 在第节中的 + + 过程 + 函数 + + 非常相似。它使用逐次平方,因此步骤数与指数的增长呈对数关系。在指数 + $e$ 大于 1 的情况下,简化步骤基于这样的事实:对于任何整数 + $x$, + $y$ 和 $m$,我们可以通过分别计算 + $x$ 和 $y$ 对 $m$ 的余数, + 然后将这些结果相乘,最后取结果对 + $m$ 的余数来找到 + $x \times y$ 对 $m$ 的余数。例如,当 + $e$ 为偶数时,我们计算 $b^{e/2}$ 对 + $m$ 的余数,平方这个值,然后对 $m$ 取余。这种技术很有用,因为它意味着我们可以在计算过程中不用处理比 + $m$ 大得多的数字。(比较练习。) + + + + + 费马测试通过随机选择一个数字来进行 + $a$ 介于 1 和 + $n-1$ 之间,并检查它对 的模余数是否 + $n$ 的 + $n$ 次幂的 $a$ 是否等于 $a$ 。 随机数 + $a$ 是使用 + + + 过程 + random, + + + 原语函数 + math_random, + + + + 我们假设其作为 Scheme 的原语包括在内。 + + + + random (原语函数) + random + Random + 返回一个小于其整数输入的非负整数。因此,要获得 + 一个介于 1 和 $n-1$ 之间的随机数,我们调用 + random 的输入为 $n-1$ 并加 1: + + + math_random (原语函数) + math_randomMath.random + 返回一个小于1的非负数。因此,要获得 + 一个介于 1 和 $n-1$ 之间的随机数,我们将 + math_random 的返回值乘以 + $n-1$,用原语函数 + math_floor (原语函数) + math_floorMath.floor + math_floor 向下取整, + 并加 1: + + + + random_definition + +;; random is predefined in Scheme + + +function random(n) { + return math_floor(math_random() * n); +} + + + + fermat_test + fermat_test_definition + square_definition + expmod_definition + random_definition + fermat_test_example + +(define (fermat-test n) + (define (try-it a) + (= (expmod a n n) a)) + (try-it (+ 1 (random (- n 1))))) + + +function fermat_test(n) { + function try_it(a) { + return expmod(a, n, n) === a; + } + return try_it(1 + math_floor(math_random() * (n - 1))); +} + + + + + fermat_test_example + fermat_test_definition + true + +(fermat-test 97) + + +fermat_test(97); + + + + + + 下面的 + + 过程 + 函数 + + 根据参数指定的次数运行测试。如果测试每次都成功,其值为 true,否则为 false。 + + fast_is_prime + fast_prime_definition + square_definition + expmod_definition + random_definition + fermat_test_definition + fast_prime_example + +(define (fast-prime? n times) + (cond ((= times 0) true) + ((fermat-test n) + (fast-prime? n (- times 1))) + (else false))) + + +function fast_is_prime(n, times) { + return times === 0 + ? true + : fermat_test(n) + ? fast_is_prime(n, times - 1) + : false; +} + + + + fast_prime_example + fast_prime_definition + true + +(fast-prime? 97 3) + + +fast_is_prime(97, 3); + + + + + + 概率方法 + + + 概率算法 + 算法概率 + + + 费马测试与大多数熟悉的算法不同,后者能计算出保证正确的答案。而费马测试得到的答案只是可能正确。更确切地说,如果 + $n$ 在费马测试中失败,我们可确定 + $n$ 不是素数。但 $n$ 通过测试的事实,虽然是一个极强的指示,仍不能保证 + $n$ 是素数。我们想说的是,对于任何数字 + $n$,如果我们进行足够多次测试,并发现 + $n$ 每次都通过测试,那么我们素性测试中出错的概率可以降到任意小。 + + + + + 不幸的是,这一断言并不完全正确。确实存在一些数字能欺骗费马测试:数字 + $n$ 哪些非素数却具有这样的性质 + $a^n$ 同余于 $a$ 模 + $n$ 对于所有整数 + $a < n$ 。这类数字非常罕见,因此在实践中费马测试是相当可靠的。 + + 能欺骗费马测试的数字称为 + 卡迈克尔数 + 卡迈克尔数,对它们的研究不多,只知道它们非常稀少。在一亿以下有 255 个 + 卡迈克尔数。最小的几个是 561, 1105, + 1729, 2465, 2821, 和 6601。在随机选择的非常大的数字的素性测试中,遇到一个欺骗费马测试的值的机会小于 + 宇宙辐射 + 宇宙辐射造成计算机在进行所谓 + 正确算法时出错的几率。认为一个算法因第一原因而不足(而非第二原因)体现了 + 工程学 vs.数学 + 数学工程学 vs. + 数学与工程学的区别。 + + 费马测试有一些变种无法被愚弄。在这些测试中,就像费马方法一样,人们测试一个整数的素性 + $n$ 通过选择一个随机整数 + $a < n$ 并检查一些依赖于 的条件 + $n$ 和 + $a$ 。 (请参见练习 了解此类测试的示例。) + 另一方面,与费马测试相比,可以证明,对于任何 $n$ ,对于大多数整数,该条件不成立 + $a < n$ 除非 + $n$ 是素数。因此,如果 + $n$ 通过某个随机选择的测试 + $a$ ,则 的机会大于一半 + $n$ 是素数。如果 + $n$ 在两个随机选择下通过测试 + $a$ ,则 的机会大于 3/4 + $n$ 是素数。通过对更多随机选择的 值运行测试 + $a$ 我们可以将出错的概率降低到任意小。 + 费马素性测试 + 素数费马测试 + + + + 存在这样一种测试,可以证明其错误的概率变得任意小,这引发了人们对这类算法的兴趣,这些算法被称为概率算法。 +概率算法 +算法概率 +素数 + 在这个领域有大量的研究活动,概率算法已经成功应用于许多领域。 概率素数测试最显著的应用之一是在 + 密码学 + 密码学领域。 + + + 虽然现在仍计算上难以分解一个任意 200 位 + 数的因子,但可以用费马测试在几秒钟内检查这样一个数的素性。 + + + 截至编写时(2021 年),仍然计算困难以分解一个任意 300 位 + 数的因子,但可以用费马测试在几秒钟内检查这样一个数的素性。 + + + 这个事实构成了一种用于构建 + 不可破解的密码的技术基础,它由 + Ronald L. Rivest + Rivest、 + Adi Shamir + Shamir 和 + Leonard Adleman + Adleman (1977) 提出。最终的 + RSA 算法 + RSA 算法 已成为一种广泛用于提升电子通讯安全的技术。由于这一点和相关的发展,研究 + 素数与密码学 + 素数,曾经被认为是“纯”数学中仅为本身而研究的话题的典范,现在发现它对密码学、电子资金转账和信息检索有重要的实际应用。 + + + + + 使用 + + smallest-divisor + smallest_divisor + + + + 过程 + 函数 + + 找出以下数字的最小因子:199、1999、 + 19999。 + + + smallest_division_solution + 199 + smallest_divisor_definition + +smallest_divisor(199); +// smallest_divisor(1999); +// smallest_divisor(19999); + + + + + + + + + + + + + 大多数 Lisp 实现都包含一个名为 + runtime + 原语 过程函数 (用 ns 标记的不是 IEEE Scheme 标准中的)runtimeruntime (ns) + runtime + 的原语,它返回一个整数,指定系统已经运行的时间量(例如,以微秒为单位)。以下是 + timed-prime-test + 过程, + + + 假设一个无参数的原语函数 + get_time (原语函数) + get_timenew Date().getTime + get_time + 返回自协调世界时 1970 年 1 月 1 日星期四 00:00:00 以来的毫秒数。此日期称为 + UNIXepoch + UNIX 纪元,是处理时间的函数规范的一部分 + UNIX$^{\textrm{TM}}$ 操作系统。 + 以下是 + timed_prime_test + 函数, + + + 当用一个整数调用时 $n$ ,打印 + $n$ 并检查是否 + $n$ 是素数。如果 $n$ + 是素数, + + 过程 + 函数 + + 打印三个星号原语函数 display 返回其 + 参数,同时也打印它。在这里 + *** + 是一个 + 字符串,我们将其作为参数传递给 display 函数。 + 第节会更详细地介绍字符串。,然后是执行测试所用的时间。 + + + newline + + + + display (primitive function) + timed_prime_test + timed_prime_definition + prime_definition + timed_prime_example + +(define (timed-prime-test n) + (newline) + (display n) + (start-prime-test n (runtime))) + +(define (start-prime-test n start-time) + (if (prime? n) + (report-prime (- (runtime) start-time)))) + +(define (report-prime elapsed-time) + (display " *** ") + (display elapsed-time)) + + +function timed_prime_test(n) { + display(n); + return start_prime_test(n, get_time()); +} + +function start_prime_test(n, start_time) { + return is_prime(n) + ? report_prime(get_time() - start_time) + : false; +} + +function report_prime(elapsed_time) { + display(" *** "); + display(elapsed_time); + return true; +} + + + + timed_prime_example + timed_prime_definition + +(timed-prime-test 43) + + +timed_prime_test(43); + + + 使用这个 + + 过程, + 函数, + + 编写一个 + + 过程 + 函数 + + + + search-for-primes + + + search_for_primes + + + 用于检查指定范围内连续奇整数的素性。 + 使用您的 + + + 过程 + + + 函数 + + + 找出大于 1000, 大于 10,000, 大于 100,000, 大于 1,000,000 的三个最小素数。注意测试每个素数所需的时间。由于测试算法的增长阶为 + $\Theta(\sqrt{n})$ ,您应该预期测试大约 10,000 附近的素数需要大约 + $\sqrt{10}$ 是测试大约 1000 附近的素数的时间的几倍。您的计时数据是否支持这一点?10,000 和 1,000,000 的数据支持这个 + $\sqrt{n}$ + 预测?您的结果是否与程序在您的计算机上按与计算所需步骤数成比例的时间运行的概念兼容? + + + search_for_primes_definition + search_for_primes_example + timed_prime_definition + +function search_for_primes(start, times) { + return times === 0 + ? true + : start > 2 && start % 2 === 0 + ? search_for_primes(start + 1, times) + // if we get true, it's a prime + : timed_prime_test(start) + ? search_for_primes(start + 2, times - 1) + : search_for_primes(start + 2, times); +} + + + + + + search_for_primes_example + search_for_primes_definition + +search_for_primes(10000, 3); +// search_for_primes(10000000, 3); +// search_for_primes(10000000000, 3); + + + + + 时间数据相当清晰地支持了对于预测足够大的 $\sqrt{n}$,如 100,000 和 1,000,000。 + + + + + + 更高效版本的 + smallest_divisor更高效版本 + + + smallest-divisor + + + smallest_divisor + + + 过程函数 + 在本节开头展示的进行许多不必要的测试:在检查数字是否能被 2 整除后,就没有必要再检查它是否能被更大的偶数整除。这表明用于 + + + test-divisor + + + test_divisor + + + 的值不应该是 2、3、4、5、6、,而应该是 2、3、5、7、9、 + 。要实施这一更改, + + 定义一个过程 + 声明一个函数 + + next + 返回 3 如果其输入等于 2 + 否则返回其输入加2。修改 + + + smallest-divisor + + + smallest_divisor + + + + 过程 + 函数 + + 以使用 + + + (next test-divisor) + + + next(test_divisor) + + + 而非 + + (+ test-divisor 1). + test_divisor + 1. + + 拼入该修改版本的 + + + timed-prime-test + + + timed_prime_test + + + 的 + + smallest-divisor, + smallest_divisor, + + + 对在练习中找到的 12 个素数运行测试。 + 由于此修改将测试步骤减半,您应该预期其运行速度大约提高一倍。如果没有确认这一预期,观察到的两种算法速度的比率是什么,您如何解释它与 2 的差异? + + + better_smallest_divisor_solution + +function next(input) { + return input === 2 + ? 3 + : input + 2; +} + +function find_divisor(n, test_divisor) { + return square(test_divisor) > n + ? n + : divides(test_divisor, n) + ? test_divisor + : find_divisor(n, next(test_divisor)); +} + + +function square(x) { + return x * x; +} +function smallest_divisor(n) { + return find_divisor(n, 2); +} +function divides(a, b) { + return b % a === 0; +} +function is_prime(n) { + return n === smallest_divisor(n); +} +function timed_prime_test(n) { + display(n); + return start_prime_test(n, get_time()); +} +function start_prime_test(n, start_time) { + return is_prime(n) + ? report_prime(get_time() - start_time) + : true; +} +function report_prime(elapsed_time) { + display(" *** "); + display(elapsed_time); +} +function next(input) { + return input === 2 + ? 3 + : input + 2; +} +function find_divisor(n, test_divisor) { + return square(test_divisor) > n + ? n + : divides(test_divisor, n) + ? test_divisor + : find_divisor(n, next(test_divisor)); +} + +timed_prime_test(43); + + + + + 两种算法的速度比不完全是 2,但这可能是由于硬件/网络问题。与以前的解决方案相比,大约快 1.5 倍。 + + + + + 修改练习中的 + + + timed-prime-test + + + timed_prime_test + + + + 过程 + 函数 + + 以使用 + + + fast-prime? + + + fast_is_prime + + + (费马方法),并测试您在该练习中找到的每个 12 个素数。由于费马测试具有 + $\Theta(\log n)$ 增长,您会如何预期测试 1,000,000 附近素数的时间与测试 1000 附近素数的时间相比?您的数据是否支持这一点?您能解释发现的任何差异吗? + + + + mod_timed_prime_test_example + mod_timed_prime_test_solution + fast_prime_definition + +function timed_prime_test(n) { + display(n); + return start_prime_test(n, get_time()); +} +function start_prime_test(n, start_time) { + return fast_is_prime(n, math_floor(math_log(n))) + ? report_prime(get_time() - start_time) + : true; +} +function report_prime(elapsed_time) { + display(" *** "); + display(elapsed_time); +} + + + + + + mod_timed_prime_test_example + mod_timed_prime_test_solution + +timed_prime_test(10007); +// timed_prime_test(10009); +// timed_prime_test(10037); +// timed_prime_test(100003, 3); +// timed_prime_test(100019, 3); +// timed_prime_test(100043, 3); +// timed_prime_test(1000003, 3); +// timed_prime_test(1000033, 3); +// timed_prime_test(1000037, 3); + + + + + 使用 fast_is_prime 测试接近 1,000,000 附近素数的时间 + 大约为 4 毫秒,是测试接近 1,000 附近素数时间的 4 倍。对比使用 is_prime 达到的 8 毫秒, + 这是更快的。然而,尽管慢了 4 倍,这一事实不能让我们相信它具有比 $\Theta(\log n)$ 更大的增长率, + 因为这需要通过更大的数字进行测试以获得对函数增长更准确的理解。 + + + + + Alyssa P. Hacker 抱怨说我们在编写时做了很多额外的工作 + expmod 。毕竟,她说,既然我们已经知道如何计算指数,我们本可以简单地写 + + expmod + no_extra_work + expt_log_definition + expmod_example + +(define (expmod base exp m) + (remainder (fast-expt base exp) m)) + + +function expmod(base, exp, m) { + return fast_expt(base, exp) % m; +} + + + 她说得对吗? + 这个 + + 过程 + 函数 + + 能否同样用于我们的快速素数测试器?请解释。 + + + 从表面上看,Alyssa 的建议是正确的:她的 + expmod 函数计算 + $\textit{base}^{\textit{exp}}$,然后根据费马测试要求找到其模 $m$ 的余数。 +

    + 然而,对于较大的底数,Alyssa 的方法很快就会遇到限制,因为 JavaScript 使用 64 位表示数字,遵循双精度浮点标准。当数字变得如此之大以至于无法在此标准下精确表示时,结果变得不可靠。更糟糕的是,该方法可能超出了该标准中可表示的最大数字,导致计算错误。 +

    + 然而,对于较小的底数,Alyssa 的方法可能比原始 expmod 函数更快,因为它只会执行一次余数运算。 +
    +
    + + + + + Louis Reasoner 很难完成练习。 + 他的 + + + fast-prime? + + + fast_is_prime + + + 测试似乎比他的 + + + prime? + + + is_prime + + + 测试运行得更慢。Louis 叫来他的朋友 Eva Lu Ator 来帮忙。当他们检查 Louis的代码时,他们发现他重写了 + expmod + + 过程 + 函数 + + 使用显式乘法,而不是调用 + square : + + expmod + even_definition + expmod_example + +(define (expmod base exp m) + (cond ((= exp 0) 1) + ((even? exp) + (remainder + (* (expmod base (/ exp 2) m) + (expmod base (/ exp 2) m)) + m)) + (else + (remainder + (* base (expmod base (- exp 1) m)) + m)))) + + +function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? ( expmod(base, exp / 2, m) + * expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; +} + + + “我看不出这样做有什么区别,” + Louis 说。“但我看出来了。” Eva 说。“通过那样编写 + + 过程 + 函数 + , + 你已经将 + $\Theta(\log n)$ 过程转换为 + $\Theta(n)$ 过程。” 请解释。 + + + Eva 是对的:通过计算表达式: + + +(expmod(base, exp / 2, m) * expmod(base, exp / 2, m)) % m + + + 表达式 + expmod(base, exp / 2, m) + 在指数为偶数时的每一步计算中被重复计算,消除了快速幂算法的好处——即在指数为偶数时将指数减半——因此消除了使算法更快的特性。 + + + + + 证明脚注中列出的 + 卡迈克尔数 + 卡迈克尔数确实能欺骗费马测试。也就是说,编写一个 + + 过程 + 函数 + , + 接收一个整数 $n$ 并测试对于每一个 $a < n$, + $a^n$ 是否同余于 + $a$ 模 $n$,并在给定的卡迈克尔数上试验您的 + + 过程 + 函数 + 。 + + + carmichael + example_1.29 + even_definition + square_definition + +function carmichael(n) { + function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? square(expmod(base, exp / 2, m)) % m + : (base * expmod(base, exp - 1, m)) % m; + } + function fermat_test(n, a) { + return expmod(a, n, n) === a; + } + function iter(n, i) { + return i === n + ? true + : fermat_test(n, i) + ? iter(n, i + 1) + : false; + } + return iter(n, 2); +} + + + + + + + example_1.29 + carmichael + true + +carmichael(561); +// carmichael(1105); +// carmichael(1729); +// carmichael(2465); +// carmichael(2821); +// carmichael(6601); + + + + + + + + + 费马测试的一种变体,它不能被欺骗,被称为 + 素数米勒–拉宾测试 + 费马素性测试的变体 + 米勒–拉宾素性测试 + 米勒,Gary L. + 拉宾,Michael O. + 米勒–拉宾测试米勒 1976; + 拉宾 1980)。它从 + 费马的费马小定理替代形式 + 费马小定理的一个替代形式开始,该定理指出如果 + $n$ 是素数且 + $a$ 是小于的任意正整数 + $n$ ,那么 $a$ 次幂的 $(n-1)$ 次方同余于 1 模 $n$ 。 要测试一个数字的素性 $n$ 通过米勒–拉宾测试,我们选择一个随机数 $a < n$ 并将其提升为 + $a$$(n-1)$ 次方模 $n$ 使用 + expmod + + 过程。 + 函数。 + + 然而,每当我们在 + expmod ,我们检查是否发现了一个 + 1 的非平凡平方根模$n$, + 也就是不等于 1 或的一个数字 $n-1$ 其平方等于 1 模 $n$ 。可以证明,如果存在这样一个 1 的非平凡平方根,那么 + $n$ 不是素数。也可以证明如果 $n$ 是一个不是素数的奇数,那么至少对半数的数字 $a < n$ ,计算 $a^{n-1}$ 以这种方式将揭示 1 的非平凡平方根模 $n$ 。 + (这就是米勒–拉宾测试无法被欺骗的原因。)修改 + expmod + + 过程 + 函数 + + 以发出信号如果它发现 1 的非平凡平方根,并使用它来 + 实现米勒–拉宾测试,通过一个 + + 过程 + 函数 + + 类似于 + + fermat-test. + fermat_test. + + 检查您的 + + 过程 + 函数 + 通过测试各种已知的素数和非素数。提示:一种方便的方法是生成一个 +expmod 信号是让它返回0。 + + + + miller_rabin_test + example_1.30 + even_definition + square_definition + +function random(n) { + return math_floor(math_random() * n); +} +function miller_rabin_test(n) { + function expmod(base, exp, m) { + return exp === 0 + ? 1 + : is_even(exp) + ? square(trivial_test(expmod(base, + exp / 2, + m), + m)) + % m + : (base * expmod(base, exp - 1, m)) + % m; + } + function trivial_test(r, m) { + return r === 1 || r === m - 1 + ? r + : square(r) % m === 1 + ? 0 + : r; + } + function try_it(a) { + return expmod(a, n - 1, n) === 1; + } + return try_it(1 + random(n - 1)); +} +function do_miller_rabin_test(n, times) { + return times === 0 + ? true + : miller_rabin_test(n) + ? do_miller_rabin_test(n, times - 1) + : false; +} + + + + + + + example_1.30 + miller_rabin_test + true + +do_miller_rabin_test(104743, 1000); + + + + + + 素数测试 +
    diff --git a/xml/zh/chapter1/section1/subsection6.xml b/xml/zh/chapter1/section1/subsection6.xml index 461640af5..09c6b900b 100644 --- a/xml/zh/chapter1/section1/subsection6.xml +++ b/xml/zh/chapter1/section1/subsection6.xml @@ -5,17 +5,17 @@ - 目前我们可以定义的 + 我们此时可以定义的 - - procedure - + + 过程 + - function + 函数 - 类别的表达能力非常有限,因为我们无法进行测试,并依据测试结果执行不同的操作。 - + 类的表达能力非常有限,因为我们无法进行测试,也无法根据测试结果执行不同的操作。 + For instance, we cannot define a procedure that computes the absolute value @@ -101,30 +101,30 @@ which are associated with the values #t and #f respectively. - - - 我们在这里从条件表达式开始,因为它们更适合于代换模型。请注意,第 1.1.1 节到第 1.1.7 节中的所有函数的主体有一个单一的返回语句,这强调了条件表达式的重要性。条件语句将在 1.3.2 中介绍。 + + + 我们这里从条件表达式开始,因为它们更适合替换模型。注意第1.1.1节到第1.1.7节中的所有函数体都只有一个返回语句,这强调了条件表达式。条件语句将在第1.3.2节介绍。 - 例如,我们无法声明一个计算 + 例如,我们不能通过测试数字是否非负数,并根据规则对每种情况采取不同的操作来声明一个计算数字 绝对值 - 的函数,通过测试该数字是否为非负数,并根据规则在每种情况下采取不同的行动。 - + 绝对值的函数, + \[\begin{array}{lll} |x| & = & \left\{ \begin{array}{rl} - x & \mbox{if $x \geq 0$} \\ - -x & \mbox{otherwise} + x & \mbox{如果 $x \geq 0$} \\ + -x & \mbox{否则} \end{array} \right. \end{array}\] - - 我们简化例子,以便可以用一个条件表达式解决问题。 + + 我们简化这个例子,这样只需一个条件表达式就够了。 - 这种结构是一种 + 这个结构是一个 情况分析 - 情况分析,可以使用 - 条件表达式在 JavaScript 中编写。 - + 情况分析,可以用JavaScript中的 + 条件表达式写成 + abs abs_definition abs_example @@ -141,168 +141,154 @@ function abs(x) { abs(-5); - - 可以用英语表达为 如果 $x$ 大于或等于零,则返回 $x$; 否则 返回 $- x$。 - 条件表达式的一般形式是 - + + 用英语可以表达为如果 $x$ 大于等于零,则返回$x$;否则返回 $- x$ + 条件表达式的一般形式是 + -predicate ? consequent-expression : alternative-expression +谓词 ? 结果表达式 : 备选表达式 - + 条件 条件表达式 语法形式条件表达式 ?:;3 - 谓词条件的条件表达式 - truetrue (关键字) - falsefalse (关键字) + 谓词条件表达式之条件 + truetrue(关键字) + falsefalse(关键字) 关键字truetrue 关键字falsefalse - 表达式原语布尔 - 布尔值 (true, false) - 表达式以一个 - 谓词开始, - 也就是一个值为 - truefalse 的表达式,这在 JavaScript 中是两个特定的 - 布尔 值。 - 原语布尔表达式 + 表达式原始布尔型 + 布尔值(true, false) + 表达式以 + 谓词即一个值为 + truefalse的表达式开始,这是JavaScript中的 + 两个区分的布尔值。 + 原始布尔表达式 true 和 - false 显然评估为布尔值 true 和 false,分别。 - 谓词 - 后面跟着一个问号, - 后续表达式, - 一个冒号,最后是 - 替代表达式。 - - + false 分别简单地求值为布尔值true和false。 + 谓词 + 后接问号, + 结果表达式, + 冒号,最后是 + 备选表达式。 + + - + - Conditional expressions are - condevaluation of - evaluationof condof cond - evaluated as follows. The predicate - $\langle p_1 \rangle$ is evaluated first. - If its value is false, then - $\langle p_2 \rangle$ is evaluated. - If $\langle p_2 \rangle$s - value is also false, then - $\langle p_3 \rangle$ is evaluated. - This process continues until a predicate is - found whose value is true, in which case the interpreter returns the - value of the corresponding - consequentcondof cond clause - consequent expression - $\langle e \rangle$ of the - clause as the value of the conditional expression. If none of the - $\langle p \rangle$s - is found to be true, the value of the cond - is undefined. + 条件表达式的 + cond求值 + 求值cond 的求值cond + 评估过程如下。先对谓词 + $\langle p_1 \rangle$进行求值。 + 如果其值为 false,则对 + $\langle p_2 \rangle$ 进行求值。 + 如果< LATEXINLINE>$\langle p_2 \rangle$</LATEXINLINE> 的值也为 false,则对 + $\langle p_3 \rangle$进行求值。 + 该过程会持续,直到找到一个其值为 true 的谓词,届时解释器返回该子句对应的 + 后续项condcond 子句 + 后续表达式 + $\langle e \rangle$的值作为条件表达式的值。如果没有任何 + $\langle p \rangle$ 的值为 true,则该 + cond 的值未定义。 - + - 为了 - 求值条件表达式的 - 条件表达式的求值 - 评估一个条件表达式, - 解释器首先评估该表达式的 - 谓词。 - 如果 + 要 + 求值条件表达式求值 + 条件表达式求值 + 对条件表达式求值, + 解释器先对表达式的 + 谓词 + 进行求值。如果该 谓词 - 评估为 true,解释器将评估 - 后续条件表达式的条件表达式 - 后续表达式 并将其值作为条件的值返回。 - 如果 谓词 - 评估为 false,它将评估 - 替代条件表达式的条件表达式 - 替代表达式 并将其值作为条件的值返回。 - 条件表达式非布尔值作为谓词 - 条件语句 - - 在完整的 JavaScript 中接受任何值,而不仅仅是布尔值,作为评估 - 谓词 表达式的结果(有关详细信息,请参见脚注 - 在节中)。本书中的程序 - 仅使用布尔值作为条件的谓词。 + 的值为 true,则解释器对 + 后续项条件表达式的条件表达式的后续项 + 后续表达式求值,并返回其值作为条件表达式的值。 + 如果该 + 谓词 + 的值为 false,则解释器求值 + 备选项条件表达式的条件表达式的备选项 + 备选表达式,并返回其值作为该条件表达式的值。 + 条件表达式非布尔值谓词 + 在完整的 JavaScript 中,条件表达式接受任何值,而不仅仅是布尔值,作为求值谓词的结果(详见第注释, + 以及第节)。本书中的程序仅使用布尔值作为条件表达式的谓词。 - - + + - The word - predicate - predicate is used for procedures that return true - or false, as well as for expressions that evaluate to true or false. - The absolute-value procedure abs makes use of the - > (primitive numeric comparison predicate) - < (primitive numeric comparison predicate) - = (primitive numeric equality predicate) - primitive procedures (those marked ns are not in the IEEE Scheme standard)>> - primitive procedures (those marked ns are not in the IEEE Scheme standard)<< - primitive procedures (those marked ns are not in the IEEE Scheme standard)== - number(s)comparison of - number(s)equality of - equalitynumbersof numbers - primitive predicates >, - <, and - =. - Abs also uses - - (primitive subtraction procedure)as negation - primitive procedures (those marked ns are not in the IEEE Scheme standard)-- - the minus operator -, - which, when used with a single - operand, as in (- x), - indicates negation. These take two numbers as arguments and - test whether the first number is, respectively, greater than, less than, - or equal to the second number, returning true or false accordingly. + 术语 + 谓词 + 谓词用于表示返回true或false的过程,也用于表示求值为true或false的表达式。 + 取绝对值的过程abs使用了 + >(原始数字比较谓词) + <(原始数字比较谓词) + =(原始数字相等谓词) + 原始过程(标记为ns的非IEEE Scheme标准)>> + 原始过程(标记为ns的非IEEE Scheme标准)<< + 原始过程(标记为ns的非IEEE Scheme标准)== + 数字比较 + 数字相等比较 + 相等数值数值的相等 + 原始谓词>、 + < 以及 + = + abs还使用了 + -(原始减法过程)作为取负 + 原始过程(标记为ns的非IEEE Scheme标准)-- + 减号操作符-,当仅有一个操作数时,如(- x),表示取负值。 + 这些谓词接受两个数字作为参数,测试第一个数字是否分别大于、小于或等于第二个数字,并相应地返回true或false。 - + - 词 + 术语 谓词 - 谓词 用于返回 true 或 false 的运算符和函数,以及用于评估为 true 或 false 的表达式。绝对值函数 - abs 利用 - >= (数值比较运算符) - >= (数值比较) + 谓词用于返回true或false的操作符和函数,也用于求值为true或false的表达式。取绝对值函数 + abs使用了 + >=(数字比较操作符) + >=(数字比较) 数字比较 - 数字相等性 - 原语谓词 >=, - 一个接受两个数字作为参数并测试第一个数字是否大于或等于第二个数字的运算符,返回 true 或 false。 + 数字相等比较 + 原始谓词>=, + 该操作符接受两个数字作为参数,测试第一个数字是否大于或等于第二个数字,并相应地返回true或false。 - + - + - 另一种写绝对值过程的方法是 - + 写出取绝对值过程的另一种方式是 + abs abs_example - + (define (abs x) (cond ((< x 0) (- x)) (else x))) - - - 可以用英语表达为 - 如果 $x$ 小于零 - 则返回 $- x$; 否则返回 - $x$. - else (在 cond 中的特殊符号) - Else 是一个特殊符号,可以在 - cond 的最终子句中替代 $\langle p \rangle$。 - 这使得 cond 返回其值 - 对应的 - $\langle e \rangle$ - 每当所有先前的条款被跳过时。事实上,任何 - 始终评估为真值的表达式都可以在这里用作 - $\langle p \rangle$。 + + + 它可以用英语描述为 + 如果 $x$ 小于零 + 返回 $- x$;否则返回 + $x$ + elsecond中的特殊符号) + Else 是一个特殊符号,可以替代 + $\langle p \rangle$ 用于 + cond 的最后一个子句。 + 这使得 cond 当所有先前的子句都被跳过时, + 返回对应的 + $\langle e \rangle$ 的值。实际上,任何 + 总是求值为true的表达式都可以用作此处的 + $\langle p \rangle$ - + @@ -374,22 +360,23 @@ abs(-5); be single expressions. - + - - 如果我们更愿意单独处理零的情况,我们可以通过编写来指定计算一个数字绝对值的函数。 - + + 如果我们想单独处理零的情况,我们可以通过书写计算一个数的绝对值的函数来指定它 + \[\begin{array}{lll} |x| &=& \left\{ \begin{array}{rl} - x & \mbox{if $x > 0$} \\ - 0 & \mbox{if $x = 0$} \\ - -x & \mbox{otherwise} + x & \mbox{如果 $x > 0$} \\ + 0 & \mbox{如果 $x = 0$} \\ + -x & \mbox{否则} \end{array} \right. \end{array}\] - - 在 JavaScript 中,我们通过在其他条件表达式内部将条件表达式嵌套作为替代表达式来表达具有多个条件的情况分析: - + + 在 JavaScript 中,我们通过将条件表达式作为其他条件表达式中的备选表达式嵌套, + 来表示多分支的条件分析: + abs abs_example @@ -401,22 +388,20 @@ function abs(x) { : - x; } - - 替代表达式 - x === 0 ? 0 : - x 周围不需要括号,因为条件表达式的语法形式 - 条件表达式替代作为条件表达式的替代 - 结合性条件表达式的条件表达式 - 条件表达式的右结合性 - 右结合的 + + 备选表达式 + x === 0 ? 0 : - x 不需要加括号,因为条件表达式的语法形式 + 条件表达式条件表达式的备选表达式 + 结合性条件表达式的结合性 + 条件表达式右结合性 + 右结合 是右结合的。 - 解释器忽略空格和换行符,这里插入是为了可读性,以对齐 - ?: - 在情况分析的第一个谓词下。 - 一般的情况分析 - 情况分析一般 - 的形式是 + 解释器忽略此处为可读性而插入的空格和换行, + 使得 + ?:对齐于多分支条件的第一个判断条件之下。 + 多分支条件的一般形式是 - + p$_1$ ? e$_1$ @@ -425,42 +410,33 @@ function abs(x) { $\vdots$ : p$_n$ ? e$_n$ -: final-alternative-expression +: 最终备选表达式 - - 我们称谓词 -$p_i$ - 连同其后续表达式 -$e_i$ - 一个 - 情况分析的子句 - 谓词子句的子句 - 子句。情况分析 - 可以看作是一系列子句,后面跟着一个最终的 - 替代表达式。 - 情况分析子句序列的序列 - 根据条件表达式的评估, - 情况分析是通过首先评估 - 谓词 p 来进行评估的。 -$_1$ - 如果它的值为假,那么 p -$_2$ - 被评估。 - 如果 p -$_2$ - 的值也是假,那么 p -$_3$ - 被评估。此过程继续,直到找到一个谓词,其值为 true,此时,解释器返回对应的 - 后续子句的子句 - 后续表达式 - e - 作为情况分析的值。 - 如果没有任何 - p 被发现为 true,情况分析的值 - 就是最终替代表达式的值。 - + + 我们称判断条件 $p_i$ 及其对应表达式 + $e_i$ + 为多分支条件的 + 子句。 + 多分支条件可以视为一系列子句,后跟一个最终备选表达式。 + 多分支条件作为子句序列 + 根据条件表达式的求值规则, + 多分支条件首先求值判断条件 + p$_1$。 + 如果其值为假,则继续求值 + p$_2$。 + 如果 + p$_2$ 的值也为假, + 则继续求值 + p$_3$。 + 如此继续,直到找到一个值为真的判断条件, + 此时解释器返回对应子句的 + 结论表达式 + e 的值作为多分支条件的值。 + 若所有 + p 判断条件都为假,多分支条件的值即为最终备选表达式的值。 + - + @@ -532,233 +508,228 @@ $\vdots$ - 除了原始谓词,例如 - > (数值比较运算符) - >=0< (数值比较运算符) - >=1<= (数值比较运算符) - ===数值作为数值相等性运算符 - "!==;4作为数值比较运算符数值 - > (数值比较) - ===1< (数值比较) - ===2<= (数值比较) - === (用于数值比较) - "!== (用于数值比较);4 - 相等性数字的数字 - >=, - >, - <, - <=, - === 和 - !== 这些都应用于 - 数字, - - 目前,我们将这些运算符限制为数字参数。在节中,我们将对相等性和不等性谓词 - === 和 - !== 进行概括。</FOOTNOTE> - 有逻辑组合操作,这使我们能够构建复合谓词。三种最常用的操作是: -
      + 除了原始谓词,比如 + >(数字比较运算符) + >=0<(数字比较运算符) + >=1<=(数字比较运算符) + ===数字作为数字相等运算符 + "!==;4作为数字比较运算符数字 + >(数字比较) + ===1<(数字比较) + ===2<=(数字比较) + ===(用于数字比较) + "!==(用于数字比较);4 + 相等数字的数字 + >=、 + >、 + <、 + <=、 + === 和 + !== 应用于数字,目前,我们将这些运算符限定为数字参数。在章节 +和中,我们将推广相等和不等谓词 +=== 和 +!== +还有逻辑组合操作,可以用来构造复合谓词。使用最频繁的三个是: + +
      • - - 表达式 -$_1$ - && - 表达式 -$_2$

        + 表达式$_1$ && + 表达式$_2$

        该操作表示 语法糖;1&& 和 {\tt "|"|} 作为 - && (逻辑与);1 - && (逻辑与)的求值;1 - 语法形式逻辑与 (&&) + &&(逻辑与);1 + &&(逻辑与)的求值;1 + 语法形式逻辑与(&& 逻辑与 合取 - 求值&&的 ;1 - 逻辑与,大致意思与英语单词 and. - 我们假设这一假设是由脚注中提到的限制所证明的 - 。完整的 JavaScript 需要考虑评估 表达式$_1$ 的结果既不是 true 也不是 false 的情况。 这个语法形式被认为是语法糖语法形式只是那些可以用更统一的方式编写的方便替代表面结构,有时称为 语法糖,这个短语源自 - Landin, Peter + 求值&&;1 + 逻辑与,其意义大致相当于英语中的“and”。 + 我们假定此假定基于脚注中提到的限制。完整的 JavaScript + 需要考虑当< META >表达式< / META >< LATEXINLINE >$_1$< / LATEXINLINE >求值结果既不为 true 也不为 false 的情况。该语法形式为 + 语法形式仅为那些可以用更加统一方式书写的内容提供方便的替代表面结构,有时称为语法糖,该术语由 + Peter Landin提出。 语法糖 Peter Landin。 - 用于

        - 表达式 $_1$ - ? - 表达式 -$_2$ - : - false。 -
      • - -
      • - 表达式$_1$ + + 表达式$_1$ ? + 表达式$_2$ : + false。 +
      • +
      • + 表达式$_1$ || - 表达式$_2$

        + 表达式$_2$

        该操作表示 - {\tt "|"|} (逻辑或);2 - {\tt "|"|} (逻辑或);2的求值 - 语法形式逻辑或 ({\tt "|"|}) + {\tt "|"|}(逻辑或);2 + {\tt "|"|}(逻辑或);2的求值 + 语法形式逻辑或({\tt "|"|}) 逻辑或 析取 - 求值的 {\tt "|"|}的 ;2 - 逻辑或,大致意思与英语单词 or. - 我们假设这个语法形式是

        - 表达式$_1$ ? + 求值对 {\tt "|"|} 的;2 + 逻辑或,其意义大致相当于英语中的“or”。 + 我们假定该语法形式为 + 表达式$_1$ ? true : - 表达式$_2$。 + 表达式$_2$ 的语法糖。
      • ! 表达式

        该操作表示 - "! (逻辑非运算符);399 - ;399"! (逻辑非) - 否定逻辑 ("!) - 逻辑非,大致意思与英语单词 not. - 当 表达式 的求值为假时,该表达式的值为真,当 表达式 的求值为真时,该表达式的值为假。 + "!"(逻辑非操作符);399 + ;399"!"(逻辑非) + 取反逻辑("!" + 逻辑非,其意义大致相当于英语中的“not”。 + 当表达式求值为 false 时,该表达式的值为 true;当表达式求值为 true 时,该表达式的值为 false。
      • -
      - 注意 - - && 和 - || 是语法形式, - 而不是运算符; - && (逻辑与);1为何是语法形式 - {\tt "|"|} (逻辑或);2为何是语法形式 - 它们的右侧表达式并不总是被求值。另一方面,运算符 - ! - 遵循节 - 中的求值规则。 - 它是一个 一元 运算符,这意味着它只接受一个参数,而到目前为止讨论的算术运算符和原始谓词都是 二元,接受两个参数。运算符 - ! 位于其参数之前; - 我们称它为 - -作为数值取反运算符 - - (数值取反运算符) - 否定数值 (-) - 二元运算符 - 一元运算符 - 前缀运算符 - 前缀运算符。另一个前缀运算符是 - 数值取反运算符,例如在上述 abs 函数中表达式 - x 的例子。 - +
    + 注意 && 和 + || 是语法形式,而非操作符; + &&(逻辑与);1为何是语法形式 + {\tt "|"|}(逻辑或);2为何是语法形式 + 它们的右侧表达式并不总是被求值。另一方面,操作符 + ! 遵循第 + 。 + 它是一个一元操作符,这意味着它只接受一个参数,而到目前为止讨论的算术操作符和原始谓词是二元的,接受两个参数。操作符 + ! 位于其参数之前;我们称之为 + -作为数值取反操作符 + -(数值取反操作符) + 取反数值(- + 二元操作符 + 一元操作符 + 前缀操作符 + 前缀操作符。另一个前缀操作符是数值取反操作符,其例子是上面abs函数中的表达式- x。 +
    - + - 作为这些用法的一个例子,数字 $x$ 在范围 - $5 < x < 10$ 的条件可以表示为 - - + 作为这些用法的一个例子,数字 + $x$ 在范围 + $5 < x < 10$ 内的条件可以表示为 + + (and (> x 5) (< x 10)) - - + + - + - 作为这些谓词用法的一个例子,数字 $x$ 在范围 - $5 < x < 10$ 的条件可以表示为 - + 作为这些谓词使用的一个例子,数字 + $x$ 在范围 + $5 < x < 10$ 内的条件可以表示为 + x > 5 && x < 10 - + 语法形式 - && - 的优先级低于比较运算符 + && + 的优先级低于比较操作符 > - 和<,并且 - 条件表达式的语法形式 - $\cdots$?$\cdots$:$\cdots$ - 的优先级低于我们目前遇到的任何其他运算符,这是我们在 - 上述 abs 函数中使用的属性。 + 和 <,而条件表达式语法形式 + $\cdots$?$\cdots$:$\cdots$ + 的优先级低于我们迄今遇到的任何其他操作符,这是我们在上面 + abs 函数中使用过的一个属性。 - - 作为另一个例子,我们可以 + + 作为另一个例子,我们可以 - - 定义 - + + define + 声明 - 一个谓词来测试一个数字是否大于或等于另一个数字,表示为 - + 一个谓词来测试一个数字是否 + + + >= + + + 大于或等于另一个数字,如 + geq_example - + (define (>= x y) (or (> x y) (= x y))) - + function greater_or_equal(x, y) { return x > y || x === y; } - + 或者也可以表示为 - + >= - + - + geq_example - + (define (>= x y) (not (< x y))) - + function greater_or_equal(x, y) { return ! (x < y); } - - + + geq_example - + (>= 7 4) - + greater_or_equal(7, 4); - + - - + + - 函数 greater_or_equal 在应用于两个数字时,行为与运算符 - >= 相同。一元运算符的优先级 - 优先级一元运算符的优先级 - 高于二元运算符,这使得该例中的括号是必要的。 + 函数 greater_or_equal, + 当应用于两个数字时,其行为与操作符 + >=相同。一元操作符具有 + 优先级一元操作符优先级 + 高于二元操作符,这使得本例中的括号是必要的。 - + - - 以下是一系列 + + 下面是一系列 - + 表达式。 - + 语句。 解释器对每个 - - 表达式的结果是什么? - + + 表达式的 + - 语句的结果是什么? + 语句的 - 假设按照展现的顺序来评估这一序列。 - + 响应打印的结果是什么? + 假设这些序列按所给顺序执行。 + ten 10 @@ -912,44 +883,43 @@ a === 4 (a + 1);
    - - 括号围绕条件表达式在最后两个语句中是必要的,因为 - 条件表达式操作数作为运算符组合的操作数 - 条件表达式的语法形式优先级低于 - 优先级条件表达式的条件表达式 - 条件表达式的优先级 - 算术运算符 + + + 在最后两个语句中,条件表达式周围需要括号, + 因为条件表达式的 + 优先级条件表达式优先级 + 低于算术运算符 +*。 - - - 以下是 - - - 表达式。 - - - 语句。 - - - 解释器对每个 - - - 表达式的结果是什么? - - - 语句的结果是什么? - - 假设按照展现的顺序评估这一序列。 - + + + + 由 GitHub 用户 Emekk 提供的解答 +
      +
    1. 10
    2. +
    3. 12
    4. +
    5. 8
    6. +
    7. 3
    8. +
    9. 6
    10. +
    11. 3
    12. +
    13. 4
    14. +
    15. 19
    16. +
    17. false
    18. +
    19. 4
    20. +
    21. 16
    22. +
    23. 6
    24. +
    25. 16
    26. +
    +
    + - - 将以下表达式翻译成 + + 将下列表达式翻译成 - + 前缀形式 - + JavaScript @@ -957,75 +927,72 @@ a === 4 \par\medskip - + $\begin{array}{l} \quad~~\dfrac{5+4+\left(2-\left(3-(6+\frac{4}{5})\right)\right)}{3 (6-2) (2-7)} \end{array}$ - + - + (5 + 4 + (2 - (3 - (6 + 4 / 5)))) / (3 * (6 - 2) * (2 - 7)); - + - - - - + + + - + 定义一个过程 - + 声明一个函数 - 接收三个数字作为参数并返回两个较大数字的平方和。 + 接受三个数字作为参数并返回 + 两个较大数字的平方和。 - + larger_two_square_example square_definition function f(x, y, z) { return square(x) + square(y) + square(z) - - // subtract the square of the smallest + // 减去最小值的平方 square(x > y ? (y > z ? z : y) : (x > z ? z : x)); } - + (define (f x y z) (let ((smallest (if (> x y) (if (> y z) z y) (if (> x z) z x)))) (- (+ (square x) (square y) (square z)) (square smallest)))) - - + + - + larger_two_square_example - + (f 4 7 2) - + f(4, 7, 2); - - + + - - + - Observe that our model of evaluation allows for - combinationcompound expression as operator of - compound expressionoperatoras operator of combination - operator of a combinationcompound expression as - combinations whose - operators are compound expressions. Use this observation to describe - the behavior of the following procedure: + 注意我们的求值模型允许 + 组合作为操作符的复合表达式 + 复合表达式作为组合的操作符 + 组合的操作符作为复合表达式 + 操作符为复合表达式的组合。利用这一观察来描述以下过程的行为: (define (a-plus-abs-b a b) @@ -1033,14 +1000,13 @@ f(4, 7, 2); - - 注意我们的评估模型允许 - 函数应用复合表达式作为函数表达式的 - 复合表达式函数表达式作为函数应用的表达式 - 函数表达式复合表达式作为 - 应用程序,这些应用程序的函数表达式是复合表达式。利用这一观察描述 - a_plus_abs_b 的行为: - + + 注意我们的求值模型允许 + 函数应用作为函数表达式的复合表达式 + 复合表达式作为应用的函数表达式 + 函数表达式作为复合表达式 + 函数表达式为复合表达式的应用。利用这一观察描述 a_plus_abs_b 的行为: + plusminusexample function plus(a, b) { return a + b; } @@ -1058,46 +1024,45 @@ function a_plus_abs_b(a, b) { a_plus_abs_b(5, -4); - - 请注意,在 JavaScript 中的条件表达式中,我们不能像在 Scheme 中那样直接使用运算符 + 和 - -,反而必须使用名称 plus 和 - minus,因为在中缀表示法中, - 只允许运算符符号出现在中间,而不允许复合表达式。 - - 根据节,应用程序的求值过程如下: -
      -
    1. 评估应用程序的子表达式。
    2. -
    3. 评估函数表达式的返回表达式,每个参数都替换为其各自参数表达式的结果。
    4. -
    - 因此,应用程序 - a_plus_abs_b(5, -4) 的求值 - (1) 评估 a_plus_abs_b,得到上面的函数,且参数已是值。所以我们需要评估 - (2) 函数的返回表达式,用参数替换为参数,从而得到: - (-4 >= 0 ? plus : minus)(5, -4)。根据相同规则,我们需要 (1) 评估函数表达式,在本例中是条件表达式 - -4 >= 0 ? plus : minus。由于谓词评估为假,函数表达式 - 评估为 minus。参数再次 (1) 已是值。因此,我们最终评估 (2) minus 的主体,参数 - a 和 - b 分别替换为 5 和 -4,结果为 - 5 - (-4),最终结果为 9。 -
    + + 注意,在 JavaScript 的条件表达式中,我们不能像 Scheme 中那样直接使用操作符 +-,而必须使用名称 plusminus,因为在中缀表示法中,只允许操作符符号处于中间位置,而不允许复合表达式。 + + + 根据 节,应用的求值过程如下: +
      +
    1. 求值应用的子表达式。 +
    2. +
    3. 用各参数替换对应参数表达式结果,求值函数表达式的返回表达式。 +
    4. +
    + 因此求值应用 a_plus_abs_b(5, -4) 时, + (1) 求值 a_plus_abs_b, + 得到上面给出的函数,参数本身已是值。接着需要 (2) 求值函数的返回表达式,参数替换后为: + (-4 >= 0 ? plus : minus)(5, -4)。 + 依据相同规则,先 (1) 求值函数表达式, + 这里是条件表达式 -4 >= 0 ? plus : minus。由于条件为假,函数表达式求值结果为 minus。参数 (1) 已是值。 + 因此最终 (2) 求值 minus 函数体,参数 ab 分别替换为 5 和 -4,结果为 + 5 - (-4),最终求值为 9。 +
    +
    -
    +
    - - - Ben Bitdiddle 发明了一项测试,用于判断他面对的解释器是使用 - 正则序求值应用序 vs. - 应用序求值正则序 vs. - 或正则序求值。他 + + + 本·比特迪德发明了一个测试来确定他所面对的解释器是使用 + 正常序求值应用序与 + 应用序求值正常序与 + 应用序求值还是正常序求值。他 - - 定义了以下两个过程: - + + 定义以下两个过程: + - 声明了以下两个函数: + 声明以下两个函数: - + ptest (define (p) (p)) @@ -1115,13 +1080,13 @@ function test(x, y) { } - - 然后他评估 - - 表达式 + + 然后他求值 + + 表达式 语句 - - + + ptest (test 0 (p)) @@ -1130,45 +1095,38 @@ function test(x, y) { test(0, p()); - - 贝恩·比特迪尔发明了一项测试,用于确定他所面对的解释器是使用 - 应用序求值正则序 vs. - 正则序求值应用序 vs. - 应用序求值还是正则序求值。他 - - - 定义了以下两个过程: - - - 声明了以下两个函数: - - - - 解决方案: + + 在使用应用序求值的解释器中,本将观察到什么行为?在使用正常序求值的解释器中,他将观察到什么行为?请解释你的答案。 + 正常序求值条件表达式的条件 + 条件表达式正常序求值的 + (假设 + + + 特殊形式 if 的 + + + 条件表达式的求值规则 + + + 无论解释器使用正常序还是应用序均相同: + 先求值谓词表达式,其结果决定是否求值后继或备选表达式。) + - 在 - test(0, p()) 的应用序求值中, - 我们需要在评估函数 - test 的返回表达式之前,先评估参数表达式。 - 然而,参数表达式 - p() 的评估不会终止:它将不断评估形如 - p() 的应用表达式,因此 - test(0, p()) 的评估不会产生合法值。另一方面,在正则序求值中, - 函数应用 - test(0, p()) 将立即评估函数 - test 的返回表达式 + 对于 test(0, p()) 的应用序求值, + 我们需要先求值参数表达式,然后才能求值函数 test 的返回表达式。 + 其中参数表达式 p() 的求值不会终止: + 它会不断求值形式为 p() 的应用表达式, + 因此对 test(0, p()) 的求值不会产生一个合理的值。 + 另一方面,在正常序求值中,函数应用 test(0, p()) 会立即求值函数 test 的返回表达式 x === 0 ? 0 : y, - 替换参数 - x 为 - 0 和 - y 为 - p()。 - 替换的结果将是 - 0 === 0 ? 0 : p()。 - 谓词 0 === 0 的评估结果为真,因此条件表达式评估为 0,而不需要评估 - p()。 + 其中参数 x 被替换为 0, + 参数 y 被替换为 p()。 + 替换后结果是 0 === 0 ? 0 : p()。 + 谓词表达式 0 === 0 的求值结果为真, + 因此条件表达式的值为 0,无需求值 p() - + + diff --git a/xml/zh/chapter1/section1/subsection7.xml b/xml/zh/chapter1/section1/subsection7.xml index 7d65189cc..e0f5960d7 100644 --- a/xml/zh/chapter1/section1/subsection7.xml +++ b/xml/zh/chapter1/section1/subsection7.xml @@ -1,32 +1,31 @@ - 示例:牛顿法的平方根方法 + 示例:牛顿求平方根方法 - - 过程数学函数与 - 函数(数学)过程与 - + + 过程数学函数与. + 函数(数学)过程与. + - 数学函数与 - 函数(数学)JavaScript函数与 + 数学函数与. + 函数(数学)JavaScript函数与. - 过程, - 函数, + 过程, + 函数, - 如上所述,过程与普通数学函数非常相似。它们 - 指定由一个或多个参数决定的值。但数学函数与计算机 + 如上所述,非常类似于普通的数学函数。它们指定一个由一个或多个参数决定的值。但是数学函数与计算机 - 过程之间有一个重要区别。 + 过程之间存在一个重要区别。 函数之间存在一个重要区别。 - +
    - + 过程 - + 计算机函数 @@ -34,125 +33,138 @@ 必须是有效的。 - 作为一个例子,考虑计算平方根的问题。我们可以将平方根函数定义为 - + 以计算平方根的问题为例。我们可以将平方根函数定义为 + \[ - \sqrt{x}\ =\text{ 使 }y\text{ 满足 }y \geq 0\text{ 且 } - y^2\ =\ x + \sqrt{x}\ =\text{ 满足 }y \geq 0\text{ 且 } + y^2\ =\ x\text{ 的 }y \] - - 这描述了一个完全合法的数学函数。我们可以用它来识别一个数字是否是另一个数字的平方根,或者推导出有关平方根的一般事实。另一方面,这个定义并没有描述一个 + + 这描述了一个完全合法的数学函数。我们可以用它来判断一个数是否是另一个数的平方根,或者推导关于平方根的一般性质。另一方面,这一定义并没有描述一个 - 过程。 + 过程。 计算机函数。 - 实际上,它几乎没有告诉我们如何实际找到给定数字的平方根。仅仅用 + 实际上,它几乎没有告诉我们如何实际找到一个给定数的平方根。如果用 - 伪Lisp: + 伪Lisp: 伪JavaScript: - - + 换一种说法也无济于事: + + (define (sqrt x) (the y (and (>= y 0) (= (square y) x)))) - + function sqrt(x) { return the y $\texttt{with}$ y >= 0 && square(y) === x; } - - 这只会引发问题。 + + 这只会使问题更加突出。 - - function 和过程 - - - 数学函数和计算机函数 - - - 数学函数和计算机函数 - - - 之间的对比反映了描述事物属性与描述如何做事之间的普遍区别,或者有时也称为 - 声明性知识与命令性知识 - 命令性知识与声明性知识 - 声明性知识和命令性知识之间的区别。在 - 数学计算机科学与 - 计算机科学数学与 - 数学中,我们通常关注声明性(是什么) - 描述,而在计算机科学中,我们通常关注 - 命令性(如何)描述。 - 声明性和命令性描述是密切相关的,正如数学和计算机科学一样。例如,说明一个程序产生的答案是 - 程序正确性 - 正确就是对程序做出声明性陈述。有大量的研究旨在建立 - 程序正确性证明 - 证明程序正确性的技术,而这个主题的许多技术难点与在命令性语句(程序构建的基础)和声明性语句(可以用于推导事物)之间的转换有关。 - - - 在相关方面,编程语言设计中的一个重要领域是探索所谓的 - 编程语言超高级 - 超高级语言 - 超高级语言,其中实际以声明性语句进行编程。 - - - 在相关方面,编程语言设计者探索了所谓的 - 编程语言超高级 - 超高级语言 - 超高级语言,其中实际以声明性语句进行编程。 - - - 这个想法是使解释器足够复杂,以便在程序员指定的是什么知识的基础上,自动生成如何做知识。这在一般情况下是无法实现的,但在某些重要领域已经取得了进展。我们将在第章中重新讨论这个想法。 - - - 过程数学函数与 - 函数(数学)过程与 - - - 数学函数与 - 函数(数学)JavaScript函数与 - - - - - 如何计算 - 平方根 - 牛顿平方的平方根 - 平方根?最常见的方法是使用牛顿法的逐次逼近,该方法指出,每当我们有一个猜测时 -$y$ 一个数的平方根的值 $x$ ,我们可以进行简单的操作,以获得更好的猜测(更接近实际平方根),通过平均 $y$$x/y$. 这个平方根算法实际上是牛顿法的一种特例,牛顿法是一种求解方程根的一般技术。平方根算法本身是由公元一世纪的亚历山大里亚的希罗(Heron of Alexandria)发展而来的。我们将看到如何在第节中将一般的牛顿法表示为 - - - Lisp 过程 - - - JavaScript 函数 - - - 例如,我们可以按如下方式计算 2 的平方根。假设我们的初始猜测是1: - \[ - \begin{array}{lll} - \textrm{Guess} & \textrm{Quotient} & \textrm{Average}\\[1em] - 1 & {\displaystyle \frac{2}{1} = 2} & {\displaystyle \frac{(2+1)}{2} = 1.5} \\[1em] - 1.5 & {\displaystyle \frac{2}{1.5} = 1.3333} & {\displaystyle \frac{(1.3333+1.5)}{2} = 1.4167} \\[1em] - 1.4167 & {\displaystyle \frac{2}{1.4167} = 1.4118} & {\displaystyle \frac{(1.4167+1.4118)}{2} = 1.4142} \\[1em] - 1.4142 & \ldots & \ldots - \end{array} - \] - - 继续这个过程,我们将获得越来越接近平方根的更好近似值。 - - - 现在让我们将这个过程形式化为函数。我们从一个被开方数radicand(我们试图计算平方根的数字)的值和一个猜测值开始。如果该猜测对我们的目的足够好,我们就完成了;如果不够好,我们必须用改进的猜测重复该过程。我们将这个基本策略写成一个 - - - 过程: - - - 函数: - - + + 之间的对比 + + + 函数和过程 + + + 数学函数和计算机函数 + + + 反映了一般性区别,即描述事物属性和描述如何做事之间的区别,或者有时所说的, + 声明式知识与命令式知识的区别 + 命令式知识与声明式知识的区别 + 声明式知识和命令式知识之间的区别。在 + 数学计算机科学与之相对 + 计算机科学数学与之相对 + 数学中,我们通常关注声明式(是什么的)描述, + 而在计算机科学中,我们通常关注命令式(如何做的)描述。声明式和命令式描述密切相关, + 数学和计算机科学也是如此。例如,要表明程序产生的答案是 + 程序的正确性 + 正确 + ,就是对程序做出一个声明式的陈述。 + 有大量研究旨在建立证明程序正确的技术,并且该领域技术难点主要在于协调 + 命令式陈述(程序构造的基础)和声明式陈述(可用于推导)的转变。 + + + 在相关领域,编程语言设计中的一个重要当前研究领域是探索所谓的 + 编程语言非常高级 + 非常高级语言 + ,在其中人们实际上用声明式语句进行编程。 + + + 在相关领域,编程语言设计者探索了所谓的 + 编程语言非常高级 + 非常高级语言 + ,其中人们实际上用声明式语句进行编程。 + + + 其思想是使解释器足够智能,以至于在程序员指定的是什么知识基础上, + 它们能够自动生成如何做的知识。这在一般情况下无法实现,但在某些重要领域已取得进展。 + 我们将在第章重新探讨这一思想。 + + + 过程数学函数与之相对 + 函数(数学)过程与之相对 + + + 数学函数与之相对 + 函数(数学)JavaScript 函数与之相对 + + + + + 如何计算 + 平方根 + 牛顿平方根的平方计算 + 平方根?最常用的方法是使用 + 牛顿的连续逼近法,它指出每当我们对一个数 + $x$的平方根的值有一个猜测 + $y$时,我们可以通过将 + $y$与 + $x/y$取平均来进行简单运算,得到一个更好的猜测(更接近实际平方根)。该平方根算法实际上是牛顿法的一个特例,牛顿法是一种用于求方程根的通用技术。平方根算法本身由公元一世纪的 + 亚历山大港的海龙 + 海龙(Heron of Alexandria)开发。我们将在第节中看到如何将通用牛顿法表示为 + + + Lisp 过程 + + + JavaScript 函数 + + + 。 + 例如,我们可以如下计算2的平方根。假设我们的初始猜测值是1: + + \[ + \begin{array}{lll} + \textrm{猜测} & \textrm{商} & \textrm{平均} \\[1em] + 1 & {\displaystyle \frac{2}{1} = 2} & {\displaystyle \frac{(2+1)}{2} = 1.5} \\[1em] + 1.5 & {\displaystyle \frac{2}{1.5} = 1.3333} & {\displaystyle \frac{(1.3333+1.5)}{2} = 1.4167} \\[1em] + 1.4167 & {\displaystyle \frac{2}{1.4167} = 1.4118} & {\displaystyle \frac{(1.4167+1.4118)}{2} = 1.4142} \\[1em] + 1.4142 & \ldots & \ldots + \end{array} + \] + + 通过继续该过程,我们得到对平方根越来越好的近似。 + + + 现在让我们用函数来形式化这个过程。我们从一个 + 被开方数 + (我们试图计算其平方根的数)的值和一个猜测值开始。如果这个猜测对我们的目的来说足够好,我们就完成了;如果不够好,我们必须用改进的猜测重复这个过程。我们将这个基本策略写成一个 + + + 过程: + + + 函数: + + sqrt_iter is_good_enough @@ -180,8 +192,8 @@ function sqrt_iter(guess, x) { sqrt_iter(3, 25); - - 通过将猜测值与被开方数和旧猜测的商相平均,可以改善猜测: + + 猜测值通过将其与被开方数除以旧猜测值得到的商取平均来改进: improve average_definition @@ -229,17 +241,19 @@ function average(x, y) { average(3, 6); - 我们还需要说明“足够好的”含义。以下的描述可以作为说明,但实际上这并不是一个很好的测试。(见习题。)这个想法是改善答案,直到它足够接近,使得其平方与被开方数之间的差距小于预定的容差(这里是0.001):我们通常给出 + 我们还必须说明我们所说的“足够好”是什么意思。下面的示例可以说明问题,但它并不是真正很好的测试。(见练习 + .) + 这个想法是不断改进答案,直到它足够接近,使其平方与被开方数的差小于预定的容差(这里为0.001):我们通常会给出 谓词命名约定 - 命名约定??用于谓词 - 问号,作为谓词名称 - ?, 作为谓词名称 - 以帮助我们记住它们是谓词。这仅是一个风格约定。就解释器而言,问号只是一个普通字符。我们通常给出 + 命名约定??谓词的 + 谓词名称中的问号 + ?,谓词名称中使用 + 以问号结尾的谓词名称,帮助我们记住它们是谓词。这仅仅是一种风格上的约定。对于解释器来说,问号只是一个普通字符。我们通常会给出 谓词命名约定 - 命名约定is_用于谓词 - is_, 作为谓词名称的开始 - 以帮助我们记住它们是谓词。 - + 命名约定is_谓词的 + is_,谓词名称中的前缀 + 以 is_ 开头的谓词名称,帮助我们记住它们是谓词。 + is_good_enough abs_definition square_definition @@ -262,21 +276,10 @@ function is_good_enough(guess, x) { is_good_enough(1.41, 2); - - 最后,我们需要一种方法来开始。例如,我们可以总是假设任何数字的平方根为1:请注意,我们将初始猜测表达为1.0而不是1。这在许多Lisp实现中不会有什么区别。 - 有理数MIT在MIT Scheme中 - 精确整数 - 整数精确 - 整数的除法 - 整数除法 - 数字有理数 - 数字整数,精确 - 数字小数点 - 数字中的小数点 - MIT Scheme数字 - 实现依赖数字 - 数字实现依赖 - 然而,MIT Scheme区分精确整数和小数值,两个整数的除法产生有理数而不是小数。例如,10除以6得到5/3,而10.0除以6.0得到1.6666666666666667。(我们将在第节中学习如何对有理数进行算术运算。)如果我们在平方根程序中从初始猜测1开始,而$x$是一个精确整数,则在平方根计算中产生的所有后续值将是有理数而不是小数。在有理数和小数的混合操作中,总是会产生小数,因此以1.0作为初始猜测会迫使所有后续值都是小数。 + + 最后,我们需要一种方法来开始。例如,我们总可以猜测任何数字的平方根是1:注意我们将初始猜测表达为1.0而不是1。在许多Lisp实现中,这不会有任何区别。 + MIT Scheme区分精确整数和小数值,两个整数相除会产生有理数而不是小数。例如,10除以6得到5/3,而10.0除以6.0得到1.6666666666666667。(我们将在第节学习如何实现有理数的算术。)如果我们在平方根程序中以1作为初始猜测,并且$x$是精确整数,那么平方根计算中产生的所有后续值都将是有理数而不是小数。有理数和小数的混合运算总是产生小数,因此以1.0作为初始猜测会强制所有后续值都是小数。 + 注释已删除:它特指Scheme(或者更具体地说是MIT Scheme) sqrt sqrt @@ -292,27 +295,27 @@ function sqrt(x) { return sqrt_iter(1, x); } - - 如果我们将这些 - - - 定义 - - - 声明 - - - 输入到解释器中,我们就可以使用 -sqrt - 就像我们可以使用任何 - - - 过程: - - - 函数: - - + + 如果我们在解释器中输入这些 + + + 定义 + + + 声明 + + + ,我们就可以使用 +sqrt + 就像我们可以使用任何 + + + 过程: + + + 函数: + + sqrt_example sqrt @@ -391,31 +394,64 @@ square(sqrt(1000)); Newtons methodsquarefor square roots - - 如果我们输入这些 + + sqrt 程序同样说明了, + 我们迄今为止介绍的简单 - - 定义 - + + 迭代过程通过过程调用实现 + 的过程式 + - 声明 + 迭代过程通过函数调用实现 + 的函数式 - 到解释器中,我们可以使用 - + 语言足以编写任何纯数值程序,比如用 C 或 Pascal 编写的程序。 这看起来可能令人惊讶, + 因为我们的语言中没有包括任何指示计算机反复执行某事的 + 循环结构。 + + + Sqrt-iter, + + + 函数 sqrt_iter, + + + 另一方面,演示了如何仅使用普通的调用 + 过程。函数。能力来完成迭代,而不需要任何特殊结构。担心使用 + + + 过程 + + + 函数 + + + 调用来实现迭代所涉及的效率问题的读者,应注意 + 尾递归 部分在 + 节中的相关说明。 + + + 迭代过程通过过程调用实现 + + + 迭代过程通过函数调用实现 + + + - + - Alyssa P. Hacker doesnt see why if - needs to be provided as a - ifwhy a special form - special formneed for - special form. Why cant I just - define it as an ordinary procedure in terms of - cond? she asks. - Alyssas friend Eva Lu Ator claims this can indeed be done, and - she defines a new version of if: + Alyssa P. Hacker 不明白为什么必须将 if + 提供为一个 + if为什么需要特殊形式 + 特殊形式需要原因。 + 她问:“我为什么不能仅用普通过程并借助 + cond来定义它呢?” + Alyssa 的朋友 Eva Lu Ator 声称这的确可以做到, + 于是她定义了一个新的 if 版本: new_if @@ -424,7 +460,7 @@ square(sqrt(1000)); (else else-clause))) - Eva demonstrates the program for Alyssa: + Eva 向 Alyssa 演示该程序: new_if @@ -443,8 +479,7 @@ square(sqrt(1000)); 0 - Delighted, Alyssa uses new-if to rewrite - the square-root program: + Alyssa 非常高兴,她使用 new-if 重写了求平方根程序: new_if is_good_enough @@ -458,32 +493,28 @@ square(sqrt(1000)); x))) - What happens when Alyssa attempts to use this to compute square roots? - Explain. + 当 Alyssa 试图用此程序来计算平方根时会发生什么? 请解释。 - - Alyssa P. Hacker不喜欢 - 语法形式所需的 - 条件表达式为何是语法形式 - 条件表达式的语法,涉及到的字符 -? -和 -:. - - “我为什么不能仅仅声明一个普通的条件函数,它的应用方式就像条件表达式一样?”她问道。 - 作为最初的《计算机程序的结构与解释》的Lisp黑客,Alyssa更喜欢一种更简单、更统一的语法。 - Alyssa的朋友Eva Lu Ator声称这确实可以做到,她声明了一个条件函数,内容如下: - + + Alyssa P. Hacker 不喜欢 + 语法形式需要原因 + 条件表达式为什么需要语法形式 + ,涉及字符 ? + 和 :。 + 她问:“我为什么不能仅声明一个普通条件函数,使其应用就像条件表达式一样工作呢?” + 作为最早《计算机程序的结构和解释》的 Lisp 爱好者,Alyssa 更喜欢更简单、更统一的语法。 + Alyssa 的朋友 Eva Lu Ator 声称这确实可以做到,她声明了如下一个 conditional 函数: + conditional function conditional(predicate, then_clause, else_clause) { return predicate ? then_clause : else_clause; } - - Eva为Alyssa演示了程序: - + + Eva 向 Alyssa 演示该程序: + conditional conditional(2 === 3, 0, 5); @@ -500,11 +531,9 @@ conditional(1 === 1, 0, 5); 0 - - 高兴的Alyssa使用 -conditional - 以重写平方根程序: - + + Alyssa 非常高兴,她使用 conditional 重写了求平方根程序: + delighted conditional is_good_enough @@ -518,58 +547,54 @@ function sqrt_iter(guess, x) { x)); } - - 当Alyssa尝试使用这个计算平方根时会发生什么?解释一下。 - - - 调用sqrt_iter会立即导致无限循环。原因在于我们的应用序求值。sqrt_iter的返回表达式的求值需要先评估其参数,包括对sqrt_iter的递归调用,无论谓词的求值结果是真的还是假的。当然,递归调用也会发生同样的情况,因此conditional函数实际上并没有被应用。 - - + + 当 Alyssa 试图用此程序计算平方根时会发生什么? 请解释。 + + + 任何对 sqrt_iter 的调用都会立即导致无限循环。原因是我们的求值采用函数求值顺序。 sqrt_iter 返回表达式的求值需要先对其所有参数求值,包括对自身的递归调用,无论谓词是否求值为 true 还是 false。递归调用同样如此,因此函数 conditional 实际上永远不会被应用。 + + - + - - + + 用于计算平方根的 - - good-enough? - + + good-enough? + is_good_enough - 测试对于寻找非常小的数字的平方根并不十分有效。此外,在实际计算机中,算术运算几乎总是以有限的精度进行。这使得我们的测试对于非常大的数字不够充分。解释这些陈述,并用示例说明测试如何在小数字和大数字中失败。 - 实现 + 测试对于求非常小数字的平方根效果不佳。此外,在真实的计算机中,算术运算几乎总是以有限精度执行。这使得我们的测试对于非常大的数字同样不够充分。请解释这些说法,并举例说明该测试在小数和大数时如何失效。实现 - - good-enough? - + + good-enough? + is_good_enough - 的另一种策略是观察如何 -guess - 从一个迭代到下一个迭代的变化,并在变化仅是猜测的一小部分时停止。设计一个平方根 + 的另一种策略是观察 guess 在连续两次迭代中的变化,当变化仅为猜测值的很小一部分时停止。设计一个使用这种终止测试的平方根 - + 过程 - + 函数 - - ,使用这种结束测试。这对于小号码和大号码的效果如何? + 。这种方法对于小数和大数效果更好吗? - 当计算小值的平方根时,绝对容差0.001太大。例如, + 在计算小数的平方根时,绝对容差 0.001 太大。例如, sqrt(0.0001) - 结果为0.03230844833048122,而不是期望值0.01,误差超过200%。 + 结果为 0.03230844833048122,而预期值为 0.01,误差超过 200%。

    - 另一方面,对于非常大的值,舍入误差可能导致算法无法接近平方根,在这种情况下它将不会终止。 + 另一方面,对于非常大的数值,舍入误差可能导致算法无法足够接近平方根,从而无法终止。

    - 以下程序通过用相对容差替代绝对容差来缓解这个问题。 - + 下列程序通过用相对容差替代绝对容差来缓解此问题。 + abs_definition average_definition sqrt @@ -583,9 +608,9 @@ function is_good_enough(guess, x) { return abs(square(guess) - x) < guess * relative_tolerance; } - +
    - + example_1.8 display(sqrt(0.0001)); @@ -594,48 +619,51 @@ display(sqrt(4000000000000)); -
    + - - 牛顿法用于 - 立方根牛顿通过牛顿法 - 牛顿法立方用于立方根 - 立方根的计算基于以下事实:如果 - $y$是$x$的立方根的近似值,则更好的近似值由以下值给出: - + + 牛顿求 + 立方根牛顿法通过牛顿法求解立方根 + 牛顿立方用于立方根 + 的方法基于这样一个事实:如果 + $y$ 是 + $x$ 的立方根的近似值,则更好的近似值为 + \[ \begin{array}{lll} \dfrac{x/y^{2}+2y} {3} \end{array} \] - - 使用这个公式来实现一个立方根 + + 使用该公式实现一个立方根 - - 过程 - + + 过程 + - 函数 + 函数 - 类似于平方根 + ,类似于平方根 - 过程。 + 过程。 函数。 - (在第节中,我们将看到如何将牛顿法一般性地实现为这些 - 平方根和立方根的 + (在第节中我们将看到如何 + 将牛顿法实现为这些 + 平方根和立方根 - 过程的抽象。 - 函数的抽象。 + 过程 + 函数 + 的抽象。) - - example_1.9 - abs_definition - cube_definition - + + example_1.9 + abs_definition + cube_definition + function is_good_enough(guess, x) { return abs(cube(guess) - x) < 0.001; } @@ -650,16 +678,16 @@ function cube_root(guess, x) { ? guess : cube_root(improve(guess, x), x); } - - - - + + + + - + example_1.9 - cube_root(3, 27); + cube_root(3, 27); - + diff --git a/xml/zh/chapter1/section1/subsection8.xml b/xml/zh/chapter1/section1/subsection8.xml index 48e1cc45e..49f05ea85 100644 --- a/xml/zh/chapter1/section1/subsection8.xml +++ b/xml/zh/chapter1/section1/subsection8.xml @@ -1,256 +1,232 @@ - + - - Procedures - + + 过程 + - Functions + 函数 - as Black-Box Abstractions - + 作为黑盒抽象 + - - + + - Sqrt + 平方根 - The function sqrt + 函数 sqrt - is our first example of a process defined by a set of mutually + 是我们第一个由一组相互 - defined procedures. - defined functions. + 定义的过程。 + 定义的函数。 - Notice that the + 注意 - definition of sqrt-iter - declaration of - sqrt_iter + sqrt-iter 的定义 + sqrt_iter 的声明 - is + 是 - - recursive procedurerecursive procedure definition - + + 递归过程递归过程定义 + - recursive functionrecursive function declaration + 递归函数递归函数声明 - recursive; that is, the + 递归的;也就是说, - - procedure - + + 过程 + - function + 函数 - is defined in terms of itself. The idea of being able to define a + 是用它自身来定义的。能够用自身来定义一个 - - procedure - + + 过程 + - function + 函数 - in terms of itself may be disturbing; it may seem unclear how such a - circular definition could make sense at all, much less - specify a well-defined process to be carried out by a computer. This will - be addressed more carefully in - section. But first - lets consider some other important points illustrated by the - sqrt example. - - - Observe that the problem of computing square roots breaks up naturally - into a number of subproblems: - programstructure of - how to tell whether a guess is good - enough, how to improve a guess, and so on. Each of these tasks is - accomplished by a separate + 的想法可能让人不安;看起来好像这样的循环定义根本无法成立,更不用说指定一个由计算机执行的明确定义的过程了。这将在章节中更仔细地讨论。但首先,让我们考虑一下由 + 平方根 示例说明的一些其他重要点。 + + + 注意,计算平方根的问题自然地分解成若干个子问题: + 程序结构 + 如何判断一个猜测是否足够好,如何改进猜测,等等。每个任务都是通过一个单独的 - procedure. - function. + 过程。 + 函数。 - The entire sqrt program can be viewed as a - cluster of + 整个 平方根 程序可以看作是一组 - - procedures - (shown in figure) - + + 过程 + (如图所示) + - functions - (shown in figure) + 函数 + (如图所示) - that mirrors the decomposition of the problem into subproblems. - - - + ,其结构反映了问题向子问题的分解。 + + +
    - Procedural decomposition of the - sqrt program. + + 平方根 程序的过程式分解。
    - +
    - Functional decomposition of the - sqrt program. + + 平方根 程序的函数式分解。
    -
    - - The importance of this - decomposition of program into parts - decomposition strategy is not simply that one - is dividing the program into parts. After all, we could take any - large program and divide it into partsthe first ten lines, the next - ten lines, the next ten lines, and so on. Rather, it is crucial that - each + + + 这种 + 将程序分解为部分 + 的分解策略的重要性不仅仅在于将程序划分为若干部分。毕竟,我们可以对任何大型程序进行划分前十行、接下来的十行、再接下来的十行,依此类推。更关键的是,每个 - - procedure - + + 过程 + - function + 函数 - accomplishes an identifiable task that can be used as a module in defining - other + 完成一个可识别的任务,该任务可以作为模块用于定义其他 - procedures. - functions. + 过程。 + 函数。 - - procedureblackas black box - + + 过程作为黑盒子 + - blackas black box + <函数></函数>作为黑盒子</SUBINDEX> - For example, when we define the - - - good-enough? procedure - + 例如,当我们用 + + 够好? 过程 + - is_good_enough function + is_good_enough 函数 - in terms of square, we are able to - regard the square + 来定义时,能够将 + 平方 - - procedure - + + 过程 + - function + 函数 - as a - black box - black box. We are not at that moment concerned with - how the + 看作一个 + 黑盒子 + 黑盒子。 我们在那时并不关心这个 + 如何计算结果, - - procedure - + + 过程 + - function + 函数 - computes its result, only with the fact that it computes the - square. The details of how the square is computed can be suppressed, - to be considered at a later time. Indeed, as far as the + ,只关注它确实计算平方这一事实。平方是如何计算的细节可以被抑制,留待以后考虑。实际上,就 - - good-enough? procedure - + + 够好? 过程 + - is_good_enough function + is_good_enough 函数 - is concerned, square is not quite a + 而言,平方并不完全是一个 - - procedure - + + 过程 + - function + 函数 - but rather an abstraction of a + ,而是一个 - procedure, - function, + 过程的抽象, + 函数的抽象, - a so-called + 也就是所谓的 - - procedural abstraction - abstractionprocedural - procedural abstraction. - + + 过程抽象 + 抽象过程 + 过程抽象。 + - functional abstraction - abstractionfunctional - functional abstraction. + 函数抽象 + 抽象函数 + 函数抽象 - At this level of abstraction, any + 在这个抽象层次,任何计算平方的 - - procedure - + + 过程 + - function + 函数 - that computes the square is equally good. - - - Thus, considering only the values they return, the following two + 都是同样好的。 + + + 因此,仅考虑它们返回的值,下述两个 - - procedures - + + 过程 + - functions + 函数 - squaring a number should be indistinguishable. Each takes a numerical - argument and produces the square of that number as the value.It - is not even clear which of these + 计算一个数字的平方应当是无法区分的。每个都接受一个数字参数并产生该数字的平方作为返回值。甚至不清楚这两个 - - procedures - + + 过程 + - functions + 函数 - is a more efficient implementation. This depends upon the hardware - available. There are machines for which the obvious - implementation is the less efficient one. Consider a machine that has - extensive tables of logarithms and antilogarithms stored in a very - efficient manner. + 中哪个实现更高效。这取决于所用的硬件。有些机器中,“显然”的实现反而效率更低。设想一台拥有以极高效方式存储大量对数和反对数表的机器。 square_example - + (define (square x) (* x x)) - + function square(x) { return x * x; @@ -259,12 +235,12 @@ function square(x) { square_example - + (define (square x) (exp (double (log x)))) (define (double x) (+ x x)) - + function square(x) { return math_exp(double(math_log(x))); @@ -274,97 +250,87 @@ function double(x) { } - - - So a - - - procedure - + + + 因此, + + 过程 + - function + 函数 - should be able to suppress detail. The users of the + 应当能够屏蔽细节。该 - - procedure - + + 过程 + - function + 函数 - may not have written the + 的用户可能并非自己编写该 - - procedure - + + 过程 + - function + 函数 - themselves, but may have obtained it from another programmer as a - black box. A user should not need to know how the + ,而是从另一程序员那里作为 - - procedure - + + 过程作为黑盒作为黑盒 + - function + 作为黑盒作为黑盒 - is implemented in order to use it. + 获得。用户使用该 - - procedureblackas black box - + + 过程 + - blackas black box + 函数 - + 时不必了解其实现细节。 + - - Local names - + + 本地名称 + - local name + 本地名称 - - One detail of a + - procedures - functions + 过程 + 函数 - implementation that should not matter to the user of the + 实现的一个细节对于 - - procedure - - - function - + 过程 + 函数 - is the implementers choice of names for the + 的使用者来说不应有影响,就是实现者为 - procedures formal parameters. - functions parameters. + 过程的形式参数 + 函数的参数 - Thus, the following + 选择的名称。因此,以下 - - procedures - - - functions - + 过程 + 函数 - should not be distinguishable: - + 不应被区分: + square_example - + (define (square x) (* x x)) - + function square(x) { return x * x; @@ -373,496 +339,432 @@ function square(x) { square_example - + (define (square y) (* y y)) - + function square(y) { return y * y; } - - This principlethat the meaning of a + + 这个原则即一个 - - procedure - + + 过程 + - function + 函数 - should be independent of the parameter names used by its - authorseems on the surface to be self-evident, but its - consequences are profound. The simplest consequence is that the - parameter names of a + 的含义应当独立于其作者所使用的参数名称表面上看似显而易见,但其影响深远。最简单的后果是 - - procedure - + + 过程 + - function + 函数 - must be local to the body of the + 的参数名称必须在 - - procedure. - + + 过程体内局部。 + - function. + 函数体内局部。 - For example, we used square - - - in the definition of - good-enough? - + 例如,我们使用了 square + + + 在定义 + good-enough? + - in the declaration of + 在声明 is_good_enough - in our square-root + 在我们的平方根 - - procedure: - + + 过程中: + - function: + 函数中: - + is_good_enough_example abs_definition square_definition - + (define (good-enough? guess x) (< (abs (- (square guess) x)) 0.001)) - + function is_good_enough(guess, x) { return abs(square(guess) - x) < 0.001; } - - The intention of the author of - - - good-enough? - - - is_good_enough - - - is to determine if the square of the first argument is within a given - tolerance of the second argument. We see that the author of + + + 作者的意图是 + good-enough? + + + 作者的意图是 + is_good_enough + + 确定第一个参数的平方是否在给定容差范围内与第二个参数相符。我们看到 - - good-enough? - + + good-enough? + is_good_enough - used the name guess to refer to the - first argument and x to refer to the - second argument. The argument of square - is guess. If the author of - square used x - (as above) to refer to that argument, we see that the - x in + 的作者使用了名称 + guess 指代第一个参数和 x 指代第二个参数。 squareguess 。如果作者 square 使用 x + (如上所述)指的是该参数,我们看到 + x - - good-enough? - + + good-enough? + is_@good_@enough - must be a different x than the one - in square. Running the + 必须是不同的 x 比位于 square . 运行 - - procedure - + + 过程 + - function + 函数 - square must not affect the value - of x that is used by + square 不得影响 的值 + x - - good-enough?, - + + good-enough?, + is_good_enough, - because that value of x may be needed by + 使用的,因为那个值 + x 可能被 - - good-enough? - + + good-enough? + is_good_enough - after square is done computing. - - - If the parameters were not local to the bodies of their respective + 后需要 + square 计算完成。 + + + 如果这些参数不是各自 - procedures, - functions, + 过程体 + 函数体 - then the parameter x in - square could be confused with the parameter - x in + 的局部变量,那么 + square 中的参数 x 可能会与 - - good-enough?, - + + good-enough? 中的参数 x 混淆, + - is_@good_@enough, + is_@good_@enough 中的参数 x 混淆, - - and the behavior of + + 并且 - - good-enough? - + + good-enough? + is_good_enough - would depend upon which version of square - we used. Thus, square would not be the - black box we desired. - - - A - parametersnames of - nameparameterof a parameter + 的行为将依赖于所使用的哪个版本的 square。因此,square 就不会是我们想要的黑盒。 + + + 一个 + 参数名称 + 名称参数的名称 - - formal parameter of a procedure - + + 过程的形式参数 + - parameter of a function + 函数的参数 - has a very special role in the + 在 - - procedure definition, - + + 过程定义中, + - function declaration, + 函数声明中, - in that it doesnt matter what name the + 扮演着一个非常特殊的角色,即其 - - formal - + + 形式 + - parameter has. Such a name is called + 参数的名称无论是什么都无关紧要。这样的名称称为 - - bound variable - variablebound - a bound variable, and we say that the procedure definition - + + 绑定变量 + 变量绑定 + 一个绑定变量,我们说过程定义 + - bound name - namebound - bound, and we say that the function declaration + 绑定名称 + 名称绑定 + 绑定,我们说函数声明 - bind - binds its + 绑定 + 绑定 - - formal parameters. - + + 形式参数。 + - parameters. + 参数。 - The meaning of a + 如果一 - - procedure definition is unchanged if a bound variable - + + 过程定义在整个定义内一致地重新命名绑定变量时,其含义不变; + - function declaration is unchanged if a bound name + 函数声明在整个声明内一致地重新命名绑定名称时,其含义不变; - is consistently renamed throughout the - definitiondeclaration.The - concept of consistent renaming is actually subtle and difficult to - define formally. Famous logicians have made embarrassing errors - here. - If a + 一致重命名的概念实际上微妙且难以形式定义,著名的逻辑学家们在这里曾犯过尴尬的错误。 + 如果一个 - variable - name + 变量 + 名称 - is not bound, we say that it is + 没有被绑定,我们说它是 - - free variable - variablefree - + + 自由变量 + 变量自由 + - free name - namefree + 自由名称 + 名称自由 - free. The set of + 自由的。绑定某名称的 - expressions - statements + 表达式 + 语句 - for which a binding + 集合称为该名称的 - defines - declares - - a name is called the - - - scope of a variable - variablescope of - + + 变量作用域 + 变量作用域 + - scope of a name - namescope of + 名称作用域 + 名称作用域 - scope of that name. In a + 作用域。在 - - procedure definition, the bound variables - + + 过程定义中,作为 + - function declaration, the bound names + 函数声明中,作为 - declared as the - - formal parametersscope of - procedurescope of formal parameters - scope of a variableprocedures formal parameters - + + 形式参数作用域 + 过程形式参数作用域 + 变量作用域过程形式参数 + - parametersscope of - scope of parameters - scope of a namefunctions parameters + 参数作用域 + 参数作用域 + 名称作用域函数参数 - - formal parameters of the procedure - + + 过程的形式参数 + - parameters of the function + 函数的参数 - have the body of the + 将 - - procedure - + + 过程体 + - function + 函数体 - as their scope. - - - In the + 作为它们的作用域。 + + + 在上面 - - definition of good-enough? - + + good-enough?的定义中, + - declaration of is_good_enough + is_good_enough的声明中, - above, - guess and - x are - bound + guess和 + x是 + 绑定的 - - variables - + + 变量 + - names + 名称 - but + 但是 - - <, - -, - + + <, + -, + - abs - and square are free. - The meaning of + abssquare是自由的。 - - good-enough? - + + good-enough? + is_good_enough - should be independent of the names we choose for - guess and - x so long as they are distinct and - different from - - - <, - -, - - - abs - and square. (If we renamed - guess to - abs we would have introduced a bug by - - - capturing a free variable - bugcapturing a free variable - free variablecapturing - - - capturing a free name - bugcapturing a free name - free namecapturing - - - capturing the - - - variable - - - name - - - abs. - It would have changed from free to bound.) The meaning of - - - good-enough? - - - is_good_enough - - - is not independent of the + 的含义应该独立于我们选择的 + guess和 + x的名称,只要它们不同且不同于 - - names of its free variables, - - - choice of its free names, - + + <, + -, + - however. It surely depends upon the fact + abssquare。(如果我们将 + guess重命名为abs, + 就会引入一个通过捕获自由变量的错误, + 使得 + abs变量由自由变成绑定。) - - (external to this definition) - + + good-enough? + - (external to this declaration) + is_good_enough + 的意义却依赖于其自由变量的 - - that the symbol abs names a procedure - + + 名称, + - that the name abs refers to a function + 自由名称的选择, - for computing the absolute value of a number. + 这是因为(定义外部的)事实, - - Good-enough? - + + 符号abs命名了一个过程, + - The function is_good_enough + 名称abs指向一个函数, - will compute a different function if we substitute + 用于计算数字的绝对值。 - - cos - + + good-enough? + - math_cos - (the primitive cosine function) + is_good_enough - for abs in its + 如果我们在其定义中将 - - definition. - + + abs + - declaration. + math_cos(原始余弦函数) - local name - + 替换为 + abs,它将计算不同的函数。 + - - Internal + + 内部 - definitions - declarations + 定义 + 声明 - and block structure - + 与块结构 + - - - We have one kind of name isolation available to us so far: + + + 到目前为止,我们只有一种名称隔离可用: - - The formal parameters of a procedure - + + 过程的形式参数 + - The parameters of a function + 函数的参数 - are local to the body of the + 在过程体内是局部的 - procedure. - function. + 过程。 + 函数。 - The square-root program illustrates another way in which we would like to - control the use of names. - programstructure of - The existing program consists of separate + 平方根程序展示了我们希望控制名称使用的另一种方式。 + 程序结构 + 现有程序由独立的 - - procedures: - + + 过程组成: + - functions: + 函数组成: @@ -871,7 +773,7 @@ function is_good_enough(guess, x) { abs_definition square_definition average_definition - + (define (sqrt x) (sqrt-iter 1.0 x)) @@ -885,7 +787,7 @@ function is_good_enough(guess, x) { (define (improve guess x) (average guess (/ x guess))) - + function sqrt(x) { return sqrt_iter(1, x); @@ -903,135 +805,129 @@ function improve(guess, x) { } - - - The problem with this program is that the only - - - procedure - + + + 这个程序的问题在于,唯一对 + + 过程 + - function + 函数 - that is important to users of sqrt is - sqrt. The other + sqrt 用户重要的是 + sqrt。其他 - - procedures - + + 过程 + - functions + 函数 - - (sqrt-iter, - good-enough?, - + + (sqrt-iter, + good-enough?, + (sqrt_iter, - is_good_enough, + is_good_enough - and improve) only clutter up their minds. - They may not + 和 improve) 只是让他们的思维混乱。 + 他们可能不会 - - define any other procedure - + + 定义任何其他过程 + - declare any other function + 声明任何其他函数 - called + 名为 - - good-enough? - + + good-enough? + is_good_enough - as part of another program to work together - with the square-root program, because sqrt - needs it. The problem is especially severe in the construction of large - systems by many separate programmers. For example, in the construction - of a large library of numerical + 作为另一个程序的一部分与平方根程序一起工作,因为 sqrt + 需要它。这个问题在由很多独立程序员构建大型系统时尤为严重。例如,在构建大型数值 - procedures, - functions, + 过程 + 函数 - many numerical functions are computed as successive approximations and - thus might have + 库时,许多数值函数是作为连续逼近计算的,因此可能有 - - procedures - + + 过程 + - functions + 函数 - named + 命名为 - - good-enough? - + + good-enough? + is_good_enough - and improve as auxiliary + 和 improve 作为辅助 - procedures. - functions. + 过程。 + 函数。 - We would like to localize the + 我们希望将这些 - subprocedures, - subfunctions, + 子过程 + 子函数 - hiding them inside sqrt so that - sqrt could coexist with other - successive approximations, each having its own private + 局部化,隐藏在 sqrt 内,这样 + sqrt 就可以和其他连续逼近共存,每一个都有自己私有的 - good-enough? procedure. - - is_good_enough function. + good-enough? 过程。 + + is_good_enough 函数。 - To make this possible, we allow a + 为实现这一点,我们允许 - - procedure - + + 过程 + - function + 函数 - to have - block structure + 拥有 + 块结构 - - internal definition - + + 内部定义 + - internal declaration + 内部声明 - internal declarations that are local to that + 作为对该 - procedure. - function. + 过程 + 函数 - For example, in the square-root problem we can write + 局部的内部声明。例如,在平方根问题中我们可以这样写 sqrt_example_2 2.2360688956433634 abs_definition square_definition average_definition - + (define (sqrt x) (define (good-enough? guess x) (< (abs (- (square guess) x)) 0.001)) @@ -1042,7 +938,7 @@ function improve(guess, x) { guess (sqrt-iter (improve guess x) x))) (sqrt-iter 1.0 x)) - + function sqrt(x) { function is_good_enough(guess, x) { @@ -1060,131 +956,127 @@ function sqrt(x) { } - + - - + + - Any matching pair of braces designates a block, and - declarations inside the block are local to the block. - block - syntactic formsblock + 任何配对的花括号表示一个 ,块内的声明对于该块是局部的。 + + 语法形式 - Such nesting of + 这种 - definitions, - declarations, + 定义的嵌套, + 声明的嵌套, - called block structure, is basically the right solution to the - simplest name-packaging problem. But there is a better idea lurking here. - In addition to internalizing the + 称为 块结构,基本上是解决最简单名称封装问题的正确方法。但这里还有一个更好的想法。在将 - definitions of the auxiliary procedures, - declarations of the auxiliary functions, + 辅助过程的定义内置化之外, + 辅助函数的声明内置化之外, - we can simplify them. Since x is bound in the + 我们可以简化它们。既然 x 被绑定在 - definition - declaration + 定义 + 声明 - of sqrt, the + 中 sqrt - - procedures - + + 过程 + - functions + 函数 - good-enough?, - is_good_enough, + 是否足够好? + is_good_enough - improve, and + improve,和 - sqrt-iter, - which are defined internally to - sqrt_iter, - which are declared internally to + sqrt-iter, + 它们在……内部定义 + sqrt_iter, + 它们在……内部声明 - sqrt, are in the scope of - x. Thus, it is not necessary to pass - x explicitly to each of these + sqrt,位于……的作用域内 + x。因此,没有必要传递 + x 明确地传递给每个 - procedures. - functions. + 过程。 + 函数。 - Instead, we allow x to be a free + 相反,我们允许 x 成为一个自由的 - - internal definitionfree variable in - free variableinternalin internal definition - + + 内部定义中的自由变量 + 自由变量内部在内部定义中 + - internal declarationfree name in - free nameinternalin internal declaration + 内部声明中的自由名称 + 自由名称内部在内部声明中 - variable - name + 变量 + 名称 - in the internal + 在内部 - definitions, - declarations, + 定义中, + 声明中, - as shown below. Then x gets its value from - the argument with which the enclosing + 如下所示。然后 x 从其包含的 - - procedure - + + 过程 + - function + 函数 - sqrt is called. This discipline is called - lexical scoping - lexical scoping.Lexical scoping dictates that free + 的参数中获取其值 +sqrt 被调用。该规则称为 + 词法作用域 + 词法作用域词法作用域规定自由的 - - variables in a procedure - + + 过程中的变量 + - names in a function + 函数中的名称 - are taken to refer to bindings made by enclosing + 被认为引用由外层的 - - procedure definitions; - + + 过程定义所绑定的变量; + - function declarations; + 函数声明所绑定的变量; - that is, they are looked up in - environmentlexical scoping and - the environment in which the + 也就是说,它们是在 + 环境词法作用域和 + 定义 - - procedure was defined. - + + 过程的环境中查找的。 + - function was declared. + 函数声明的环境中查找的。 - We will see how this works in detail in chapter when we - study environments and the detailed behavior of the interpreter. - + 我们将在第章详细讨论环境和解释器的具体行为时看到它是如何工作的。 + sqrtblock structured sqrt_example_2 abs_definition square_definition average_definition - + (define (sqrt x) (define (good-enough? guess) (< (abs (- (square guess) x)) 0.001)) @@ -1195,7 +1087,7 @@ function sqrt(x) { guess (sqrt-iter (improve guess)))) (sqrt-iter 1.0)) - + function sqrt(x) { function is_good_enough(guess) { @@ -1214,50 +1106,44 @@ function sqrt(x) { - - We will use block structure extensively to help us break up large programs - into tractable pieces.Embedded + + 我们将广泛使用块结构来帮助我们将大型程序划分为易处理的部分。嵌入式 - - definitions must come first in a procedure - + + 定义必须出现在过程主体的最前面 + - declarations must come first in a function + 声明必须出现在函数主体的最前面 - body. + 。 - - internal definitionposition of - + + 内部定义<索引下标>的位置</索引下标> + - internal declarationposition of - + 内部声明<索引下标>的位置</索引下标> + - The management is not responsible for the consequences of running programs - that intertwine + 管理层不对运行交织了 - definition - declaration + 定义 + 声明 - and use; see also - footnotes - and - in section. + 和使用的程序所产生的后果负责;另请参见第脚注和第脚注,以及第节。 - The idea of block structure originated with the programming language - Algolblock structure - Algol60. It appears in most advanced programming languages and is an - important tool for helping to organize the construction of large programs. - programstructure of - block structure + 块结构的概念起源于编程语言 + Algol<索引下标>块结构</索引下标> + Algol 60。它出现在大多数先进的编程语言中,是帮助组织大型程序构建的重要工具。 + 程序<索引下标>结构</索引下标> + 块结构 - - internal definition - + + 内部定义 + - internal declaration + 内部声明 - +
    diff --git a/xml/zh/chapter1/section2/section2.xml b/xml/zh/chapter1/section2/section2.xml index 2cf709b47..1ec08a213 100644 --- a/xml/zh/chapter1/section2/section2.xml +++ b/xml/zh/chapter1/section2/section2.xml @@ -1,10 +1,10 @@
    - 程序 + 过程 函数 - 及其生成的过程 + 以及它们生成的进程 @@ -12,81 +12,79 @@ - 我们现在已经考虑了编程的元素:我们使用了 - 原始算术操作,组合了这些操作,并且 - 通过 + 我们现在已经考虑了编程的基本要素:我们使用了原始的算术运算,我们将这些运算组合起来,并且我们通过 - 将其定义为复合程序。 - 将其声明为复合函数。 - - 但这还不足以让我们说我们知道如何编程。我们的情况类似于一个已经 - 学会了国际象棋棋子如何移动规则的人,但对典型开局、战术或策略一无所知。像初学者的棋手一样, - 我们尚不知道该领域的常见使用模式。 - 我们缺乏对哪些移动是值得做的知识 + 将它们定义为复合过程。 + 将它们声明为复合函数。 + + 进行了抽象。 + 但这还不足以让我们说我们已经掌握了编程。我们的情况类似于一个学会了象棋棋子走法规则但并不了解典型开局、战术或策略的人。就像一个新手象棋玩家一样,我们还不知道该领域中常见的使用模式。 + 我们缺乏知道哪些走法值得走的知识 - (哪些程序值得定义) - (哪些函数值得声明) + (哪些过程值得定义)。 + (哪些函数值得声明)。 - 我们缺乏预测移动后果的经验 + 我们缺乏预测走子后果的经验 - (执行一个程序)。 - (执行一个函数)。 + (执行过程)。 + (执行函数)。 - 视觉化所考虑的行动后果的能力对于成为一名专家程序员至关重要,正如在任何合成的创造性活动中一样。例如,成为一名专家摄影师,必须学会如何观察一个场景,并知道每个可能的曝光选择下,图像中每个区域在打印时将显得多么黑暗,以及 + 预见所考虑的操作后果的能力,对于成为一名专家程序员至关重要,正如在任何综合性的创造性活动中一样。例如,成为一名专家级摄影师,必须学会如何观察一个场景,并了解对于每种可能的曝光和 - 显影。 + 显影。 处理选项。 - 术语显影可能对数字时代的主流受众并不熟悉。 + 在数字时代,主流观众可能不熟悉“显影”一词。 - 只有这样,人才能向后推理,规划构图、照明、曝光和 + 只有这样,才能反向推理,规划构图、光线、曝光和 - 显影 + 显影 处理 - 以获得所需的效果。因此编程也是如此,我们在规划一个过程的行动方案,并通过程序控制该过程。要成为专家,我们必须学会可视化各种类型生成的 + ,以达到预期效果。编程也是如此,我们正在规划过程所采取的行动路线,并通过程序来控制该过程。要成为专家,我们必须学会预测由各种类型 - 程序。 - 函数。 + 过程产生的过程。 + 函数产生的过程。 - 只有在我们培养出这样的技能之后,才能可靠地构造出表现出所需行为的程序。 + 只有当我们具备了这种技能,才能学习可靠地构造出表现出理想行为的程序。 一个 - 程序 + 过程 函数 是一个 - 模式作为过程的局部演化模式 - 用于计算过程的局部演化的模式。它指定了过程的每个阶段如何建立在上一个阶段之上。我们希望能够对局部 + 模式 作为过程局部演化的模式 + 用于计算过程局部演化的模式。它规定了过程的每个阶段如何建立在前一阶段的基础上。我们希望能够对过程的整体,或全局行为进行陈述,而该过程的局部 过程的局部演化 过程局部演化 - 已由 + 演化已经由一个 - 程序 - 函数 + 过程指定。 + 函数指定。 - 指定的过程的整体或全局行为做出陈述。一般来说,这很难做到,但我们至少可以尝试描述一些典型的过程演化模式。 + 一般来说,这非常困难,但我们至少可以尝试描述一些典型的过程演化模式。 - 在本节中,我们将检查一些由简单 + 在本节中,我们将考察一些由简单 - 程序 - 函数 + 过程生成 + 函数生成 - 生成的常见形状。我们还将研究这些过程消耗时间和空间这两种重要计算资源的速率。我们将考虑的 + 的常见形态。我们还将研究这些过程消耗重要计算资源——时间和空间的速率。 + 我们将考虑的 - 程序 + 过程 函数 - - 非常简单。它们的作用类似于摄影中的测试模式:作为过于简化的原型模式,而不是实际的例子。 + + 非常简单。它们的作用类似于摄影中的测试模式:作为过度简化的典型模式,而不是其自身的实用例子。 diff --git a/xml/zh/chapter1/section2/subsection1.xml b/xml/zh/chapter1/section2/subsection1.xml index 34ad06554..6500cfe6c 100644 --- a/xml/zh/chapter1/section2/subsection1.xml +++ b/xml/zh/chapter1/section2/subsection1.xml @@ -1,14 +1,14 @@ - Linear Recursion and Iteration + 线性递归与迭代 - iterative processrecursive process vs. - recursive processiterative process vs. + 迭代过程递归过程 对比. + 递归过程迭代过程 对比. - - We begin by considering the - factorial - factorial function, defined by + + 我们首先考虑 + 阶乘 + 阶乘函数,其定义为 \[ \begin{array}{lll} @@ -16,10 +16,9 @@ \end{array} \] - There are many ways to compute factorials. One way is to make use of - the observation that $n!$ is equal to - $n$ times $(n-1)!$ for - any positive integer$n$: + 计算阶乘的方法有很多种。其中一种是利用观察到的事实:对于任意正整数$n$, + $n!$ 等于 + $n$ 乘以 $(n-1)!$ \[ \begin{array}{lll} @@ -27,26 +26,24 @@ \end{array} \] - Thus, we can compute $n!$ by computing - $(n-1)!$ and multiplying the - result by $n$. If we add the stipulation that 1! - is equal to 1, - this observation translates directly into a + 因此,我们可以通过计算 $(n-1)!$ 并乘以 + $n$ 来计算 $n!$。如果我们加上规定1! + 等于1,这一观察可以直接转化成一个 - procedure: - computer function: + 过程: + 计算机函数: - factoriallinear recursive version + 阶乘线性递归版本 factorial_definition factorial_example 120 - + (define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1))))) - + function factorial(n) { return n === 1 @@ -58,90 +55,77 @@ function factorial(n) { factorial_example - + (factorial 5) - + factorial(5); - - We can use the substitution model of - section to watch this - substitution model of procedure applicationshape of process - procedure in action computing 6!, as shown in - figure. + + 我们可以使用第节的替换模型观察这个 + 过程应用的替换模型过程形态 + 来计算6!,如图所示。
    - A linear recursive process for computing 6!. + 计算6!的线性递归过程。
    - +
    - We can use the substitution model of - section to watch this - substitution model of function applicationshape of process - function in action computing 6!, as shown in - figure. + 我们可以使用第节的替换模型观察这个 + 函数应用的替换模型过程形态 + 来计算6!,如图所示。 - - + <!-- 图像后移以适应SICP JS分页 --> + <!-- 图像代码在本文件后面以PDF_ONLY形式重复 -->
    - A linear recursive process for computing 6!. + 计算6!的线性递归过程。
    -
    - - - Now lets take a different perspective on computing factorials. We - could describe a rule for computing $n!$ by - specifying that we first multiply 1 by 2, then multiply the result by 3, - then by 4, and so on until we reach $n$. - More formally, we maintain a running product, together with a counter - that counts from 1 up to $n$. We can describe - the computation by saying that the counter and the product simultaneously - change from one step to the next according to the rule + + + + 现在让我们换一种计算阶乘的视角。我们可以通过说明:先将1乘以2,然后将结果乘以3,再乘以4,依此类推,直到乘到$n$,来描述计算< LATEXINLINE>$n!$</LATEXINLINE>的规则。 + 更正式地说,我们维护一个正在进行的积,以及一个从1计数到$n$的计数器。我们可以描述计算过程为计数器和积同时根据如下规则从一步变化到下一步: \[ \begin{array}{lll} - \textrm{product} & \leftarrow & \textrm{counter} \cdot \textrm{product}\\ - \textrm{counter} & \leftarrow & \textrm{counter} + 1 + \textrm{积} & \leftarrow & \textrm{计数器} \cdot \textrm{积}\\ + \textrm{计数器} & \leftarrow & \textrm{计数器} + 1 \end{array} \] -and stipulating that $n!$ is the value of the - product when the counter exceeds $n$. - + 并规定当计数器超过$n$时,积的值即为$n!$。 +
    - - Once again, we can recast our description as a + + 我们可以再次将描述重新表述为计算阶乘的 - procedure - function - - for computing - factorials:In a real program we would probably use the - block structure introduced in the last section to hide the + 过程 + 函数 + : + 在实际程序中,我们可能会使用上一节介绍的块结构来隐藏 - - definition of fact-iter: - + + fact-iter 的定义: + - declaration of fact_iter: + fact_iter 的声明: factorial_example 120 - + (define (factorial n) (define (iter product counter) (if (> counter n) @@ -149,7 +133,7 @@ and stipulating that $n!$ is the value of the (iter (* counter product) (+ counter 1)))) (iter 1 1)) - + function factorial(n) { function iter(product, counter) { @@ -162,14 +146,12 @@ function factorial(n) { } - We avoided doing this here so as to minimize the number of things to - think about at - once. + 这里为了尽量减少需要同时考虑的内容,我们避免了这样做。 - factoriallinear iterative version + factorial线性迭代版本 factorial_iterative_definition factorial_example - + (define (factorial n) (fact-iter 1 1 n)) @@ -179,7 +161,7 @@ function factorial(n) { (fact-iter (* counter product) (+ counter 1) max-count))) - + function factorial(n) { return fact_iter(1, 1, n); @@ -193,361 +175,285 @@ function fact_iter(product, counter, max_count) { } - As before, we can use the substitution model to visualize the process + 像之前一样,我们可以使用替换模型来形象地展示 - +
    - A linear iterative process for computing - $6!$. + 计算 + $6!$的线性迭代过程。
    - of computing $6!$, as shown in - figure. - + 计算 $6!$的过程,如 + 图所示。 +
    - - + <!-- 图像为SICP JS排版以前移至此处 --> + <!-- 图像代码复制自本文件之前的WEB_ONLY部分 -->
    - A linear recursive process for computing 6!. + 计算6!的线性递归过程。
    - A linear iterative process for computing - $6!$. + 计算 + $6!$的线性迭代过程。
    - of computing $6!$, as shown in - figure. + 计算 $6!$的过程,如 + 图所示。
    -
    +
    - - Compare the two processes. From one point of view, they seem hardly - different at all. Both compute the same mathematical function on the - same domain, and each requires a number of steps proportional to + + 比较这两个过程。从一个角度看,它们几乎没有什么不同。两者都在相同的定义域上计算相同的数学函数,并且每个都需要与 + $n$ + 成正比的步骤数来计算 + $n!$。事实上,这两个过程甚至执行了相同的乘法序列,得到相同的部分积序列。另一方面,当我们考虑 + 过程的形状 + 过程形状 + “形状”时,我们发现它们的发展方式却截然不同。 + + + + 考虑第一个过程。替换模型显示了一个先展开后收缩的形状,如图 + + 中箭头所示。展开发生在过程构建了一系列 + 延迟操作 + 延迟操作(在此情况下是一系列乘法)时。收缩则发生在这些操作被实际执行时。这种以延迟操作链为特征的过程称为 + 递归过程 + 过程递归 + 递归过程。执行这个过程需要解释器跟踪后续要执行的操作。在计算 + $n!$时,延迟乘法链的长度,因此跟踪它所需的信息量, + 线性增长 + 随着 + $n$ + 线性增长(与 $n$ - to compute $n!$. Indeed, both processes even - carry out the same sequence of multiplications, obtaining the same sequence - of partial products. On the other hand, when we consider the - shape of a process - processshape of - shapes of the two processes, we find that they evolve quite - differently. - - - - Consider the first process. The substitution model reveals a shape of - expansion followed by contraction, indicated by the arrow in - figure. - The expansion occurs as the process builds up a chain of - deferred operations - deferred operations (in this case, a chain of multiplications). - The contraction occurs as the operations are actually performed. This - type of process, characterized by a chain of deferred operations, is called a - recursive process - processrecursive - recursive process. Carrying out this process requires that the - interpreter keep track of the operations to be performed later on. In the - computation of $n!$, the length of the chain of - deferred multiplications, and hence the amount of information needed to - keep track of it, - linear growth - grows linearly with $n$ (is proportional to - $n$), just like the number of steps. - Such a process is called a - recursive processlinear - linear recursive process - processlinear recursive - linear recursive process. - - - - By contrast, the second process does not grow and shrink. At each - step, all we need to keep track of, for any $n$, - are the current values of the + 成正比),就像步骤数一样。这种过程称为 + 递归过程线性 + 线性递归过程 + 过程线性递归 + 线性递归过程。 + + + + 相比之下,第二个过程既不展开也不收缩。对于任何 + $n$,在每一步中,我们只需跟踪 - variables - names + 变量 + 名称 - product, counter, - and + productcounter - max-count. - max_count. + max-count + max_count - We call this an - iterative process - processiterative - iterative process. In general, an iterative process is one whose - state can be summarized by a fixed number of - state variable - state variables, together with a fixed rule that describes how - the state variables should be updated as the process moves from state to - state and an (optional) end test that specifies conditions under which the - process should terminate. In computing $n!$, the - number of steps required grows linearly with $n$. - Such a process is called a - iterative processlinear - linear iterative process - processlinear iterative - linear iterative process. - + 当前的值。我们称这种为 + 迭代过程 + 过程迭代 + 迭代过程。一般来说,迭代过程是指其状态可以通过固定数量的 + 状态变量 + 状态变量来总结,同时有一条固定规则描述状态变量应如何在过程从一个状态向另一个状态转移时进行更新,且(可选)有一个终止测试,用于指定过程应终止的条件。在计算 + $n!$时,所需步骤数随 + $n$线性增长。这种过程称为 + 迭代过程线性 + 线性迭代过程 + 过程线性迭代 + 线性迭代过程。 + - - The contrast between the two processes can be seen in another way. - In the iterative case, the state variables provide a complete description of - the state of the process at any point. If we stopped the computation between - steps, all we would need to do to resume the computation is to supply the - interpreter with the values of the three state variables. Not so with the - recursive process. In this case there is some additional - hidden information, maintained by the interpreter and not - contained in the state variables, which indicates where the process - is in negotiating the chain of deferred operations. The longer the - chain, the more information must be maintained.When we discuss the - implementation of - - procedures - functions - - on register machines in chapter, we will see that any iterative - process can be realized in hardware as a machine that has a - fixed set of registers and no auxiliary memory. In contrast, realizing a - recursive process requires a machine that uses an - auxiliary data structure known as a - stack - stack. - substitution model of procedurefunction applicationshape of process - - - - In contrasting iteration and recursion, we must be careful not to - confuse the notion of a - recursive procedurefunctionrecursive process vs. - recursive processrecursive procedurefunction vs. - recursive process with the notion of a recursive - - - procedure. - - - function. - - - When we describe a - - procedure - function - - as recursive, we are referring to the syntactic fact that the - - procedure definition - function declaration - - refers (either directly or indirectly) to the - - procedure - function - - itself. But when we describe a process as following a pattern that is, say, - linearly recursive, we are speaking about how the process evolves, not - about the syntax of how a - - procedure - function - - is written. It may seem disturbing that we refer to a recursive + + 这两种过程的对比也可以从另一个方面体现出来。 + 在迭代情况下,状态变量提供了过程在任意时刻状态的完整描述。 + 如果我们在步骤之间停止计算,恢复计算所需做的仅仅是向解释器提供三个状态变量的值。 + 递归过程则不然。在此情况下,解释器维护着一些额外的隐藏的信息,这些信息不包含在状态变量中, + 它表明了在处理一连串延迟操作时过程所处的位置。 + 链越长,必须维护的信息就越多。当我们在第章讨论 - procedure - function - - such as - - fact-iter + 过程 + 函数 + 在寄存器机上的实现时,我们将看到任何迭代过程都可以“通过硬件”实现为具有一组固定寄存器且无辅助内存的机器。 + 对比之下,实现递归过程需要使用一种称为的辅助数据结构的机器。 + 过程函数应用的替换模型过程形态 + + + + 在对比迭代和递归时,我们必须小心不要将递归的过程函数递归过程vs. + 递归过程递归的过程函数 vs. + 递归过程的概念,与递归的过程函数的概念相混淆。 + 当我们描述一个 + 过程 + 函数 + 为递归时,我们指的是语法上的事实,即该 + 过程定义 + 函数声明 + (无论是直接还是间接)引用了该 + 过程 + 函数 + 本身。但当我们描述一个过程遵循某种模式,比如线性递归时,我们讲的是过程如何演变,而不是 + 过程 + 函数 + 如何被书写的语法。 + 说一个递归的 + 过程 + 函数 + ,例如 + fact-iter fact_iter - - as generating an iterative process. However, the process really is - iterative: Its state is captured completely by its three state variables, - and an interpreter need keep track of only three - - variables - names - - in order to execute the process. - + 产生的是一个迭代过程,可能让人感到不安。然而,该过程实际上是迭代的:它的状态完全由三个状态变量捕获,且解释器执行该过程时仅需跟踪三个 + 变量 + 名称 + 。 + - - One reason that the distinction between process and + + 过程和 - procedure - function + 过程 + 函数 - may be confusing is that most implementations of common languages + 区别可能令人困惑的一个原因是,大多数常用语言的实现 - - (including - Adarecursive procedures - Pascalrecursive procedures - Crecursive procedures - Ada, Pascal, and C) - + + (包括 + Ada递归过程 + Pascal递归过程 + C递归过程 + Ada、Pascal 和 C) + - (including - Crecursive functions in - C, - Java, recursive functions in - Java, and - Python, recursive functions in - Python) + (包括 + C递归函数 + C, + Java,递归函数 + Java,以及 + Python,递归函数 + Python) - are designed in such a way that the interpretation of - any recursive + 都是这样设计的:解释任何递归的 - procedure - function + 过程 + 函数 - consumes an amount of memory that grows with the number of + 会消耗随着 - procedure - function + 过程 + 函数 - calls, even when the process described is, in principle, iterative. - As a consequence, these languages can describe iterative processes only - by resorting to special-purpose - looping constructs - looping constructs such as - $\texttt{do}$, + 调用次数而增长的内存,尽管所描述的过程原则上是迭代的。 + 因此,这些语言只能通过借助专用的 + 循环结构 + 循环结构来描述迭代过程,例如 +$\texttt{do}$, $\texttt{repeat}$, $\texttt{until}$, - $\texttt{for}$, and - $\texttt{while}$. - The implementation of + $\texttt{for}$$\texttt{while}$ - Scheme + Scheme JavaScript - we shall consider in chapter does not share this defect. It will - execute an iterative process in constant space, even if the iterative - process is described by a recursive + 的实现我们将在第章中考虑不具有这个缺陷。它将以常量空间执行迭代过程,即使迭代过程是由递归 - procedure. - function. + 过程。 + 函数。 - - An implementation with this property is called - tail recursion - tail-recursive. With a tail-recursive implementation, - iteration can be expressed using the ordinary procedure - iterative processimplemented by procedure call - call mechanism, so that special iteration constructs are useful only as - syntactic sugarlooping constructs as - syntactic sugar.Tail recursion has long been - known as a compiler optimization trick. A coherent semantic basis for - tail recursion was provided by - Hewitt, Carl Eddie - Carl Hewitt (1977), who explained it in - message passingtail recursion and - terms of the message-passing model of computation that we - shall discuss in chapter. Inspired by this, Gerald Jay Sussman - and - Steele, Guy Lewis Jr. - Guy Lewis Steele Jr.(see Steele 1975) - constructed a tail-recursive interpreter for Scheme. Steele later showed - how tail recursion is a consequence of the natural way to compile - procedure - Sussman, Gerald Jay - calls (Steele 1977). - The IEEE standard for Scheme requires that Scheme implementations - tail recursionSchemein Scheme - be tail-recursive. - + + 具有此特性的实现称为 + 尾递归 + 尾递归的。使用尾递归实现, + 迭代可以使用普通的过程调用机制 + 迭代过程由过程调用实现 + 来表达,因此特殊的迭代结构仅作为 + 语法糖循环结构作为 + 语法糖而有用。尾递归长期以来一直被认为是编译器的优化技巧。尾递归的连贯语义基础由 + 休伊特, 卡尔 艾迪 + 卡尔·休伊特Hewitt (1977)提供,他在 + 消息传递尾递归和 + 我们将在第章讨论的 + 消息传递计算模型中解释了它。受此启发,杰拉尔德·杰伊·萨斯曼 + 和 + 斯蒂尔, 盖·刘易斯 小 + 盖·刘易斯·斯蒂尔 小(参见Steele 1975) + 构造了一个用于 Scheme 的尾递归解释器。斯蒂尔后来展示了尾递归是编译过程调用的自然结果 + 萨斯曼, 杰拉尔德·杰伊 + (Steele 1977)。 + Scheme 的 IEEE 标准要求 Scheme 实现 + 尾递归Scheme + 必须是尾递归的。 + - An implementation with this property is called - tail recursion - tail-recursive.Tail recursion has long been - known as a compiler optimization trick. A coherent semantic basis for - tail recursion was provided by - Hewitt, Carl Eddie - Carl Hewitt (1977), who explained it in - message passingtail recursion and - terms of the message-passing model of computation that we - shall discuss in chapter. Inspired by this, Gerald Jay Sussman - and - Steele, Guy Lewis Jr. - Guy Lewis Steele Jr.(see Steele 1975) - constructed a tail-recursive interpreter for Scheme. Steele later showed - how tail recursion is a consequence of the natural way to compile - function calls - Sussman, Gerald Jay - (Steele 1977). - The IEEE standard for Scheme requires that Scheme implementations - tail recursionSchemein Scheme - tail recursionJavaScriptin JavaScript - Schemetail recursion in - JavaScripttail recursion in - be tail-recursive. The ECMA standard for JavaScript eventually followed - suit with ECMAScript 2015 (ECMA 2015). Note, however, - that as of this writing (2021), most implementations of JavaScript do - not comply with this standard with respect to tail recursion. - With a tail-recursive implementation, - iterative processimplemented by function call - iteration can be expressed using the ordinary function - call mechanism, so that special iteration constructs are useful only as - syntactic sugarlooping constructs as - syntactic sugar.Exercise - explores JavaScript's while loops as syntactic - sugar for functions that give rise to iterative processes. - The full language JavaScript, like other conventional languages, - features a plethora of syntactic - forms, all of which can be expressed more uniformly in the - language Lisp. - This, together with the fact that these constructs typically involve - semicolons whose placement rules are sometimes not obvious, - led Alan Perlis to quip: Syntactic sugar causes - cancer of the semicolon. - syntactic sugar - Perlis, Alan J.quips by - semicolon (;)cancer of + 具有此特性的实现称为 + 尾递归 + 尾递归的尾递归长期以来一直被认为是编译器的优化技巧。尾递归的连贯语义基础由 + 休伊特, 卡尔 艾迪 + 卡尔·休伊特 (1977),他在 + 消息传递尾递归和 + 我们将在第章讨论的 + 消息传递计算模型中解释了它。受此启发,杰拉尔德·杰伊·萨斯曼 + 和 + 斯蒂尔, 盖·刘易斯 小 + 盖·刘易斯·斯蒂尔 小(参见Steele 1975) + 构造了一个用于 Scheme 的尾递归解释器。斯蒂尔后来展示了尾递归是编译函数调用的自然结果 + 萨斯曼, 杰拉尔德·杰伊 + (Steele 1977)。 + Scheme 的 IEEE 标准要求 Scheme 实现 + 尾递归Scheme + 尾递归JavaScript + Scheme中的尾递归 + JavaScript中的尾递归 + 必须是尾递归的。ECMA 标准随后在 ECMAScript 2015 (ECMA 2015)中也采纳了这一点。但请注意,截至本文撰写时(2021 年),大多数 JavaScript 实现 + 并未遵守此标准中关于尾递归的规定。 + 使用尾递归实现, + 迭代过程由函数调用实现 + 迭代可以使用普通的函数调用机制表达,因此特殊的迭代结构仅作为 + 语法糖循环结构作为 + 语法糖而有用。练习探讨了 JavaScript 的 while 循环作为生成迭代过程的函数的语法糖。 + 完整语言 JavaScript,像其他传统语言一样,具有大量的语法形式, + 所有这些都可以在 Lisp 语言中更统一地表达。这一点,加上这些结构通常涉及分号,其放置规则有时不明显, + 促使艾伦·珀利斯戏言:语法糖导致分号癌症。 + 语法糖 + 珀利斯, 艾伦·J.的戏言 + 分号 (;)癌症 - iterative processrecursive process vs. - recursive processiterative process vs. - + 迭代过程递归过程与 + 递归过程迭代过程与 + + - Each of the following two + 以下两个 - procedures - functions + 过程 + 函数 - defines a method for adding two positive integers in terms of the + 都定义了一种利用 - procedures - functions + 过程 + 函数 - inc, which increments its argument by 1, - and dec, which decrements its argument by 1. + inc(将其参数加 1)和 dec(将其参数减 1)来对两个正整数求和的方法。 inc_dec_definition - + (define (inc x) (- x -1)) (define (dec x) (- x 1)) - + function inc(x) { return x + 1; @@ -560,9 +466,9 @@ function dec(x) { plus_example - + (+ 4 5) - + plus(4, 5); @@ -572,12 +478,12 @@ plus(4, 5); inc_dec_definition 9 plus_example - + (define (+ a b) (if (= a 0) b (inc (+ (dec a) b)))) - + function plus(a, b) { return a === 0 ? b : inc(plus(dec(a), b)); @@ -589,12 +495,12 @@ function plus(a, b) { inc_dec_definition 9 plus_example - + (define (+ a b) (if (= a 0) b (+ (dec a) (inc b)))) - + function plus(a, b) { return a === 0 ? b : plus(dec(a), inc(b)); @@ -602,22 +508,22 @@ function plus(a, b) { - Using the substitution model, illustrate the process generated by each + 使用替换模型,说明每个 - procedure - function + 过程 + 函数 - in evaluating + 在计算 - (+ 4 5). - plus(4, 5);. + (+ 4 5)时产生的过程。 + plus(4, 5);时产生的过程。 - Are these processes iterative or recursive? + 这些过程是迭代的还是递归的? - The process generated by the first function is recursive. + 第一个函数产生的过程是递归的。 plus(4, 5) @@ -639,7 +545,7 @@ inc( 8 ) 9 - The process generated by the second function is iterative. + 第二个函数产生的过程是迭代的。 plus(4, 5) @@ -660,30 +566,32 @@ plus(0, 9) - + + + - The following + 以下 - procedure - function + 过程 + 函数 - computes a mathematical function called - Ackermanns function - function (mathematical)Ackermanns - Ackermanns function. + 计算一种数学函数,称为 + 阿克曼函数 + 函数(数学)阿克曼函数 + 阿克曼函数。 ackermann_definition ackermann_example - + (define (A x y) (cond ((= y 0) 0) ((= x 0) (* 2 y)) ((= y 1) 2) (else (A (- x 1) (A x (- y 1)))))) - + function A(x, y) { return y === 0 @@ -697,22 +605,20 @@ function A(x, y) { - What are the values of the following + 以下 - - expressions? - - - statements? - + 表达式 + 语句 + 的值是多少? + ackermann_example ackermann_definition 1024 - + (A 1 10) - + A(1, 10); @@ -721,9 +627,9 @@ A(1, 10); ackermann_definition 65536 - + (A 2 4) - + A(2, 4); @@ -732,30 +638,29 @@ A(2, 4); ackermann_definition 65536 - + (A 3 3) - + A(3, 3); - Consider the following + 考虑以下 - procedures, - functions, + 过程, + 函数, - where A is the + 其中 A 是上面定义的 - procedure defined - function declared - - above: + 过程 + 函数 + : fghk_definition fghk_example ackermann_definition - + (define (f n) (A 0 n)) (define (g n) (A 1 n)) @@ -763,7 +668,7 @@ A(3, 3); (define (h n) (A 2 n)) (define (k n) (* 5 n n)) - + function f(n) { return A(0, n); @@ -784,37 +689,37 @@ function k(n) { fghk_example 80 fghk_definition - + (k 4) - + k(4); - Give concise mathematical definitions for the functions computed by - the + 请简明地用数学定义描述 - procedures - functions + 过程 + 函数 - f, g, and - h for positive integer values of - $n$. For example, - $k(n)$ computes - $5n^2$. + fg 和 + h + 对正整数 $n$ 计算的函数。例如, + $k(n)$ 计算 + $5n^2$ - The function $f(n)$ computes - $2 n$, - the function $g(n)$ computes - $2^n$, and - the function $h(n)$ computes - $2^{2^{\cdot^{\cdot^{\cdot^2}}}}$ - where the number of 2s in the chain of exponentiation is - $n$. + 函数 $f(n)$ 计算 + $2 n$, + 函数 $g(n)$ 计算 + $2^n$, + 函数 $h(n)$ 计算 + $2^{2^{\cdot^{\cdot^{\cdot^2}}}}$, + 其中指数塔中 2 的个数为 + $n$ - + + - +
    diff --git a/xml/zh/chapter1/section2/subsection2.xml b/xml/zh/chapter1/section2/subsection2.xml index 65d191ac1..11557c3c0 100644 --- a/xml/zh/chapter1/section2/subsection2.xml +++ b/xml/zh/chapter1/section2/subsection2.xml @@ -1,50 +1,50 @@ - Tree Recursion + 树形递归 - tree-recursive process - processtree-recursive - recursive processtree + 树形递归过程 + 过程树形递归 + 递归过程树形 - - Another common pattern of computation is called tree recursion. - As an example, consider computing the sequence of - Fibonacci numbers - Fibonacci numbers, - in which each number is the sum of the preceding two: + + 另一种常见的计算模式称为树形递归。 + 例如,考虑计算 + 斐波那契数 + 斐波那契数列, + 其中每个数字都是前两个数字的和: \[\begin{array}{l} 0, 1, 1, 2, 3, 5, 8, 13, 21, \ldots \end{array}\] - In general, the Fibonacci numbers can be defined by the rule + 一般来说,斐波那契数可以由以下规则定义: \[\begin{array}{lll} \textrm{Fib}(n) & = & \left\{ \begin{array}{ll} - 0 & \mbox{if $n=0$}\\ - 1 & \mbox{if $n=1$}\\ - \textrm{Fib}(n-1)+\textrm{Fib}(n-2) & \mbox{otherwise} + 0 & \mbox{如果 $n=0$}\\ + 1 & \mbox{如果 $n=1$}\\ + \textrm{Fib}(n-1)+\textrm{Fib}(n-2) & \mbox{否则} \end{array} \right. \end{array}\] - We can immediately translate this definition into a recursive + 我们可以立即将这个定义转化为一个递归 - procedure - function + 过程 + 函数 - for computing Fibonacci numbers: + 来计算斐波那契数: - fibtree-recursive version + fib树形递归版本 fib_definition fib_example - + (define (fib n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2)))))) - + function fib(n) { return n === 0 @@ -60,193 +60,181 @@ function fib(n) { fib_example fib_definition 8 - + (fib 6) - + fib(6); - +
    - The tree-recursive process generated in computing - (fib 5). + 计算 + (fib 5)时生成的树形递归过程。
    - +
    - The tree-recursive process generated in computing - fib(5). + 计算 + fib(5)时生成的树形递归过程。
    -
    +
    - - Consider the pattern of this computation. To compute - - (fib 5), - fib(5), - - we compute - - (fib 4) - fib(4) - - and - - (fib 3). - fib(3). - - To compute - - (fib 4), - fib(4), - - we compute - - (fib 3) - fib(3) - - and - - (fib 2). - fib(2). - - In general, the evolved process looks like a tree, as shown in - - - figure. - - - figure. - - - Notice that the branches split into - two at each level (except at the bottom); this reflects the fact that the - fib - - procedure - function - - calls itself twice each time it is invoked. - + + 考虑这个计算的模式。为了计算 + + (fib 5) + fib(5) + + 我们计算 + + (fib 4) + fib(4) + + 和 + + (fib 3) + fib(3) + + 为了计算 + + (fib 4) + fib(4) + + 我们计算 + + (fib 3) + fib(3) + + 和 + + (fib 2) + fib(2) + + 一般来说,演化出的过程看起来像一棵树,如 + + + 图所示。 + + + 图所示。 + + + 注意,树枝在每一层(底部除外)分成两个,这反映出 + fib + + 过程 + 函数 + + 每次调用时都会调用自身两次。 + - - This + + 这个 - procedure - function + 过程 + 函数 - is instructive as a prototypical tree recursion, but it is a terrible way to - compute Fibonacci numbers because it does so much redundant computation. - Notice in + 作为典型的树递归是有指导意义的,但它是计算斐波那契数的一种非常糟糕的方法,因为它执行了大量的冗余计算。注意在 - - figure - + + 图 + - figure + 图 - that the entire - computation of + 中,整个 - - (fib 3)almost - half the workis - + + 计算(fib 3)几乎一半的工作是 + - fib(3)almost - half the workis + 计算fib(3)几乎一半的工作 - duplicated. In fact, it is not hard to show that the number of times the + 被重复了。事实上,不难证明该 - procedure - function + 过程 + 函数 - will compute + 计算 - (fib 1) + (fib 1) fib(1) - or + 或 - - (fib 0) - + + (fib 0) + fib(0) - (the number of leaves in the above tree, in general) is precisely - $\textrm{Fib}(n+1)$. To get an idea of how - bad this is, one can show that the value of + (上面树中叶节点的数量,一般来说)恰好是 + $\textrm{Fib}(n+1)$。为了了解这种情况有多糟糕,可以证明 $\textrm{Fib}(n)$ - exponential growthtree-recursiveof tree-recursive Fibonacci-number computation - grows exponentially with $n$. More precisely - (see exercise), - $\textrm{Fib}(n)$ is the closest integer to - $\phi^{n} /\sqrt{5}$, where + 指数增长树递归的树递归斐波那契数计算 + 随着$n$呈指数增长。更准确地说(参见练习), + $\textrm{Fib}(n)$是最接近整数的 + $\phi^{n} /\sqrt{5}$,其中 \[\begin{array}{lllll} - \phi&=&(1+\sqrt{5})/2 & \approx & 1.6180 + \phi &= & (1+\sqrt{5})/2 & \approx & 1.6180 \end{array}\] - is the - golden ratio - golden ratio, which satisfies the equation + 是 + 黄金分割比 + 黄金分割比,满足方程 \[\begin{array}{lll} - \phi^{2} &=&\phi + 1 + \phi^{2} &= & \phi + 1 \end{array}\] - Thus, the process uses a number of steps that grows exponentially with the - input. On the other hand, the space required grows only linearly with the - input, because we need keep track only of which nodes are above us in the - tree at any point in the computation. In general, the number of steps - required by a tree-recursive process will be proportional to the number of - nodes in the tree, while the space required will be proportional to the - maximum depth of the tree. - + 因此,该过程使用的步骤数量随输入呈指数增长。另一方面,所需的空间只随输入线性增长,因为我们只需要在计算过程中的任意时刻跟踪树中位于我们上方的那些节点。一般来说,一个树递归过程所需的步骤数量与树中节点数成正比,而所需空间与树的最大深度成正比。 + - - We can also formulate an iterative process for computing the Fibonacci - numbers. The idea is to use a pair of integers $a$ - and $b$, initialized to - $\textrm{Fib}(1)=1$ and - $\textrm{Fib}(0)=0$, and to repeatedly apply the - simultaneous transformations + + 我们也可以制定一个计算斐波那契数的迭代过程。其思想是使用一对整数 + $a$ + 和 + $b$,初始值分别为 + $\textrm{Fib}(1)=1$和 + $\textrm{Fib}(0)=0$,然后反复应用同时变换 \[\begin{array}{lll} a & \leftarrow & a+b \\ b & \leftarrow & a \end{array}\] - It is not hard to show that, after applying this transformation - $n$ times, $a$ and - $b$ will be equal, respectively, to - $\textrm{Fib}(n+1)$ and - $\textrm{Fib}(n)$. Thus, we can compute - Fibonacci numbers iteratively using the - - procedure - function - + 不难证明,在应用该变换 + $n$次之后, + $a$和 + $b$ + 分别等于 + $\textrm{Fib}(n+1)$和 + $\textrm{Fib}(n)$。因此,我们可以使用以下 + + 过程 + 函数 + + 迭代计算斐波那契数: - fiblinear iterative version + fib线性迭代版本 fib_example 8 - + (define (fib n) (fib-iter 1 0 n)) @@ -254,7 +242,7 @@ fib(6); (if (= count 0) b (fib-iter (+ a b) a (- count 1)))) - + function fib(n) { return fib_iter(1, 0, n); @@ -266,141 +254,108 @@ function fib_iter(a, b, count) { } - This second method for computing $\textrm{Fib}(n)$ - is a linear iteration. The difference in number of steps required by the two - methodsone linear in $n$, one growing as - fast as $\textrm{Fib}(n)$ itselfis - enormous, even for small inputs. - + 这种计算 + $\textrm{Fib}(n)$ + 的第二种方法是线性迭代方法。两种方法所需步骤数量的差异——一种随 + $n$线性增长,另一种则以 + $\textrm{Fib}(n)$本身的速度增长——即使对较小的输入,差异也是巨大的。 + - - One should not conclude from this that tree-recursive processes are useless. - When we consider processes that operate on hierarchically structured data - rather than numbers, we will find that tree recursion is a natural and - powerful tool.An example of this was hinted at in - section: The interpreter - itself evaluates expressions using a tree-recursive process. But - even in numerical operations, tree-recursive processes can be useful in - helping us to understand and design programs. For instance, although the - first - fib + + 不应该因此断言树形递归过程是无用的。当我们考虑作用于层次结构数据而非数字的过程时,会发现树形递归是一种自然且强大的工具。这方面的一个例子已在第节中有所暗示:解释器本身就是使用树形递归过程来计算表达式的。但即使在数值运算中,树形递归过程也有助于我们理解和设计程序。例如,尽管第一个 + fib - procedure - function + 过程 + 函数 - is much less efficient than the second one, it is more straightforward, - being little more than a translation into + 比第二个效率低得多,但它更直接,几乎只是对斐波那契数列定义的 - + Lisp - + JavaScript - of the definition of the Fibonacci sequence. To formulate the iterative - algorithm required noticing that the computation could be recast as an - iteration with three state variables. - + 的翻译。要构思迭代算法,就需要注意到计算可以重构为具有三个状态变量的迭代。 + - - Example: Counting change - - counting change + + 示例:计算零钱 + + 计算零钱 - - It takes only a bit of cleverness to come up with the iterative Fibonacci - algorithm. In contrast, consider the following problem: - How many different ways can we make change of + + 仅需要一些巧思就能想出迭代斐波那契算法。相反,请考虑以下问题: + 有多少种不同的方法可以用半美元、四分之一美元、一角、五分和一分来找零 - - \$1.00, - + + \$1.00, + - 1.00 (100 cents), + 1.00(100 美分), - given half-dollars, quarters, dimes, nickels, and pennies - (50 cents, 25 cents, 10 cents, 5 cents, and 1 cent, respectively)? - More generally, can - we write a + 它们分别是 50 美分、25 美分、10 美分、5 美分和 1 美分? + 更一般地,能否写一个 - procedure - function + 过程 + 函数 - to compute the number of ways to change any given amount of money? - + 来计算任何给定金额的找零方法数? + - - This problem has a simple solution as a recursive + + 该问题有一个简单的递归 - procedure. - function. + 过程 + 函数 - Suppose we think of the types of coins available as arranged in some order. - Then the following relation holds: + 解决方案。假设我们将可用的硬币种类按某种顺序排列。 + 则以下关系成立:
    - The number of ways to change amount $a$ using - $n$ kinds of coins equals + 使用 + $n$ 种硬币换取金额 $a$ 的方法数等于
    • - the number of ways to change amount $a$ - using all but the first kind of coin, plus + 使用除了第一种硬币以外的所有硬币换取金额 $a$ 的方法数,加上
    • - the number of ways to change amount $a-d$ - using all $n$ kinds of coins, where - $d$ is the denomination of the first kind - of coin. + 使用所有 $n$ 种硬币换取金额 $a-d$ 的方法数, + 其中 $d$ 是第一种硬币的面值。
    -
    +
    - - To see why this is true, observe that the ways to make change can be divided - into two groups: those that do not use any of the first kind of coin, and - those that do. Therefore, the total number of ways to make change for some - amount is equal to the number of ways to make change for the amount without - using any of the first kind of coin, plus the number of ways to make change - assuming that we do use the first kind of coin. But the latter number is - equal to the number of ways to make change for the amount that remains after - using a coin of the first kind. - + + 要理解这一点,观察找零的方法可以分为两类:一类是不使用第一种硬币,另一类是使用第一种硬币。因此,找零某个金额的方法总数等于不使用第一种硬币找零该金额的方法数,加上使用第一种硬币找零该金额的方法数。而后一种方法数等于使用一枚第一种硬币后剩余金额的找零方法数。 + - - Thus, we can recursively reduce the problem of changing a given amount to - problems of changing smaller amounts or using fewer kinds of coins. Consider - this reduction rule carefully, and convince yourself that we can use it to - describe an algorithm if we specify the following degenerate - cases:For example, work through in detail how the reduction rule - applies to the problem of making change for 10 cents using pennies and - nickels. + + 因此,我们可以递归地将找零给定金额的问题化简为找零较小金额或使用较少种类硬币的问题。仔细考虑这个化简规则,并确信如果我们指定以下退化情况,我们可以用它来描述一个算法:例如,详细推导如何运用该化简规则解决用便士和五分镍币找零10美分的问题。
    • - If $a$ is exactly 0, we should count that - as 1 way to make change. + 如果 $a$ 恰好为0,我们应将其计为一种找零方法。
    • - If $a$ is less than 0, we should count - that as 0 ways to make change. + 如果 $a$ 小于0,我们应将其计为零种找零方法。
    • -
    • If $n$ is 0, we should count that - as 0 ways to make change. +
    • 如果 $n$ 是0,我们应将其计为零种找零方法。
    - We can easily translate this description into a recursive + 我们可以很容易地将这一描述翻译成递归的 - procedure: - function: + 过程: + 函数: count_change count_change_definition count_change_example - + (define (count-change amount) (cc amount 5)) @@ -421,7 +376,7 @@ function fib_iter(a, b, count) { ((= kinds-of-coins 3) 10) ((= kinds-of-coins 4) 25) ((= kinds-of-coins 5) 50))) - + function count_change(amount) { return cc(amount, 5); @@ -448,27 +403,24 @@ function first_denomination(kinds_of_coins) { } - (The + ( - - first-denomination procedure - + + first-denomination 过程 + - first_denomination function + first_denomination 函数 - takes as input the number of kinds of coins available and returns the - denomination of the first kind. Here we are thinking of the coins as - arranged in order from largest to smallest, but any order would do as well.) - We can now answer our original question about changing a dollar: + 接受可用硬币种类数作为输入,并返回第一种硬币的面值。这里我们将硬币按从大到小排序,但任何顺序都可。)我们现在可以回答最初关于兑换一美元的问题: count_change_example count_change_definition 292 - + (count-change 100) - + 292 @@ -479,108 +431,95 @@ count_change(100); 292 -
    +
    - + - Count-change + count-change - The function count_change + 函数 count_change - generates a tree-recursive process with redundancies similar to those in - our first implementation of fib. + 生成了一个具有冗余的树形递归过程,其冗余类似于我们最初实现的 fib 中的情况。 - - (It will take - quite a while for that + + (计算 - 292 + 292 293 - to be computed.) - + 需要相当长的时间。) + - On the other hand, it is not - obvious how to design a better algorithm for computing the result, and we - leave this problem as a challenge. The observation that a - efficiencytreeof tree-recursive process - tree-recursive process may be highly inefficient but often easy to specify - and understand has led people to propose that one could get the best of both - worlds by designing a smart compiler that could transform - tree-recursive + 另一方面,如何设计一个更好的算法来计算结果并不明显,我们将此问题作为挑战留给读者。观察到一个 + 效率树形递归过程 + 树形递归过程可能非常低效,但通常易于指定和理解,这促使人们提出可以通过设计一个 + 智能编译器 + 来同时获得两者的优点,该编译器可以将树形递归 - procedures - functions + 过程 + 函数 - into more efficient + 转换为更高效的 - procedures - functions + 过程 + 函数 - that compute the same result.One approach to coping with redundant - computations is to arrange matters so that we automatically construct a - table of values as they are computed. Each time we are asked to apply the + 并计算相同的结果。应对冗余计算的一种方法是安排在计算时自动构造一个数值表。每当我们被要求对某个参数应用 - procedure - function + 过程 + 函数 - to some argument, we first look to see if the value is already stored in the - table, in which case we avoid performing the redundant computation. This - strategy, known as - tabulation - tabulation or - memoization - memoization, can be implemented in a - straightforward way. Tabulation can sometimes be used to transform processes - that require an exponential number of steps + 时,我们首先检查该值是否已存储在表中,在这种情况下,我们避免执行冗余计算。这种策略被称为 + 表格法 + 表格法或 + 记忆化 + 记忆化,可以用简单的方式实现。表格法有时可以将需要指数级步骤的过程 - - (such as count-change) - + + (如 count-change) + - (such as count_change) + (如 count_change - into processes whose space and time requirements grow linearly with the - input. See exercise. - tree-recursive process - processtree-recursive - recursive processtree - counting change - + 转换为空间和时间需求随输入线性增长的过程。见练习。 + 树形递归过程 + 过程树形递归 + 递归过程树形 + 找零计数 + - - A function $f$ is defined by the + + 函数 $f$ 由以下 - - rule that - + + 规则定义 + - rules + 规则 - $f(n)=n$ if $n < 3$ - and $f(n)={f(n-1)}+2f(n-2)+3f(n-3)$ if - $n\ge 3$. Write a + 如果 $n < 3$,则 $f(n)=n$,如果 + $n\ge 3$,则 $f(n)={f(n-1)}+2f(n-2)+3f(n-3)$。 + 编写一个 - procedure - JavaScript function + 过程 + JavaScript函数 - that computes $f$ by means of a recursive process. - Write a + 通过递归过程计算 $f$。 + 编写一个 - procedure - function + 过程 + 函数 - that computes $f$ by means of an iterative - process. + 通过迭代过程计算 $f$ example_1.12_1 25 -// iterative function +// 迭代函数 function f_iterative(n) { return n < 3 ? n @@ -598,7 +537,7 @@ function f_iterative_impl(a, b, c, count) { example_1.12_2 25 -//recursive function +// 递归函数 function f_recursive(n) { return n < 3 ? n @@ -622,12 +561,12 @@ f_recursive(5); - + - - The following pattern of numbers is called - Pascals triangle - Pascals triangle. + + 下列数字模式称为 + 帕斯卡三角形 + 帕斯卡三角形 \[ { @@ -641,40 +580,28 @@ f_recursive(5); \end{array}} \] - The numbers at the edge of the triangle are all 1, and each number inside - the triangle is the sum of the two numbers above it.The elements - of Pascals triangle are called the binomial coefficients, - because the $n$th row consists of - binomial coefficients - the coefficients of the terms in the expansion of - $(x+y)^n$. This pattern for computing the - coefficients - appeared in - PascalPascal, Blaise - Blaise Pascals 1653 seminal work on probability theory, - Trait du triangle arithmtique. - According to - Edwards, Anthony William Fairbank - Edwards (2019), the same pattern appears - in the works of - the eleventh-century Persian mathematician - Al-Karaji - Al-Karaji, - in the works of the twelfth-century Hindu mathematician - Bhaskara - Bhaskara, and - in the works of the - thirteenth-century Chinese mathematician - Yang Hui - Yang Hui. + 三角形边缘上的数字全是 1,三角形内部的每个数字是其上方两个数字之和。帕斯卡三角形的元素称为 二项式系数,因为第 $n$ 行由 + 二项式系数 + 组成,即 $(x+y)^n$ 展开项的系数。 + 这种计算系数的模式出现在 + 帕斯卡布莱兹·帕斯卡 1653 年关于概率论的奠基著作 + 算术三角形讲义 中。 + 据 + 安东尼·威廉·费尔班克·爱德华兹 + 爱德华兹(2019 年)介绍,同样的模式还出现在十一世纪波斯数学家 + 阿尔-卡拉吉 + 阿尔-卡拉吉、十二世纪印度数学家 + 巴斯卡拉 + 巴斯卡拉和十三世纪中国数学家 + 杨辉 + 杨辉的著作中。 - Write a + 编写一个 - procedure - function + 程序 + 函数 - that computes elements of Pascals triangle by means of a recursive - process. + 通过递归过程计算帕斯卡三角形的元素。 pascal_triangle @@ -690,8 +617,8 @@ function pascal_triangle(row, index) { pascal_triangle(row - 1, index); } - - + + @@ -701,54 +628,48 @@ function pascal_triangle(row, index) { pascal_triangle(5, 4); - - + + - + - - Prove that $\textrm{Fib}(n)$ is the closest - integer to $\phi^n/\sqrt{5}$, where - $\phi= (1+\sqrt{5})/2$. + + 证明 $\textrm{Fib}(n)$$\phi^n/\sqrt{5}$ 的最接近的整数,其中 + $\phi= (1+\sqrt{5})/2$ - - Hint: Let - $\psi= (1-\sqrt{5})/2$. Use induction and the - definition of the Fibonacci numbers (see - section) to prove that - $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$. - + + 提示:设 + $\psi= (1-\sqrt{5})/2$。利用归纳法和斐波那契数的定义(参见第节)证明 + $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$。 + - Hint: Use induction and the - definition of the Fibonacci numbers to prove that - $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$, - where - $\psi= (1-\sqrt{5})/2$. + 提示:利用归纳法和斐波那契数的定义证明 + $\textrm{Fib}(n)=(\phi^n-\psi^n)/\sqrt{5}$, + 其中 + $\psi= (1-\sqrt{5})/2$ - First, we show that + 首先,使用强归纳法证明 $\textrm{Fib}(n) = - \dfrac{\phi^n-\psi^n}{\sqrt{5}}$, - where - $\psi = \dfrac{1-\sqrt{5}}{2}$ - using strong induction. + \dfrac{\phi^n-\psi^n}{\sqrt{5}}$, + 其中 + $\psi = \dfrac{1-\sqrt{5}}{2}$

    $\textrm{Fib}(0) = 0$ - and + 且 $\dfrac{\phi^0-\psi^0}{\sqrt{5}} = 0$

    $\textrm{Fib}(1) = 1$ - and + 且 $\dfrac{\phi^1-\psi^1}{\sqrt{5}} = \dfrac{\dfrac{1}{2}\left(1+\sqrt{5} - 1 + \sqrt{5}\right)}{\sqrt{5}} = 1$

    - So the statement is true for $n=0,1$. - Given $n \geq 1$, assume the proposition - to be true for $0, 1, \dots , n$. + 因此该命题对 $n=0,1$ 成立。 + 给定 $n \geq 1$,假设命题对 $0, 1, \dots , n$ 成立。

    $\textrm{Fib}(n+1) = \textrm{Fib}(n) + \textrm{Fib}(n-1) = @@ -759,19 +680,19 @@ pascal_triangle(5, 4);

    $=\dfrac{\phi^{n-1}(\phi^2) - \psi^{n-1}(\psi^2)}{\sqrt{5}} - = \dfrac{\phi^{n+1} - \psi^{n+1}}{\sqrt{5}}$, - so the statement is true. + = \dfrac{\phi^{n+1} - \psi^{n+1}}{\sqrt{5}}$, + 因此命题成立。

    - Notice that since $|\psi| < 1$ and - $\sqrt{5} > 2$, one has + 注意由于 $|\psi| < 1$ 且 + $\sqrt{5} > 2$,有 $\left|\dfrac{\psi^n}{\sqrt{5}}\right| < \dfrac{1}{2}$

    - Then the integer closest to + 因此最接近 $\textrm{Fib}(n) + \dfrac{\psi^n}{\sqrt{5}} = - \dfrac{\phi^n}{\sqrt{5}}$ is - $\textrm{Fib}(n)$. + \dfrac{\phi^n}{\sqrt{5}}$的整数是 + $\textrm{Fib}(n)$
    -
    +
    diff --git a/xml/zh/chapter1/section2/subsection3.xml b/xml/zh/chapter1/section2/subsection3.xml index 72c92c0d0..5e02f9ba2 100644 --- a/xml/zh/chapter1/section2/subsection3.xml +++ b/xml/zh/chapter1/section2/subsection3.xml @@ -1,53 +1,32 @@ - Orders of Growth + 增长阶 - order of growth + 增长阶 - - The previous examples illustrate that processes can differ - considerably in the rates at which they consume computational - resources. One convenient way to describe this difference is to use - the notion of - processorder of growth of - order of growth to obtain a gross measure of the - processresources required by - resources required by a process as the inputs become larger. - + + 前面的例子说明了过程在消耗计算资源的速度上可能有显著差异。描述这种差异的一种方便方法是使用 + 过程增长阶 + 增长阶的概念,以获得随着输入规模增大过程所需 + 过程所需资源 + 资源的粗略测度。 + - - Let $n$ be a parameter that measures the size of - the problem, and let $R(n)$ be the amount - of resources the process requires for a problem of size - $n$. In our previous examples we took - $n$ to be the number for which a given - function is to be computed, but there are other possibilities. - For instance, if our goal is to compute an approximation to the - square root of a number, we might take - $n$ to be the number of digits accuracy required. - For matrix multiplication we might take $n$ to - be the number of rows in the matrices. In general there are a number of - properties of the problem with respect to which it will be desirable to - analyze a given process. Similarly, $R(n)$ - might measure the number of internal storage registers used, the - number of elementary machine operations performed, and so on. In - computers that do only a fixed number of operations at a time, the - time required will be proportional to the number of elementary machine - operations performed. - + + 设 $n$ 是衡量问题规模的参数,设 $R(n)$ 是该过程处理规模为 $n$ 的问题所需的资源量。在之前的例子中,我们将 $n$ 取为要计算的给定函数的输入值,但也存在其他可能性。例如,如果我们的目标是计算一个数的平方根近似值,我们可以将 $n$ 定义为所需的准确位数。对于矩阵乘法,我们可能将 $n$ 定为矩阵的行数。通常情况下,针对某个过程,问题的某些属性是分析其性能所必需考虑的。同样,$R(n)$ 可能表示所用的内部存储寄存器数、执行的基本机器操作次数,等等。在那些一次只执行固定数量操作的计算机上,所需时间将与执行的基本机器操作次数成正比。 + - - We say that $R(n)$ has order of growth - 0e$\theta(f(n))$ (theta of $f(n)$) - theta$\theta(f(n))$ (theta of $f(n)$) - order notation - $\Theta(f(n))$, written - $R(n)=\Theta(f(n))$ (pronounced - theta of $f(n)$), if there are - positive constants $k_1$ and - $k_2$ independent of - $n$ such that + + 我们称 $R(n)$ 的增长阶为 + 0e$\theta(f(n))$$f(n)$ 的theta) + theta$\theta(f(n))$$f(n)$ 的theta) + 阶符号 + $\Theta(f(n))$,写作 + $R(n)=\Theta(f(n))$(读作 + $f(n)$ 的theta),如果存在两个与 + $n$ 无关的正常数 $k_1$ 和 + $k_2$,使得 \[ \begin{array}{lllll} @@ -55,212 +34,159 @@ \end{array} \] - for any sufficiently large value of $n$. - (In other words, for large $n$, - the value $R(n)$ is sandwiched between - $k_1f(n)$ and - $k_2f(n)$.) - + 对于任何足够大的 $n$ 都成立。 + (换言之,当 $n$ 很大时,值 $R(n)$ 夹在 + $k_1f(n)$ 和 + $k_2f(n)$ 之间。) + - - order of growthlinear recursive process - linear recursive processorder of growth - recursive processlinear - For instance, with the linear recursive process for computing factorial - described in section the - number of steps grows proportionally to the input - $n$. Thus, the steps required for this process - grows as $\Theta(n)$. We also saw that the space - required grows as $\Theta(n)$. For the - order of growthlinear iterative process - linear iterative processorder of growth - iterative processlinear - iterative factorial, the number of steps is still - $\Theta(n)$ but the space is - $\Theta(1)$that is, - constant.These statements mask a great deal of oversimplification. - For instance, if we count process steps as machine operations - we are making the assumption that the number of machine operations needed to - perform, say, a multiplication is independent of the size of the numbers to - be multiplied, which is false if the numbers are sufficiently large. - Similar remarks hold for the estimates of space. Like the design and - description of a process, the analysis of a process can be carried out at - various levels of abstraction. - The - order of growthtree-recursive process - tree-recursive processorder of growth - recursive processtree - tree-recursive Fibonacci computation requires - $\Theta(\phi^{n})$ steps and space - $\Theta(n)$, where - $\phi$ is the golden ratio described in - section. - + + 增长阶线性递归过程 + 线性递归过程增长阶 + 递归过程线性 + 例如,对于第节中描述的计算阶乘的线性递归过程,步骤数与输入 + $n$ 成正比增长。因此,该过程所需的步骤数增长为 + $\Theta(n)$。我们还看到所需的空间增长为 + $\Theta(n)$。对于 + 增长阶线性迭代过程 + 线性迭代过程增长阶 + 迭代过程线性 + 迭代阶乘,步骤数依然是 + $\Theta(n)$,但空间是 + $\Theta(1)$即常量空间。这些陈述掩盖了大量的简化。例如,如果我们将过程步骤计为机器操作,我们假设执行乘法操作所需的机器操作数与被乘数的大小无关,但当数字足够大时,这一假设是不成立的。对于空间估计,也有类似的说明。就像过程的设计和描述一样,过程的分析可以在不同的抽象层次上进行。 + 增长阶树递归过程 + 树递归过程增长阶 + 递归过程 + 树递归的斐波那契计算需要 + $\Theta(\phi^{n})$ 步骤和空间 + $\Theta(n)$,其中 + $\phi$ 是第节中描述的黄金比例。 + - - Orders of growth provide only a crude description of the behavior of a - process. For example, a process requiring $n^2$ - steps and a process requiring $1000n^2$ steps and - a process requiring $3n^2+10n+17$ steps all have - $\Theta(n^2)$ order of growth. On the other hand, - order of growth provides a useful indication of how we may expect the - behavior of the process to change as we change the size of the problem. - For a - linear growth - $\Theta(n)$ (linear) process, doubling the size - will roughly double the amount of resources used. For an - exponential growth - exponential process, each increment in problem size will multiply the - resource utilization by a constant factor. In the remainder of - section - we will examine two algorithms whose order of growth is - logarithmic growth - logarithmic, so that doubling the problem size increases the resource - requirement by a constant amount. - order of growth - + + 增长阶仅提供了对过程行为的粗略描述。例如,需要 + $n^2$ + 步骤的过程、需要 + $1000n^2$ + 步骤的过程以及需要 + $3n^2+10n+17$ + 步骤的过程,均具有 + $\Theta(n^2)$ 的增长阶。另一方面,增长阶有助于我们预期随着问题规模变化,过程行为将如何改变。对于一个 + 线性增长 + $\Theta(n)$(线性)过程,规模加倍将大致使资源使用量加倍。对于一个 + 指数增长 + 的过程,问题规模每增加一次,资源利用将乘以一个常数因子。在第节的剩余部分,我们将研究两个算法,它们的增长阶是 + 对数增长 + ,因此将问题规模加倍会使资源需求增加一个常数量。 + 增长阶 + - - Draw the tree illustrating the process generated by the + + 画出由 - count-change procedure - - count_change function + count-change 过程 + + count_change 函数 - of section in making change for - 11 cents. What are the orders of growth of the space and number of steps - used by this process as the amount to be changed increases? - - The tree-recursive process generated in computing - cc(11, 5) is illustrated by the - image below, due to Toby Thain, assuming that the coin values in - first_denomination are - $\mathbb{C}_{1} = 1$, + 生成的过程树,见第节 + 在换取十一美分时。随着需兑换金额的增加,该过程使用的空间和步骤数量的增长阶数是什么? + 计算 cc(11, 5) 时生成的三叉递归过程由下面的图片示意,归功于 Toby Thain,假设 first_denomination 中的硬币面值是 + $\mathbb{C}_{1} = 1$, $\mathbb{C}_{2} = 5$, $\mathbb{C}_{3} = 10$, - $\mathbb{C}_{4} = 25$ and - $\mathbb{C}_{5} = 50$. + $\mathbb{C}_{4} = 25$ 和 + $\mathbb{C}_{5} = 50$.
    -
    +
    - Let us consider the process for evaluating - cc(n, k), which means the amount to - be changed is n and the number of - kinds of coins is k. Let us assume - the coin values are constants, not dependent on - n or - k. -

    - The space required for a tree-recursive process isas discussed in - sectionproportional to the - maximum depth of the tree. At each step from a parent to a child in the - tree, either n strictly decreases - (by a constant coin value) or k - decreases (by 1), and leaf nodes have an amount of at most 0 or a number - of kinds of coins of 0. Thus, every path has a length of - $\Theta(n + k)$, which is also the order of - growth of the space required for - cc(n, k). + 让我们考虑计算 + cc(n, k) 的过程,它表示需要找零的金额为 n, + 可用的硬币种类数为 k。假设硬币面值是常数,与 + n 和 + k 无关。

    - - Let us derive a function $T(n, k)$ such that - the time required for calculating - cc(n, k) has an order of growth of - $\Theta(T(n, k))$. The following argument is - due to Yati Sagade, including the illustrations - (Sagade 2015). - Let us start with the call tree for changing some amount - $n$ with just 1 kind of coin, i.e., - the call tree for cc(n, 1). - -
    + 该树-递归过程所需的空间是—正如第节所讨论的 + 与树的最大深度成正比。在从父节点到子节点的每一步中,n 要么严格减少(减少一个常数硬币面值),要么 k 减少(减少1),叶子节点的金额最多为0或硬币种类数为0。因此,每条路径的长度为 $\Theta(n + k)$ ,这也是 cc(n, k) 所需空间增长的阶 + 让我们推导一个函数 $T(n, k)$ 使得 + 计算 + cc(n, k) 所需的时间 + 的增长阶为 + $\Theta(T(n, k))$ 以下论证来自 Yati Sagade,包括插图 + (Sagade 2015)。 + 让我们从更改某个数量的调用树开始 + $n$ 只有一种硬币,即, + cc(n, 1) 的调用树。 +
    - We are only allowed here to use one kind of coin, with value - $\mathbb{C}_{1} = 1$. The red nodes are - terminal nodes that yield 0, the green node is a terminal node that - yields 1 (corresponding to the first condition in the declaration of - cc). Each nonterminal node spawns - two calls to cc, one (on the left) - with the same amount, but fewer kinds of coins, and the other (on the - right) with the amount reduced by 1 and equal kinds of coins. -

    - Excluding the root, each level has exactly 2 nodes, and there are - $n$ such levels. This means, the number of - cc calls generated by a single - cc(n, 1) call (including the original - call) is: - + 我们这里只允许使用一种硬币,其面值为 + $\mathbb{C}_{1} = 1$红色节点是产生0的终端节点,绿色节点是产生1的终端节点(对应于cc声明中的第一个条件)。每个非终端节点生成对cc的两个调用,一个(在左侧)金额相同,但硬币种类更少,另一个(在右侧)金额减少1且硬币种类相同。 +

    +除根节点外,每一层恰好有2个节点,且有$n$此类层级。这意味着,由单个cc调用 + cc(n, 1)(包括原始调用)生成的 + cc调用数量为: + \[ T(n,1) = 2n + 1 = \Theta(n) \] - - Next, we will look at the call tree of - cc(n, 2) - to calculate $T(n,2)$: +
    接下来,我们将查看 + cc(n, 2) 的调用树 + 以计算$T(n,2)$: -
    +
    -
    +
    - Here, we are allowed to use two denominations of coins: - $\mathbb{C}_{2} = 5$ - and $\mathbb{C}_{1} = 1$. -

    - Each black node spawns a cc(m, 1) - subtree (blue), which we’ve already analyzed, and a - cc(m - 5, 2) subtree. The node - colored in red and green is a terminal node, but yields 0 if the amount - is less than zero and 1 if the amount is exactly zero. Sagade denotes - this final amount as $\epsilon$, which can - be $\le0$. -

    - Excluding the root and and the last level in this tree which contains the - red-green terminal node, there will be exactly - $\big\lfloor {\frac {n} {5}} \big\rfloor$ - levels. Now each of these levels contains a call to - cc(m, 1) (the blue nodes), each of - which, in turn, is $\Theta(n)$ in time. So each - of these levels contains $T(n,1) + 1$ calls to - cc. Therefore, the total number of - nodes (including the terminal node and the root) in the call tree for - cc(n, 2) is: - + 这里,我们允许使用两种面额的硬币: + $\mathbb{C}_{2} = 5$ + 和 + $\mathbb{C}_{1} = 1$. +

    + 每个黑色节点生成一个 cc(m, 1) + 子树(蓝色),我们已经分析过了,还有一个 + cc(m - 5, 2) 子树。节点 + 以红色和绿色表示的是终端节点,但如果金额小于零则结果为0, + 如果金额正好为零则结果为1。Sagade 将此最终金额表示为 $\epsilon$ ,可以是 $\le0$. +

    + 除去根节点和该树中包含红绿终端节点的最后一层,将恰好有 + $\big\lfloor {\frac {n} {5}} \big\rfloor$ + 层级。现在每一层级都包含一个对 + cc(m, 1)(蓝色节点)的调用,每个调用反过来都是 + $\Theta(n)$ 及时。因此,每个 + 这些层级都包含 $T(n,1) + 1$调用到 + cc。因此,对于 + cc(n, 2) 的调用树中 + 节点的总数(包括终端节点和根节点)为: + \[ T(n,2) = \big\lfloor {\frac {n} {5} } \big\rfloor ( T(n,1) + 1) + 2 = \big\lfloor {\frac {n} {5} } \big\rfloor ( 2n + 2 ) + 2 = \Theta(n^2) \] - - Moving ahead, let’s take a look at the call tree of - cc(n, 3), i.e., we are now allowed - to use three denominations of coins, the new addition being - $\mathbb{C}_{3} = 10$: +
    + 接下来,让我们看一下 + cc(n, 3) 的调用树,即我们现在可以使用 + 三种面值的硬币,新增加的是 + $\mathbb{C}_{3} = 10$ : -
    +
    -
    +
    - Here also, we see, similar to the previous case, that the total number of - calls to cc will be - + 在这里,我们也看到,类似于前面的情况,调用cc的总次数将是 + \[ T(n,3) = \big\lfloor {\frac {n} {10} } \big\rfloor ( T(n,2) + 1) + 2 = \big\lfloor {\frac {n} {10} } \big\rfloor \times \Theta(n^2) + 2 = \Theta(n^3) \] - - We can see a pattern here. For some $k$, - $k \gt 1$, we have, - + + 我们可以在这里看到一个模式。对于某些 $k$, + $k \gt 1$,我们有, \[ T(n,k) = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor ( T(n, k-1) + 1) + 2 \] - - Here, $\mathbb{C}_{k}$ is the - $k^{th}$ coin denomination. We can expand this - further: - + 这里,$\mathbb{C}_{k}$$k^{th}$币种。我们可以进一步扩展: \[ T(n,k) = \big\lfloor {\frac {n} { \mathbb{C}_{k} } } \big\rfloor ( T(n, k-1) + 1 ) + 2 @@ -270,21 +196,15 @@ ) + 2 = \Theta(n^k) \] - - Note that the actual values of the coin denominations have no effect on - the order of growth of this process, if we assume they are constants that - do not depend on n and - k. - + 请注意,如果我们假设币种面额是常数且不依赖于nk,那么这些币种面额的实际值不会影响该过程的增长阶。 - - sineapproximation for small angle - The sine of an angle (specified in radians) can be computed by making use - of the approximation $\sin x\approx x$ - if $x$ is sufficiently small, and the - trigonometric identity + + 小角度正弦函数近似计算 + 角度的正弦值(以弧度为单位)可以利用近似式 + $\sin x\approx x$ + 计算,条件是当 $x$ 足够小时,同时利用三角恒等式 \[ \begin{array}{lll} @@ -292,20 +212,18 @@ \end{array} \] - to reduce the size of the argument of $\sin$. - (For purposes of this exercise an angle is considered sufficiently - small if its magnitude is not greater than 0.1 radians.) These - ideas are incorporated in the following + 以减小 $\sin$ 的自变量值。 + (本练习中,若角度的绝对值不大于0.1弧度,则认为该角度为足够小。)这些思想体现在以下 - procedures: - functions: + 过程: + 函数: - cube - sine_definition - sine_example - abs_definition - + 立方 + 正弦函数定义 + 正弦函数示例 + 绝对值定义 + (define (cube x) (* x x x)) (define (p x) @@ -315,7 +233,7 @@ (if (not (> (abs angle) 0.1)) angle (p (sine (/ angle 3.0))))) - + function cube(x) { return x * x * x; @@ -333,70 +251,59 @@ function sine(angle) { 0.9999996062176211 - sine_example - sine_definition - + 正弦函数示例 + 正弦函数定义 + (define pi 3.14159) (sine (/ pi 2)) - + sine(math_PI / 2);
      -
    1. How many times is the +
    2. 在计算 - procedure - function + 过程 + 函数 - p - applied when + p + 时,当计算 - (sine 12.15) + (sine 12.15) sine(12.15) - is evaluated? + 需要调用多少次?
    3. - What is the order of growth in space and number of steps (as a function - of$a$) used by the process generated - by the sine + 使用该 + sine - procedure - function + 过程 + 函数 - when + 计算 - (sine a) + (sine a) sine(a) - is evaluated? + 时,生成的过程在空间和步骤数上的增长阶(作为$a$的函数)是多少?
      -
    1. The function p - will call itself recursively as long as the angle value is greater - than 0.1. There will be altogether 5 calls of - p, with arguments 12.15, 4.05, - 1.35, 0.45, 0.15 and 0.05. +
    2. 当角度值大于0.1时,函数p会递归调用自身。p共调用5次,调用参数分别为12.15、4.05、1.35、0.45、0.15和0.05。
    3. - The function sine gives - rise to a recursive process. In each recursive call, the - angle is divided by 3 - until its absolute value is smaller than 0.1. - Thus the number of steps and the space required has an order - of growth of $O(\log a)$. Note that the base of the logarithm - is immaterial for the order of growth because the logarithms - of different bases differ only by a constant factor. + 函数sine生成一个递归过程。在每次递归调用中,angle都会除以3,直到其绝对值小于0.1。 + 因此,步骤数和所需空间的增长阶为 $O(\log a)$。注意对增长阶而言,底数的不同无关紧要,因为不同底数的对数仅相差常数因子。
    -
    +
    diff --git a/xml/zh/chapter1/section2/subsection4.xml b/xml/zh/chapter1/section2/subsection4.xml index 58b026303..a2a251341 100644 --- a/xml/zh/chapter1/section2/subsection4.xml +++ b/xml/zh/chapter1/section2/subsection4.xml @@ -1,35 +1,31 @@ - 幂运算 + 求幂运算 - 幂运算 + 求幂运算 - - 考虑计算给定数字的指数的问题。 - 我们希望有一个 + + 考虑计算给定数的指数的问题。我们希望有一个 - 过程 + 过程 函数 - ,作为参数接受一个基数 -$b$ 和一个正整数指数 $n$ 和 - 计算 $b^n$ . 实现这一点的一种方法是通过 - 递归定义 - + 它接受一个底数 $b$ 和一个正整数指数 $n$ 作为参数,计算出$b^n$。一种方法是通过递归定义 + \[ \begin{array}{lll} b^{n} &=& b\cdot b^{n-1}\\ b^{0} &=& 1 \end{array} \] - - 这很容易转化为 + + 这可以直接转化为 - 过程 + 过程 函数 - - exptlinear recursive version + + expt线性递归版本 expt_definition expt_example @@ -56,12 +52,11 @@ function expt(b, n) { expt(3, 4); - - 这是一个线性递归过程,要求 -$\Theta(n)$ 步骤和$\Theta(n)$ 空间。正如在阶乘中一样,我们 - 可以很容易地制定一个等效的线性迭代: - - exptlinear iterative version + + 这是一个线性递归过程,执行步骤和空间需求均为 + $\Theta(n)$。就像阶乘一样,我们可以轻松地构造一个等效的线性迭代: + + expt线性迭代版本 expt_linear_definition expt_example2 81 @@ -95,24 +90,26 @@ function expt_iter(b, counter, product) { expt(3, 4); - + - 该版本要求 $\Theta(n)$ 步骤和$\Theta(1)$ 空间。 + 此版本需要 $\Theta(n)$ 步骤和 + $\Theta(1)$ 空间。 + 我们可以通过使用 - 逐次平方法 - 更少的步骤来计算指数。 - 例如,不是计算 $b^8$ 为 - + 连乘平方 + 连乘平方来减少计算指数的步骤。 + 例如,不是通过 + \[ \begin{array}{l} b\cdot(b\cdot(b\cdot(b\cdot(b\cdot(b\cdot(b\cdot b)))))) \end{array} \] - - 我们可以使用三次乘法来计算它: - + + 计算 $b^8$,我们可以用三次乘法计算它: + \[ \begin{array}{lll} b^{2} &= & b\cdot b\\ @@ -120,25 +117,26 @@ expt(3, 4); b^{8} &= & b^{4}\cdot b^{4} \end{array} \] - + - - 这种方法对于以 2 为底的指数效果很好。如果我们使用规则,也可以在一般情况下利用逐次平方法来计算指数 - + + 该方法对指数为2的幂的情况效果良好。如果我们使用如下规则, + \[ \begin{array}{llll} - b^{n} &=& (b^{n/2})^{2} &\qquad\,\mbox{if}\ n\ \mbox{is even}\\ - b^{n} &=& b\cdot b^{n-1} &\qquad\mbox{if}\ n\ \mbox{is odd} + b^{n} &=& (b^{n/2})^{2} &\qquad\,\mbox{如果}\ n\ \mbox{是偶数}\\ + b^{n} &=& b\cdot b^{n-1} &\qquad\mbox{如果}\ n\ \mbox{是奇数} \end{array} \] - - 我们可以将这种方法表示为 + + 我们也可以利用连乘平方来计算一般指数的幂。 + 我们可以将该方法表示为一个 - 过程: + 过程: 函数: - + fast_expt expt_log_definition square_definition @@ -174,26 +172,26 @@ function fast_expt(b, n) { fast_expt(3, 4); - - 用于测试一个整数是否为偶数的谓词是通过 + + 其中用于测试整数是否为偶数的谓词是基于 - - 原始过程 - 余数整数整除后的余数 - {\tt "%} (余数运算符)/// - {\tt "%} (余数)"% - 余数, - + + 基元过程 + remainder整数除法后的余数 + {\tt "%}(余数运算符)/// + {\tt "%}(余数)"% + remainder, + - 余数整数整除后的余数 - {\tt "%} (余数运算符)/// - {\tt "%} (余数)/// - 运算符%, - 它计算整除后的余数, + remainder整数除法后的余数 + {\tt "%}(余数运算符)/// + {\tt "%}(余数)/// + 运算符%, + 计算整数除法后的余数, - 来定义的 - + 定义为 + is_even even_definition even_example @@ -217,77 +215,63 @@ function is_even(n) { is_even(7); - - 该过程通过 + + 由 - 快速幂函数 + fast-expt fast_expt - 增长阶对数 - 对数增长 - 以对数方式增长 -$n$ 在空间和步骤数量上都得到优化。为了证明这一点,可以观察到计算$b^{2n}$ 使用 + 过程演变而来,其空间和步骤数都随着 + $n$ 的对数级增长。 + 要理解这一点,可以观察使用 - 快速幂函数 + fast-expt fast_expt - 只需比计算 -$b^n$ . 因此,我们可以计算的指数的大小在每次允许的乘法中大约翻倍。因此,计算需要的乘法次数对于一个指数 $n$ 以接近对数的速度增长 $n$ 以 2 为底。该过程已 $\Theta(\log n)$ 增长。更准确地说,所需的乘法次数等于 $n$ 的以 2 为底的对数减去 1,加上 $n$ 的二进制表示中 1 的数量。这个总数总是小于 $n$ 的对数的两倍。定义阶次符号的任意常数 $k_1$ 和 $k_2$ 意味着,对于对数过程,所取对数的底数并不重要,因此所有这样的过程都可描述为 $\Theta(\log n)$。 + 计算 $b^{2n}$ 只比计算 $b^n$ 多一次乘法。 + 因此,所能计算的指数大小(大约)随着允许的乘法次数的增加而翻倍。 + 这样,计算指数为 $n$ 所需的乘法次数增长速率大致与以2为底的对数增长一致。 + 该过程有 $\Theta(\log n)$ 的增长率。更精确地说,所需乘法次数等于 $n$ 的以2为底对数减1,加上 $n$ 二进制表示中1的个数。该总和总是小于两倍的以2为底的对数。 + 在渐进符号定义中的任意常数 $k_1$$k_2$ 表明,对于对数过程,取对数的底数无关紧要,因此所有此类过程均描述为 $\Theta(\log n)$ + - $\Theta(\log n)$ 增长和 $\Theta(n)$ 增长之间的差异在 $n$ 变大时变得非常明显。例如, + $\Theta(\log n)$ 增长与 $\Theta(n)$ 增长之间的差异随着 $n$ 变大而变得非常显著。例如, - 快速幂函数 + fast-expt fast_expt - 当 $n=1000$ 时,仅需 14 次乘法.你可能会想,为什么有人会关心将数字提高到 1000 次方。请参见 - section. - 同样可以利用逐次平方法的思想来设计一种迭代算法,该算法可以在以对数数量的步骤中计算指数(见 练习),尽管,正如迭代算法经常出现的那样,这种算法并没有像递归算法那样简洁地写下来.这个迭代算法是古老的。它出现在 - Chandah-sutra - Chandah-sutra 由 - Pingala, chrya - chrya,写于公元前 200 年之前。见 - Knuth, Donald E. - Knuth 1997b,第 4.6.3 节,关于此和其他幂运算方法的全面讨论和分析。 - 幂运算 + 当 $n=1000$ 时,仅需要14次乘法。您可能会想,为什么有人会关心将数值求到1000次方。参见第节。 + 也可以利用连乘平方的思想设计一个迭代算法,它以对数级的步骤数计算指数(参见练习),尽管像许多迭代算法一样,该算法不像递归算法那样直接表达。这个迭代算法由来已久。它出现在公元前200年之前由 + 平伽罗 (Pingala, chrya) + 所著的Chandah-sutra中。详见 + 唐纳德·E·克努斯 (Donald E. Knuth) + Knuth 1997b,第4.6.3节,内有关于该方法及其他指数计算方法的完整讨论与分析。 + 指数运算 - + 设计一个 - 过程 + 过程 函数 - ,该过程演变为一个迭代的幂运算过程,使用逐次平方法,并使用对数数量的步骤,正如 + ,它演变出一个使用连乘平方并使用对数级别步骤数的迭代指数运算过程,就像 - 快速幂函数. - fast_expt. + fast-expt + fast_expt - (提示:使用观察到的结果 -$(b^{n/2})^2 =(b^2)^{n/2}$ - ,保持与 - 指数一起 -$n$ - 和基数 -$b$ - 和一个额外的状态变量 -$a$ - ,并以这样的方式定义状态转变,使得乘积 -$a b^n$ - 状态在每个状态之间保持不变。在过程开始时 -$a$ - 被视为 1,答案由 - $n$ 的值给出 -$a$ - 在过程结束时。一般而言,定义一个 - 迭代过程的不变量 - 不变量,使其在状态之间保持不变,是思考 - 迭代过程算法设计 - 迭代算法设计的一种有效方法。) - - + (提示:利用观察到的 + $(b^{n/2})^2 =(b^2)^{n/2}$,除了指数 $n$ 和底数 + $b$,还保持一个额外的状态变量 + $a$,并以这样一种方式定义状态变换,使得乘积 $a b^n$ 在状态之间保持不变。过程开始时,取 $a$ 为1,结果由过程结束时 $a$ 的值给出。通常,定义一个 + 迭代过程的不变量量 + 不变量量,该量在状态之间保持不变,是设计 + 迭代算法设计 + 迭代算法的一个有力思考方式。) + - + fast_expt_iter example_1.17 8 @@ -304,32 +288,33 @@ function fast_expt(b, n){ return fast_expt_iter(1, b, n); } - + - + example_1.17 fast_expt_iter fast_expt(2, 3); - + - - - 本节中的幂运算算法基于通过重复乘法进行幂运算。类似地,可以通过重复加法执行整数乘法。以下乘法 + + + 本节中的指数算法基于通过重复乘法来执行指数运算。以类似的方式,可以通过重复加法来执行整数乘法。 + 以下乘法 - 过程 + 过程 函数 - (假设我们的语言只能加法,而不能进行乘法)类似于 -expt - + (假设我们的语言只能加法,不能乘法)与 + expt - 过程: + 过程: 函数: - + 类似。 + times_definition times_example @@ -356,32 +341,25 @@ function times(a, b) { times(3, 4); - - 该算法所需的步骤数量是线性的 -b - 现在假设我们包括,与 - 加法一起, - - 操作 - 函数 - -double - ,它将一个整数翻倍,和 -halve - ,它将一个(偶)整数除以 2。使用这些,设计一个乘法 + + 该算法所需的步骤数与 b 成线性关系。现在假设除了加法之外,我们还包括 - 过程 + 操作 函数 - 类似于 + double(将整数加倍)和 halve(将偶数整数除以2)。利用这些,设计一个与 - - 快速幂函数 + + fast-expt fast_expt - 使用对数数量的步骤。 + 类似且使用对数级别步骤数的乘法 + + 过程 + 函数 + - + example_1.18_definition example_1.18 even_definition @@ -404,11 +382,11 @@ function fast_times(a, b) { : a + fast_times(a, b - 1); } - - - + + + - + example_1.18 example_1.18_definition 12 @@ -418,29 +396,28 @@ fast_times(3, 4); - + - - 使用练习的结果 - 和,设计一个 + + 利用练习题的结果, + 设计一个 - 过程 + 过程 函数 - ,生成一个迭代过程,以加法、翻倍和除以一半为基础 - 用对数数量的步骤实现两个整数的乘法。这个 - 算法,有时被称为 - 俄罗斯农民乘法法 - 俄罗斯农民法乘法 - 俄罗斯农民法 的乘法,是古老的。它的使用示例在 - 莱茵德纸草书 - 莱茵德纸草书中可以找到,这是现存的两部最古老的数学文献之一, - 约公元前 1700 年由一位名叫 - AhmoseAh-mose - Ah-mose 的埃及抄写员撰写(并复制自更古老的文献)。 - + ,生成一个关于加法、加倍和减半的整数乘法的迭代过程,并且该过程使用对数级别的步骤数。这 + 个算法,有时称为 + 俄国农民乘法法 + 俄国农民法乘法 + 俄国农民法乘法,历史悠久。其使用实例见于 + 林德纸草文献 + 林德纸草文献,这是现存最古老的两个数学文献之一,约成书于公元前1700年(且抄自更古老的文献),出自 + 一位名叫 + 阿赫摩斯A赫摩斯 + 阿赫摩斯的埃及文士之手。 + - + fast_times_iter example_1.19 even_definition @@ -467,10 +444,11 @@ function times(a, b) { return fast_times_iter(0, a, b); } - - - - + + + + + example_1.19 fast_times_iter 12 @@ -480,102 +458,56 @@ times(3, 4); - + - - 有一个聪明的算法,用于在 + + 有一种巧妙的算法可以在 fib对数版本 - 对数步骤中计算斐波那契数。回想状态变量的转换 -$a$$b$ - 在 + 的对数步数内计算斐波那契数。回顾状态变量的变换 + $a$$b$ - - fib-iter + + fib-iter fib_iter - 过程中,节: -$a\leftarrow a+b$$b\leftarrow a$ - 使用练习的结果 - 和,设计一个 - - 过程 - 函数 - - ,生成一个迭代过程,以加法、翻倍和除以一半为基础 - 用对数数量的步骤实现两个整数的乘法。这个 - 算法,有时被称为 - 俄罗斯农民乘法法 - 俄罗斯农民法乘法 - 俄罗斯农民法 的乘法,是古老的。它的使用示例在 - 莱茵德纸草书 - 莱茵德纸草书中可以找到,这是现存的两部最古老的数学文献之一, - 约公元前 1700 年由一位名叫 - AhmoseAh-mose - Ah-mose 的埃及抄写员撰写(并复制自更古老的文献)。 -$T$ - ,并观察应用 -$T$ - 一遍又一遍地 -$n$ - 次数,从 1 和 0 开始, - 产生这一对 -$\textrm{Fib}(n+1)$$\textrm{Fib}(n)$ - . 换句话说,斐波那契数是通过应用 -$T^n$ - . 也就是说,斐波那契数是通过应用 -$n$ - 次方的转换 -$T$ - 次方的转换 -$(1,0)$ - . 现在考虑 -$T$ - 特殊情况 -$p=0$$q=1$ - 在一系列变换中 -$T_{pq}$ - 在 -$T_{pq}$ - 变换序对 -$(a,b)$ - 根据 -$a\leftarrow bq+aq+ap$$b\leftarrow bp+aq$ - 应用这种变换 -$T_{pq}$ - 两次,效果与使用单一变换相同 -$T_{p'q'}$ - 的相同形式,并计算 -$p'$$q'$ - 以...的术语 -$p$ $q$ - 两次变换,这个效果与使用单一变换相同 -$T^n$ - 使用逐次平方法,如在 + 第节的过程中 : + $a\leftarrow a+b$ 和 + $b\leftarrow a$ 。称此转换为 + $T$ ,并观察应用 $T$ 一遍又一遍 $n$ 次,从 1 和 0 开始, + 生成这对 $\textrm{Fib}(n+1)$ 和 + $\textrm{Fib}(n)$。换句话说,斐波那契数列是通过应用 + $T^n$ ,该 $n$ 第 + 次变换的幂 $T$,从 + 这对开始$(1,0)$。现在考虑 + $T$ 作为……的特例 + $p=0$$q=1$ 在一族变换中 $T_{pq}$ ,其中 $T_{pq}$ 转换这对 $(a,b)$ 根据 $a\leftarrow bq+aq+ap$$b\leftarrow bp+aq$。证明如果我们应用这样的变换 $T_{pq}$两次,效果与使用单次变换相同$T_{p'q'}$ 形式相同,并计算 + $p'$$q'$ 从……方面来说 + 关于 $p$ $q$。这为我们提供了一种显式的方法来对这些变换求平方,因此我们可以计算$T^n$使用连乘平方法,正如 - 快速幂函数 + fast-expt fast_expt - 过程。 + 过程。 函数。 - 将所有这些放在一起以完成以下 + 将这些组合在一起完成以下 - 过程, + 过程, 函数, - 其运行在对数数量的步骤中:此练习是 + 它以对数步数运行:本练习是 - - 提出的 - + + 由我们提出的 + - 由 + 基于 Stoy, Joseph E. - Joe Stoy,基于 + Joe Stoy,依据 Kaldewaij, Anne - Kaldewaij 1990 中的一个例子。 + Kaldewaij 1990中的一个例子。 fib_log_definition even_definition @@ -618,8 +550,8 @@ function fib_iter(a, b, p, q, count) { } - - + + fib_log_solution example_1.20 even_definition @@ -644,10 +576,11 @@ function fib_iter(a, b, p, q, count) { count - 1); } - - - - + + + + + example_1.20 fib_log_solution 5 diff --git a/xml/zh/chapter1/section2/subsection5.xml b/xml/zh/chapter1/section2/subsection5.xml index a29b0070d..199a4a360 100644 --- a/xml/zh/chapter1/section2/subsection5.xml +++ b/xml/zh/chapter1/section2/subsection5.xml @@ -5,225 +5,210 @@ 最大公约数 - 两个整数的最大公约数 (GCD) 被定义为能够同时整除这两个数 $a$ 和 $b$ 的最大整数,并且没有余数。例如,16 和 28 的 GCD 是 4。在章中,当我们研究如何实现有理数算术时,我们将需要能够计算 GCD,以便将有理数简化为最简形式。(要将有理数简化为最简形式,我们必须将分子和分母都除以它们的 GCD。例如,16/28 简化为 4/7。) 找到两个整数 GCD 的一种方法是对它们进行因子分解并搜索共同因子,但还有一个著名的算法更为高效。 + 两个整数 $a$$b$ 的最大公约数(GCD)定义为同时整除 $a$$b$ 且没有余数的最大整数。例如,16 和 28 的最大公约数是 4。在第 章中,当我们研究如何实现有理数运算时,我们将需要能够计算最大公约数,以便将有理数约分到最简形式。(要将有理数约分到最简形式,必须将分子和分母同时除以它们的最大公约数。例如,16/28 约分为 4/7。)找到两个整数的最大公约数的一种方法是对它们进行因数分解并寻找公因数,但有一种著名的算法效率更高。 - - 欧几里得算法 - 算法的思想基于这样的观察:如果 -$r$ 是余数,当 -$a$ 被除以 -$b$ , 然后 的公因子 -$a$ 和 -$b$ 正是 的公因子 -$b$ 和 -$r$ . 因此,我们可以使用方程 - + + 欧几里得算法 + 该算法的思想基于以下观察:如果 + $r$ 是 + $a$ 除以 + $b$ 的余数, + 则 + $a$$b$ 的公约数 + 与 $b$$r$ 的公约数恰好相同。 + 因此,我们可以利用等式 + \[\begin{array}{lll} - \textrm{GCD} (a, b) &=& \textrm{GCD}(b, r) + \textrm{最大公约数} (a, b) &=& \textrm{最大公约数}(b, r) \end{array}\] - - 逐步将计算 GCD 的问题简化为计算越来越小的整数对的 GCD 的问题。例如, - - + + 逐步将计算最大公约数的问题简化为计算较小整数对最大公约数的问题。例如, + \[\begin{array}{lll} - \textrm{GCD}(206,40) & = & \textrm{GCD}(40,6) \\ - & = & \textrm{GCD}(6,4) \\ - & = & \textrm{GCD}(4,2) \\ - & = & \textrm{GCD}(2,0) \\ + \textrm{最大公约数}(206,40) & = & \textrm{最大公约数}(40,6) \\ + & = & \textrm{最大公约数}(6,4) \\ + & = & \textrm{最大公约数}(4,2) \\ + & = & \textrm{最大公约数}(2,0) \\ & = & 2 \end{array}\] - - 化简 -$\textrm{GCD}(206, 40)$ 到 -$\textrm{GCD}(2, 0)$ , 其结果是 2。可以显示,从任何两个正整数开始,进行反复化简将始终最终产生一对,其中第二个数字为 0。然后 GCD 就是这一对中的另一个数字。这个计算 GCD 的方法被称为 欧几里得算法欧几里得算法之所以如此称呼,是因为它出现在欧几里得的 欧几里得的几何原本欧几里得的 几何原本 几何原本(书 7,大约公元前 300 年)。根据 Knuth, Donald E. Knuth (1997a) 的说法,可以认为这是已知的最古老的非平凡算法。古埃及的乘法方法(练习)无疑更古老,但正如 Knuth 解释的,欧几里得算法是已知的最古老的被提出作为一般算法的方法,而不是一组插图示例。 - + + 将 $\textrm{最大公约数}(206, 40)$ 化简为 + $\textrm{最大公约数}(2, 0)$, + 结果为 2。可以证明,从任意两个正整数开始, + 反复进行这种化简最终总会得到第二个数为 0 的数对, + 此时的最大公约数就是该数对中的另一个数。 + 这种计算最大公约数的方法被称为 欧几里得算法欧几里得算法之所以得名, + 是因为它出现在欧几里得的 + 欧几里得著作欧几里得几何原本 + (第7本,约公元前300年)中。根据 + 唐纳德·E·克努斯 + 克努斯(1997a)的说法,它可以被认为是已知最古老的非平凡算法。 + 古埃及的乘法方法(练习)当然更早, + 但正如克努斯所解释的,欧几里得算法是已知最早被作为通用算法提出的方法, + 而非仅作为一组示例。 + - 表达欧几里得算法很容易作为一个 - - 过程: - 函数: + 用 + 过程: + 函数: - - gcd - gcd_definition - gcd_example - -(define (gcd a b) + 表达欧几里得算法很简单: + + 最大公约数 + 最大公约数定义 + 最大公约数示例 + +(define (最大公约数 a b) (if (= b 0) a - (gcd b (余数 a b)))) - + (最大公约数 b (remainder a b)))) + -function gcd(a, b) { - return b === 0 ? a : gcd(b, a % b); +function 最大公约数(a, b) { + return b === 0 ? a : 最大公约数(b, a % b); } - + - - gcd_example - gcd_definition + + 最大公约数示例 + 最大公约数定义 4 - -(gcd 20 12) - + +(最大公约数 20 12) + -gcd(20, 12); +最大公约数(20, 12); - - 这生成了一个迭代过程,其步骤数量随着相关数字的对数增长。 + + 该方法生成一个迭代过程,其步骤数随所涉及数字的对数增长。 - - 欧几里得算法所需步骤数量的对数增长与 - 斐波那契数欧几里得的 GCD 算法与 - 斐波那契数之间存在有趣的关系: -
    - 拉梅定理: - 拉梅拉梅定理 - 如果欧几里得的算法需要 -$k$ 步骤来计算某对的 GCD,那么对中的较小数字必须大于或等于 -$k$ -第 th 斐波那契数。 - -这个定理由 -拉梅拉梅 证明于1845年,他是一位法国数学家和工程师,主要以对数学物理的贡献而闻名。为了证明该定理,我们考虑对 -$(a_k ,b_k)$ , 其中 -$a_k\geq b_k$ , 其中欧几里得的算法在 -$k$ -步骤。证明基于这样的说法,如果 -$(a_{k+1},\ b_{k+1}) \rightarrow (a_{k},\ b_{k}) - \rightarrow (a_{k-1},\ b_{k-1})$ -是三个连续对在化简过程中,那么我们必须 -$b_{k+1}\geq b_{k} + b_{k-1}$ -要验证这一说法,考虑到化简步骤是通过应用 -$a_{k-1} = b_{k}$, - $b_{k-1} = - \textrm{remainder of}\ a_{k}\ \textrm{divided by}\ b_{k}$ -步骤。第二个方程意味着 -$a_{k} = qb_{k} + b_{k-1}$ -某个正整数 -$q$ -因为 -$q$ -至少必须为 1,我们有 -$a_{k} = qb_{k} + b_{k-1} \geq b_{k} + b_{k-1}$ -。而在之前的化简步骤中,我们有 -$b_{k+1}= a_{k}$ -。因此, -$b_{k+1} = a_{k}\geq b_{k} + b_{k-1}$ -。这验证了该声称。现在我们可以通过对 -$k$ -算法终止所需的步骤数。结果对于 -$k=1$ -,因为这仅仅要求 -$b$ -至少与 -$\text{Fib}(1)=1$ -。假设结果对于所有小于或等于 -$k$ -并建立结果 -$k+1$ -。设 -$(a_{k+1},\ b_{k+1})\rightarrow(a_{k},\ b_{k}) - \rightarrow(a_{k-1},\ b_{k-1})$ -在化简过程中是连对。根据我们的归纳假设,我们有 -$b_{k-1}\geq {\textrm{Fib}}(k-1)$ -和 -$b_{k}\geq {\textrm{Fib}}(k)$ -因此,将我们刚刚证明的结论与斐波那契数的定义结合起来得到 -$b_{k+1} \geq b_{k} + b_{k-1}\geq {\textrm{Fib}}(k) + - {\textrm{Fib}}(k-1) = {\textrm{Fib}}(k+1)$ -,这完成了拉梅定理的证明。 + + 欧几里得算法所需步骤数具有对数增长的事实, + 欧几里得算法增长阶 + 与 + 斐波那契数欧几里得最大公约数算法与 + 斐波那契数之间有一个有趣的联系: +
    + 拉姆定理: + 拉姆拉姆定理 + 如果欧几里得算法 + 计算某对数的最大公约数需要$k$步,那么该对中的较小数字必须大于或等于第 + $k$个斐波那契数。该定理由 + 拉姆加布里埃尔·拉姆 + 于1845年证明,他是 + 一位法国数学家和工程师,主要以对数学物理学的贡献闻名。为证明该定理,我们考虑满足 + $a_k\geq b_k$,且欧几里得算法在 + $k$步后终止的数对$(a_k, b_k)$。证法基于下面的断言: + 若 + $(a_{k+1}, b_{k+1}) \rightarrow (a_k, b_k) \rightarrow (a_{k-1}, b_{k-1})$是约简过程中的连续三对, + 则必须有 + $b_{k+1} \geq b_k + b_{k-1}$。 + 验证该断言时,考虑约简步骤定义为应用变换 + $a_{k-1} = b_k$以及 + $b_{k-1} = a_k\ \textrm{除以}\ b_k\ \textrm{的余数}$。 + 第二个等式意味着 + $a_k = q b_k + b_{k-1}$,其中 + $q$为某个正整数。由于 + $q \geq 1$,则有 + $a_k = q b_k + b_{k-1} \geq b_k + b_{k-1}$。 + 在前一步约简中,$b_{k+1} = a_k$,因此, + $b_{k+1} = a_k \geq b_k + b_{k-1}$。 + 这验证了该断言。现在我们可以对算法终止所用步数 + $k$做归纳证明该定理。对于 + $k=1$,结果显然成立,因为这仅要求 + $b$至少等于 + $\text{Fib}(1)=1$。假设该结果对所有小于或等于 + $k$的整数成立,现对 + $k+1$进行证明。设 + $(a_{k+1}, b_{k+1}) \rightarrow (a_k, b_k) \rightarrow (a_{k-1}, b_{k-1})$为约简过程中的连续数对。 + 由归纳假设有 + $b_{k-1} \geq \textrm{Fib}(k-1)$且 + $b_k \geq \textrm{Fib}(k)$。因此,结合已证断言和斐波那契数的定义, + 有 + $b_{k+1} \geq b_k + b_{k-1} \geq \textrm{Fib}(k) + \textrm{Fib}(k-1) = \textrm{Fib}(k+1)$, + 证毕拉姆定理。
    -
    + - 我们可以利用这个定理来获得欧几里得算法的增长阶估计。设 $n$ 为两个输入中较小的一个 - - 过程. - 函数. - - 如果该过程需要 $k$ 步骤,则我们必须有 - $n\geq {\textrm{Fib}} (k)\approx\phi^k/\sqrt{5}$。 - 因此步骤数 $k$ 的增长与 - $n$ 的对数(以 $\phi$ 为底)相同。因此,增长阶为 - $\Theta(\log n)$。 - 最大公约数 - 欧几里得算法 + 我们可以利用该定理对欧几里得算法的增长阶做一个估计。设 + $n$为输入的两个数中较小的一个。 + 如果该过程执行了 + $k$步,那么必须有 + $n \geq \textrm{Fib}(k) \approx \phi^k/\sqrt{5}$。 + 因此,步骤数 + $k$随 + $n$的以 + $\phi$为底的对数增长。故其增长阶为 + $\Theta(\log n)$。 + 最大公约数 + 欧几里得算法 - - - 由 + + + 由 - 过程 + 过程 函数 - 生成的过程当然取决于解释器使用的规则。作为一个例子,考虑迭代 -gcd - -上述 + 生成的过程当然依赖于解释器所使用的规则。 + 例如,考虑上面给出的迭代的 + gcd - 过程 + 过程 函数 - - 。假设我们使用 - 正常序求值应用序 vs. - 应用序求值正常序 vs. - 正常序求值来解释这个 + 。 + 假设我们用 + 正常顺序求值应用顺序对比 + 应用顺序求值正常顺序对比 + 中讨论的正常顺序求值来解释该 - 过程 + 过程 函数 - - ,正如在 - 一节中讨论的那样。(正常序求值规则 + , + 如章节所述。( - if + if 条件表达式 - 的描述见练习。) - 使用代换法(对于正常序),说明评估过程中的 + 的正常顺序求值规则见练习题。) + 利用替换法(针对正常顺序),说明计算 - (gcd 206 40) + (gcd 206 40) gcd(206, 40) - 生成的过程,并指示 -remainder -实际执行的操作。多少 -remainder -在正常序求值中实际执行的操作有多少 + 时生成的过程,并指出实际执行了哪些 + 求余操作。 + 在正常顺序求值 - (gcd 206 40)? + (gcd 206 40)? gcd(206, 40)? - 在应用序求值中又有多少? - + 中实际执行了多少 + 求余操作? + 在应用顺序求值中呢? +
      -
    1. -使用正常序求值,过程经历了 18 次余数操作。在评估条件时有 14 次,最终化简阶段有其余的操作。 - +
    2. + 在正常顺序求值中,该过程经历了18次求余操作。 + 在判定条件时进行了14次,其余在最终归约阶段执行。 + gcd(206, 40) 40 === 0 ? 206 : gcd(40, 206 % 40) gcd(40, 206 % 40) 206 % 40 === 0 ? 40 : gcd(206 % 40, 40 % (206 % 40)) -// remainder operation (1) +// 求余操作 (1) 6 === 0 ? 40 : gcd(206 % 40, 40 % (206 % 40)) gcd(206 % 40, 40 % (206 % 40)) @@ -231,7 +216,7 @@ gcd(206 % 40, 40 % (206 % 40)) ? 206 % 40 : gcd(40 % (206 % 40), (206 % 40) % (40 % (206 % 40))) -// remainder operations (2) and (3) +// 求余操作 (2) 和 (3) 4 === 0 ? 206 % 40 : gcd(40 % (206 % 40), @@ -242,7 +227,7 @@ gcd(40 % (206 % 40), (206 % 40) % (40 % (206 % 40))) : gcd((206 % 40) % (40 % (206 % 40)), (40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40))) -// remainder operations (4), (5), (6), (7) +// 求余操作 (4), (5), (6), (7) 2 === 0 ? 40 % (206 % 40) : gcd((206 % 40) % (40 % (206 % 40)), @@ -257,7 +242,7 @@ gcd((206 % 40) % (40 % (206 % 40)), ((206 % 40) % (40 % (206 % 40))) % ((40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40)))) -// remainder operations +// 求余操作 (8), (9), (10), (11), (12), (13), (14) 0 === 0 ? (206 % 40) % (40 % (206 % 40)) @@ -267,40 +252,40 @@ gcd((206 % 40) % (40 % (206 % 40)), ((40 % (206 % 40)) % ((206 % 40) % (40 % (206 % 40)))) (206 % 40) % (40 % (206 % 40)) -// remainder operations (15), (16), (17), (18) +// 求余操作 (15), (16), (17), (18) 2
    3. - -使用应用序求值,过程执行了 4 次余数操作。 - +
    4. + 在应用顺序求值中,该过程执行了4次求余操作。 + gcd(206, 40) 40 === 0 ? 206 : gcd(40, 206 % 40) gcd(40, 206 % 40) -// 余数操作 (1) +// 求余操作 (1) gcd(40, 6) 6 === 0 ? 40 : gcd(6, 40 % 6) gcd(6, 40 % 6) -// 余数操作 (2) +// 求余操作 (2) gcd(6, 4) 4 === 0 ? 6 : gcd(4, 6 % 4) gcd(4, 6 % 4) -// 余数操作 (3) +// 求余操作 (3) gcd(4, 2) 2 === 0 ? 4 : gcd(2, 4 % 2) gcd(2, 4 % 2) -// 余数操作 (4) +// 求余操作 (4) gcd(2, 0) 0 === 0 ? 2 : gcd(0, 2 % 0) 2 - - </LI> -
    + + +
    -
    +
    diff --git a/xml/zh/chapter1/section2/subsection6.xml b/xml/zh/chapter1/section2/subsection6.xml index c2d4554f4..5fc7d7125 100644 --- a/xml/zh/chapter1/section2/subsection6.xml +++ b/xml/zh/chapter1/section2/subsection6.xml @@ -3,30 +3,27 @@ 示例:素性测试 - 素数测试 - 素数 + 质数测试 + 质数 - 本节描述了两种检查整数 $n$ 素性的 - 方法,一种增长阶为 $\Theta(\sqrt{n})$, - 另一种是增长阶为 $\Theta(\log n)$ 的 - 概率算法。本节末尾的练习建议了基于这些算法的编程项目。 + 本节描述了两种检测整数 $n$ 是否为质数的方法,一种算法的增长阶为 $\Theta(\sqrt{n})$,另一种为 概率 算法,增长阶为 $\Theta(\log n)$。本节末的练习建议基于这些算法进行编程项目。 - 查找因子 + 寻找因数 - 自古以来,数学家们对素数问题着迷,许多人致力于确定测试数字是否为素数的方法。测试一个数字是否为素数的一种方法是查找该数字的因子。以下程序查找给定数字$n$的最小整数因子(大于 1)。它通过从 2 开始依次测试整数来直接实现n的可除性。 - + 自古以来,数学家们一直对素数相关的问题着迷,许多人致力于研究判定一个数是否为素数的方法。判定一个数是否为素数的一个方法是寻找这个数的因数。以下程序用于寻找给定数$n$的最小整数因数(大于1)。它以直接的方法实现,通过依次检测从2开始的整数是否能整除$n$</LATEXINLINE>。 + find_divisor divides smallest_divisor smallest_divisor_definition square_definition smallest_divisor_example - + (define (smallest-divisor n) (find-divisor n 2)) @@ -37,7 +34,7 @@ (define (divides? a b) (= (remainder b a) 0)) - + function smallest_divisor(n) { return find_divisor(n, 2); @@ -53,135 +50,136 @@ function divides(a, b) { return b % a === 0; } - - + + smallest_divisor_example smallest_divisor_definition 2 - + (smallest-divisor 42) - + smallest_divisor(42); - - + + - 我们可以如下测试一个数字是否为素数: - 当且仅当 $n$ 是其自身的最小因子时, - $n$ 才是素数。 - + 我们可以如下测试一个数是否为素数: + $n$ 是素数当且仅当 + $n$ 是其自身的最小因数。 + is_prime prime_definition smallest_divisor_definition prime_example - + (define (prime? n) (= n (smallest-divisor n))) - + function is_prime(n) { return n === smallest_divisor(n); } - + - + prime_example prime_definition false - + (prime? 42) - + is_prime(42); - + - 终止测试 - - find-divisor - find_divisor - - 基于这样一个事实:如果 $n$ 不是素数, - 则它必须有一个小于或等于 $\sqrt{n}$ 的因子。如果 - $d$ 是 - $n$ 的因子,那么 $n/d$ 也是。 - 但 $d$ 和 $n/d$ 不能都大于 $\sqrt{n}$。 - 这意味着算法只需测试介于 1 和 $\sqrt{n}$ 之间的因子。因此,识别 $n$ 为素数所需步骤数的增长阶为 $\Theta(\sqrt{n})$。 + find-divisor + find_divisor + 的终止测试基于这样一个事实:如果 $n$ 不是素数,它一定有一个不大于 $\sqrt{n}$ 的因数。如果 $d$$n$ 的因数,那么 $n/d$ 也是因数。但 $d$$n/d$ 不可能都大于 $\sqrt{n}$这意味着算法只需要测试介于 1 和 $\sqrt{n}$ 之间的因数。因此,识别 $n$ 为素数所需的步骤数增长的阶为 $\Theta(\sqrt{n})$ 费马测试 - - The $\Theta(\log n)$ 素性测试基于数论中的一个结果,称为 + + $\Theta(\log n)$ 费马测试基于数论中的一个结果,称为 费马素性测试 - 素数费马测试 - 费马的小定理。皮埃尔 - 费马费马,皮埃尔·德 - 费马(16011665)被认为是现代 + 素数费马测试 for + 费马小定理。皮埃尔 + 费马皮埃尔·德·费马 + (16011665)被认为是现代 数论 - 数论的创始人。他取得了许多重要的数论结果,但他通常只公布结果,没有提供证明。 - 费马的费马小定理证明 - 费马的小定理在1640年他写的一封信中被陈述。第一个已发表的证明由 - 欧拉,莱昂哈德费马的小定理的证明 - 欧拉在1736年给出(而在未发表的 - 莱布尼茨,戈特弗里德·威廉·冯费马的小定理的证明 - 莱布尼茨的手稿中找到了更早的相同证明)。费马最著名的结果被称为费马最后定理于1637年在他所持有的书籍算术(由三世纪的希腊数学家 - 丢番图算术,费马的版本 - 丢番图)中写下,并留下旁注我发现了一个真正非凡的证明, 但这段空白不够写下它。找到费马最后定理的证明成为数论中最著名的挑战之一。最终完整的解决方案由 - 威尔斯,安德鲁 - 普林斯顿大学的安德鲁·威尔斯在1995年给出。 + 的创始人之一。他获得了许多重要的数论结果,但他通常只公布结果,而不提供证明。 + 费马小定理费马小定理的证明 + 费马小定理最初是在他1640年写的一封信中提出的。第一个公开的证明由 + 欧拉,莱昂哈德费马小定理的证明 + 欧拉于1736年给出(之前在 + 莱布尼茨,巴伦·戈特弗里德·威廉·冯费马小定理的证明 + 莱布尼茨的未公开手稿中发现了相同的证明)。费马最著名的结果之一—— + 被称为费马大定理——于1637年写在他那本 + 算术(是三世纪希腊数学家 + 丢番图的算术,费马的副本 + 丢番图的著作)的边缘,并附有批注我发现了一个真正卓越的证明,但此边空太小,无法容纳。。寻找费马大定理的证明成为数论中最著名的挑战之一。1995年 + 怀尔斯,安德鲁 + 普林斯顿大学的安德鲁·怀尔斯最终给出了完整的解决方案。 -
    - 费马的小定理: - 费马的费马的小定理 - 如果 $n$ 是一个素数,并且 - $a$ 是小于 $n$ 的任意正整数, - 那么 $a$ 的 $n$ 次幂同余于 - $a$ 模$n$。 +
    + 费马小定理: + 费马小定理费马小定理 + 如果 $n$ 是素数,且 + $a$ 是任何小于 + $n$ 的正整数,那么 $a$ 的 + $n$ 次幂模 + $n$ 余数与 + $a$ 相同。
    - (两个数字被称为 - 模 $n$ 同余 - 同余模 - $n$ 如果它们在除以 时有相同的余数 $n$ 。 一个数字的余数 $a$ 被除以 $n$ 也被称为 - 模 $n$ 余数 - 模 $n$ - 的余数 $a$ - $n$ ,或简称为 $a$ - $n$ 。) - + (两个数被称为 + 同余于 $n$ + 模同余 + $n$ 是指当被 + $n$ 除时余数相同。一个数 + $a$ 除以 + $n$ 的余数也称为 + 余数$n$ + $n$ + 余数 $a$ + $n$,或简称为 $a$ + $n$。) + - 如果 $n$ 不是素数,那么通常大多数小于 - $n$ 的数字 $a$ 将不满足上述关系。这导致了以下用于测试素性的算法: - 给定一个数 $n$,选择一个 - 随机数生成器素性素性测试中使用 - 随机数 $a < n$ 并计算 $a^n$ 模 - $n$ 的余数。如果结果不等于 - $a$,则 $n$ 一定不是素数。如果等于 - $a$,那么 $n$ 很可能是素数。现在选择另一个 - 随机数 $a$ 并用同样的方法测试它。如果它也满足这个方程,那么可以更有信心地认为 - $n$ 是素数。通过尝试越来越多的 $a$ 值,可以增加对结果的信心。这个算法被称为费马测试。 + 如果 $n$ 不是素数,那么通常大多数小于 + $n$ 的数 $a$ 不会满足上述关系。 这就产生了以下的素性测试算法: + 给定一个数字 $n$,选择一个 + 随机数生成器素性在素性测试中 + 随机数 $a < n$ 并计算 + $a^n$ 模 + $n$ 的余数。 如果结果不等于 + $a$,那么 $n$ 肯定不是素数。 如果结果等于 + $a$,那么 $n$ 很可能是素数。 现在再选择另一个随机数 + $a$,用相同的方法测试它。 如果它也满足该等式,那么我们可以更加确信 + $n$ 是素数。 通过尝试更多的 + $a$ 值,我们可以提高对结果的置信度。 该算法称为费马测试。 - - 要实现费马测试,我们需要一个 + + 为了实现费马测试,我们需要一个 - 过程 + 过程 函数 - 来计算一个数的 - 幂运算模 $n$ - : - + 来计算模另一个数的 + 指数运算$n$ + 指数: + expmod expmod_definition expmod_example @@ -220,128 +218,87 @@ function expmod(base, exp, m) { expmod(4, 3, 5); - - 这与 + + 这与第 - fast-expt + fast-expt fast_expt - 在第节中的 + 篇中的 - 过程 + 过程 函数 - 非常相似。它使用逐次平方,因此步骤数与指数的增长呈对数关系。在指数 - $e$ 大于 1 的情况下,简化步骤基于这样的事实:对于任何整数 - $x$, - $y$ 和 $m$,我们可以通过分别计算 - $x$ 和 $y$ 对 $m$ 的余数, - 然后将这些结果相乘,最后取结果对 - $m$ 的余数来找到 - $x \times y$ 对 $m$ 的余数。例如,当 - $e$ 为偶数时,我们计算 $b^{e/2}$ 对 - $m$ 的余数,平方这个值,然后对 $m$ 取余。这种技术很有用,因为它意味着我们可以在计算过程中不用处理比 - $m$ 大得多的数字。(比较练习。) - + 非常相似。它使用连乘平方法,因此步骤数随指数的增长呈对数增长。当指数 + $e$ 大于 1 时的化简步骤基于这样一个事实:对于任意整数 + $x$$y$ 和 + $m$,计算 $x$ 乘 + $y$ 对 + $m$ 取模的余数,可以通过分别计算 + $x$ 对 + $m$ 的余数和 + $y$ 对 + $m$ 的余数,将它们相乘,然后对结果再次取模 + $m$ 来实现。例如,当 + $e$ 为偶数时,我们先计算 + $b^{e/2}$ 对 + $m$ 取模的余数,将其平方,再对 + $m$ 取模。该技术的优势在于计算过程中我们永远不需要处理大于 + $m$ 很多数量级的数。(参见练习。) + - - 费马测试通过随机选择一个数字来进行 - $a$ 介于 1 和 - $n-1$ 之间,并检查它对 的模余数是否 - $n$ 的 - $n$ 次幂的 $a$ 是否等于 $a$ 。 随机数 - $a$ 是使用 + + 费马测试通过随机选择一个数 + $a$,范围在 1 到 + $n-1$(含)之间,并检查 + $a$ 的 + $n$ 次方对 + $n$ 取模的余数是否等于 + $a$。随机数 + $a$ 是用 - - 过程 - random, - + + random 过程 + - 原语函数 - math_random, + math_random 原始函数 + 选取的,我们假设它作为 Scheme 的原始函数被包含。 - 我们假设其作为 Scheme 的原语包括在内。 - - - - random (原语函数) + + random (原始函数) random - Random - 返回一个小于其整数输入的非负整数。因此,要获得 - 一个介于 1 和 $n-1$ 之间的随机数,我们调用 - random 的输入为 $n-1$ 并加 1: - + random + 返回一个非负整数,小于其输入整数。因此,为了得到一个介于 1 和 + $n-1$ 之间的随机数,我们用 + 输入 + $n-1$ 调用 + random 并将结果加 1: + - math_random (原语函数) + math_random (原始函数) math_randomMath.random - 返回一个小于1的非负数。因此,要获得 - 一个介于 1 和 $n-1$ 之间的随机数,我们将 + 该函数返回一个小于 1 的非负数。因此,为了得到一个介于 1 到 + $n-1$ 的随机数,我们将 math_random 的返回值乘以 - $n-1$,用原语函数 - math_floor (原语函数) + $n-1$,利用原始函数 + math_floor (原始函数) math_floorMath.floor - math_floor 向下取整, - 并加 1: + math_floor 向下取整后加 1: - - random_definition - -;; random is predefined in Scheme - - -function random(n) { - return math_floor(math_random() * n); -} - - - - fermat_test - fermat_test_definition - square_definition - expmod_definition - random_definition - fermat_test_example - -(define (fermat-test n) - (define (try-it a) - (= (expmod a n n) a)) - (try-it (+ 1 (random (- n 1))))) - - -function fermat_test(n) { - function try_it(a) { - return expmod(a, n, n) === a; - } - return try_it(1 + math_floor(math_random() * (n - 1))); -} - - - - - fermat_test_example - fermat_test_definition - true - -(fermat-test 97) - - -fermat_test(97); - - - + - 下面的 + 以下 - 过程 + 过程 函数 - 根据参数指定的次数运行测试。如果测试每次都成功,其值为 true,否则为 false。 - + 运行测试若干次,由参数指定。如果每次测试均成功,其值为真,否则为假。 + fast_is_prime fast_prime_definition square_definition @@ -349,13 +306,13 @@ fermat_test(97); random_definition fermat_test_definition fast_prime_example - + (define (fast-prime? n times) (cond ((= times 0) true) ((fermat-test n) (fast-prime? n (- times 1))) (else false))) - + function fast_is_prime(n, times) { return times === 0 @@ -365,18 +322,18 @@ function fast_is_prime(n, times) { : false; } - - + + fast_prime_example fast_prime_definition true - + (fast-prime? 97 3) - + fast_is_prime(97, 3); - + @@ -384,104 +341,73 @@ fast_is_prime(97, 3); 概率算法 - 算法概率 + 算法概率算法 - 费马测试与大多数熟悉的算法不同,后者能计算出保证正确的答案。而费马测试得到的答案只是可能正确。更确切地说,如果 - $n$ 在费马测试中失败,我们可确定 - $n$ 不是素数。但 $n$ 通过测试的事实,虽然是一个极强的指示,仍不能保证 - $n$ 是素数。我们想说的是,对于任何数字 - $n$,如果我们进行足够多次测试,并发现 - $n$ 每次都通过测试,那么我们素性测试中出错的概率可以降到任意小。 + 费马测试的性质不同于大多数熟悉的算法,后者计算出的答案是保证正确的。而这里得到的答案只是可能正确的。更准确地说,如果$n$在费马测试中失败,我们可以确定$n$不是素数。但$n$通过测试虽然是一个极其有力的指示,但仍不能保证$n$是素数。我们希望说的是,对于任何数字$n$,如果我们进行足够多次的测试并发现$n$总是通过测试,那么我们素性测试的错误概率可以被降低到任意小的程度。 - - 不幸的是,这一断言并不完全正确。确实存在一些数字能欺骗费马测试:数字 - $n$ 哪些非素数却具有这样的性质 - $a^n$ 同余于 $a$ 模 - $n$ 对于所有整数 - $a < n$ 。这类数字非常罕见,因此在实践中费马测试是相当可靠的。 - - 能欺骗费马测试的数字称为 + + 不幸的是,这个断言并不完全正确。确实存在一些数字能够欺骗费马测试:这些数字$n$不是素数,但却具有这样一个性质,即对于所有小于$n$的整数$a$$a^n$$n$同余于$a$。这种数字极其罕见,因此费马测试在实践中是相当可靠的。 + + 能够欺骗费马测试的数字称为 卡迈克尔数 - 卡迈克尔数,对它们的研究不多,只知道它们非常稀少。在一亿以下有 255 个 - 卡迈克尔数。最小的几个是 561, 1105, - 1729, 2465, 2821, 和 6601。在随机选择的非常大的数字的素性测试中,遇到一个欺骗费马测试的值的机会小于 - 宇宙辐射 - 宇宙辐射造成计算机在进行所谓 - 正确算法时出错的几率。认为一个算法因第一原因而不足(而非第二原因)体现了 - 工程学 vs.数学 - 数学工程学 vs. + 卡迈克尔数,除了它们极其罕见之外,人们对它们知之甚少。在一亿以下共有255个卡迈克尔数。最小的几个包括561、1105、1729、2465、2821和6601。在对随机选择的非常大数字进行素性测试时,偶然遇到能欺骗费马测试的数字的概率甚至比 + 宇宙射线 + 宇宙射线导致计算机在执行一个正确算法时产生错误的概率还低。将算法认为因第一种原因而不充分,但因第二种原因却充分,体现了 + 数学与工程学的区别 数学与工程学的区别。 - 费马测试有一些变种无法被愚弄。在这些测试中,就像费马方法一样,人们测试一个整数的素性 - $n$ 通过选择一个随机整数 - $a < n$ 并检查一些依赖于 的条件 - $n$ 和 - $a$ 。 (请参见练习 了解此类测试的示例。) - 另一方面,与费马测试相比,可以证明,对于任何 $n$ ,对于大多数整数,该条件不成立 - $a < n$ 除非 - $n$ 是素数。因此,如果 - $n$ 通过某个随机选择的测试 - $a$ ,则 的机会大于一半 - $n$ 是素数。如果 - $n$ 在两个随机选择下通过测试 - $a$ ,则 的机会大于 3/4 - $n$ 是素数。通过对更多随机选择的 值运行测试 - $a$ 我们可以将出错的概率降低到任意小。 - 费马素性测试 - 素数费马测试 - + 费马测试有一些变体不会被欺骗。在这些测试中,与费马方法一样,人们通过选择一个小于$n$的随机整数$a$并检查一个依赖于$n$$a$的条件来测试整数$n$的素性。(参见 + 习题,该习题提供了此类测试的例子。)另一方面,与费马测试不同的是,可以证明,对于任何$n$,除非$n$是素数,否则大多数小于$n$的整数$a$不满足该条件。因此,如果$n$对某个随机选择的$a$通过了测试,那么$n$为素数的概率超过一半。如果$n$对两个随机选择的$a$都通过了测试,那么$n$为素数的概率超过四分之三。通过用越来越多随机选择的$a$运行该测试,我们可以将错误概率降低到任意小的程度。 + 费马素性测试 + 素数<与费马测试相关> + - - 存在这样一种测试,可以证明其错误的概率变得任意小,这引发了人们对这类算法的兴趣,这些算法被称为概率算法。 -概率算法 -算法概率 -素数 - 在这个领域有大量的研究活动,概率算法已经成功应用于许多领域。 概率素数测试最显著的应用之一是在 + + 存在一种测试,可以证明其错误概率可以任意接近零,这激发了人们对这类算法的兴趣,这类算法被称为 + 概率算法。这一领域有大量的研究活动,概率算法已被成功应用于许多领域。 + 概率素性测试最引人注目的应用之一是应用于 密码学 密码学领域。 - - 虽然现在仍计算上难以分解一个任意 200 位 - 数的因子,但可以用费马测试在几秒钟内检查这样一个数的素性。 - + + 虽然现在分解任意一个200位数在计算上是不可行的,但这样的数的素性可以用费马测试在几秒钟内检验。 + - 截至编写时(2021 年),仍然计算困难以分解一个任意 300 位 - 数的因子,但可以用费马测试在几秒钟内检查这样一个数的素性。 + 截至2021年,分解任意一个300位数在计算上仍然不可行,但这样的数的素性可以用费马测试在几秒钟内检验。 - 这个事实构成了一种用于构建 - 不可破解的密码的技术基础,它由 - Ronald L. Rivest - Rivest、 - Adi Shamir - Shamir 和 - Leonard Adleman - Adleman (1977) 提出。最终的 - RSA 算法 - RSA 算法 已成为一种广泛用于提升电子通讯安全的技术。由于这一点和相关的发展,研究 - 素数与密码学 - 素数,曾经被认为是“纯”数学中仅为本身而研究的话题的典范,现在发现它对密码学、电子资金转账和信息检索有重要的实际应用。 - + 这一事实构成了一种技术的基础,该技术用于构造 + 不可破解的密码,由 + 罗纳德·L·里维斯特 + 里维斯特, + 阿迪·沙密尔 + 沙密尔,和 + 伦纳德·阿德曼 + 阿德曼(1977年)提出。由此产生的 + RSA算法 + RSA算法已成为增强电子通信安全的广泛使用的技术。正因如此及相关的发展, + 素数密码学与 + 曾被认为是仅仅为自身研究的“纯”数学顶峰的素数,现在被发现对密码学、电子资金转账和信息检索具有重要的实际应用。 + - + 使用 - smallest-divisor + smallest-divisor smallest_divisor - 过程 + 过程 函数 - 找出以下数字的最小因子:199、1999、 - 19999。 + 找出下列数字的最小除数:199,1999,19999。 - + smallest_division_solution 199 smallest_divisor_definition @@ -490,9 +416,9 @@ smallest_divisor(199); // smallest_divisor(1999); // smallest_divisor(19999); - - - + + + @@ -500,49 +426,30 @@ smallest_divisor(199); - - 大多数 Lisp 实现都包含一个名为 - runtime - 原语 过程函数 (用 ns 标记的不是 IEEE Scheme 标准中的)runtimeruntime (ns) - runtime - 的原语,它返回一个整数,指定系统已经运行的时间量(例如,以微秒为单位)。以下是 - timed-prime-test + + 大多数 Lisp 实现包含一个原语,称为 + 运行时 + 原语 过程函数(标记为ns的内容不属于 IEEE Scheme 标准)运行时runtime (ns) + 运行时 + ,它返回一个整数,指明系统运行的时间长度(例如,以微秒为单位)。 以下 + timed-prime-test 过程, - + - 假设一个无参数的原语函数 - get_time (原语函数) + 假设有一个原语函数 + get_time(原语函数) get_timenew Date().getTime get_time - 返回自协调世界时 1970 年 1 月 1 日星期四 00:00:00 以来的毫秒数。此日期称为 - UNIXepoch - UNIX 纪元,是处理时间的函数规范的一部分 - UNIX$^{\textrm{TM}}$ 操作系统。 - 以下是 + 无参数,返回自 1970 年 1 月 1 日星期四 UTC 时间 00:00:00 以来经过的毫秒数。此日期称为 + UNIX纪元 + UNIX 纪元,并且是处理 UNIX + $^{\textrm{TM}}$ 操作系统中时间函数规定的一部分。 + 以下 timed_prime_test 函数, - 当用一个整数调用时 $n$ ,打印 - $n$ 并检查是否 - $n$ 是素数。如果 $n$ - 是素数, - - 过程 - 函数 - - 打印三个星号原语函数 display 返回其 - 参数,同时也打印它。在这里 - *** - 是一个 - 字符串,我们将其作为参数传递给 display 函数。 - 第节会更详细地介绍字符串。,然后是执行测试所用的时间。 - - - newline - - - + 当传入一个整数时 $n$,打印$n$并检查是否$n$是素数。如果$n$ 是素数, 过程 函数 打印三个星号原始函数 display 返回其参数,但也打印它。在这里 "***" 是一个 字符串,一个字符序列,我们将其作为参数传递给 display 函数。章节 更详细地介绍了字符串。,后面跟着执行测试所用的时间。 换行 display (primitive function) timed_prime_test timed_prime_definition @@ -591,41 +498,43 @@ function report_prime(elapsed_time) { timed_prime_test(43); - 使用这个 + 使用此 - 过程, - 函数, + 过程, + 函数, 编写一个 - 过程 + 过程 函数 - - search-for-primes - + + search-for-primes + - search_for_primes + search_for_primes - 用于检查指定范围内连续奇整数的素性。 - 使用您的 + 用于检查指定范围内连续奇数的素数性。 + 使用你的 - + 过程 - + 函数 - 找出大于 1000, 大于 10,000, 大于 100,000, 大于 1,000,000 的三个最小素数。注意测试每个素数所需的时间。由于测试算法的增长阶为 - $\Theta(\sqrt{n})$ ,您应该预期测试大约 10,000 附近的素数需要大约 - $\sqrt{10}$ 是测试大约 1000 附近的素数的时间的几倍。您的计时数据是否支持这一点?10,000 和 1,000,000 的数据支持这个 - $\sqrt{n}$ - 预测?您的结果是否与程序在您的计算机上按与计算所需步骤数成比例的时间运行的概念兼容? + 找出大于1000、大于10000、 + 大于100000、大于1000000的三个最小素数。记录测试 + 每个素数所需的时间。由于测试算法的增长阶为 + $\Theta(\sqrt{n})$,你应该预期测试 + 约10,000左右的素数大约需要 + $\sqrt{10}$ 大约是测试约1000以内素数所需时间的 倍。你的时间数据是否支持这一点?对于 100,000 和 1,000,000 的数据表现如何,$\sqrt{n}$ + 预测?你的结果是否与这样一种观点相符,即你机器上的程序运行时间与计算所需的步骤数成正比? - + search_for_primes_definition search_for_primes_example timed_prime_definition @@ -641,10 +550,10 @@ function search_for_primes(start, times) { : search_for_primes(start + 2, times); } - - - - + + + + search_for_primes_example search_for_primes_definition @@ -652,89 +561,85 @@ search_for_primes(10000, 3); // search_for_primes(10000000, 3); // search_for_primes(10000000000, 3); - - - - 时间数据相当清晰地支持了对于预测足够大的 $\sqrt{n}$,如 100,000 和 1,000,000。 + + + + 时间数据相当清楚地支持了对于足够大的数字(如100,000和1,000,000)而言,$\sqrt{n}$的预测。 + - - 更高效版本的 - smallest_divisor更高效版本 + + 在本节开头展示的 + smallest_divisor更高效的版本 - - smallest-divisor - + + smallest-divisor + smallest_divisor - 过程函数 - 在本节开头展示的进行许多不必要的测试:在检查数字是否能被 2 整除后,就没有必要再检查它是否能被更大的偶数整除。这表明用于 + 过程函数 + 做了大量不必要的测试:在检查了数字是否能被2整除之后,再去检查它是否能被更大的偶数整除就没有意义了。这表明 - - test-divisor - + + test-divisor + test_divisor - 的值不应该是 2、3、4、5、6、,而应该是 2、3、5、7、9、 - 。要实施这一更改, + 的取值不应该是 2、3、4、5、6、,而是 2、3、5、7、9、。要实现这个改动, - 定义一个过程 + 定义一个过程 声明一个函数 - next - 返回 3 如果其输入等于 2 - 否则返回其输入加2。修改 + next,当输入等于2时返回3,其他情况下返回输入加2。修改 - - smallest-divisor - + + smallest-divisor + smallest_divisor - 过程 + 过程 函数 - + 以使用 - - (next test-divisor) - + + (next test-divisor) + next(test_divisor) - 而非 + 替代 - (+ test-divisor 1). + (+ test-divisor 1). test_divisor + 1. - 拼入该修改版本的 + 使用包含此修改版本的 - - timed-prime-test - + + timed-prime-test + timed_prime_test - 的 - smallest-divisor, + smallest-divisor, smallest_divisor, - 对在练习中找到的 12 个素数运行测试。 - 由于此修改将测试步骤减半,您应该预期其运行速度大约提高一倍。如果没有确认这一预期,观察到的两种算法速度的比率是什么,您如何解释它与 2 的差异? - - + 对练习中找到的12个质数分别进行测试。由于该改动将测试步骤减半,你应该预期它运行速度大约提高一倍。这个预期是否得到证实?如果没有,两个算法的速度比是多少?你如何解释它与2不同的原因? + + better_smallest_divisor_solution function next(input) { @@ -792,42 +697,46 @@ function find_divisor(n, test_divisor) { timed_prime_test(43); - - - - 两种算法的速度比不完全是 2,但这可能是由于硬件/网络问题。与以前的解决方案相比,大约快 1.5 倍。 + + + + 两个算法的速度比不完全是2,这可能是由于硬件或网络问题导致。 + 相较于之前的方案,速度约提高了1.5倍。 - + + - - 修改练习中的 + + + 修改习题中的 - - timed-prime-test - + + timed-prime-test + timed_prime_test - 过程 + 过程 函数 以使用 - - fast-prime? - + + fast-prime? + fast_is_prime - (费马方法),并测试您在该练习中找到的每个 12 个素数。由于费马测试具有 - $\Theta(\log n)$ 增长,您会如何预期测试 1,000,000 附近素数的时间与测试 1000 附近素数的时间相比?您的数据是否支持这一点?您能解释发现的任何差异吗? - - - - mod_timed_prime_test_example + (费马方法),并测试你在该练习中找到的12个质数的每一个。由于费马测试具有 + $\Theta(\log n)$的增长, + 你期望测试接近1,000,000的质数所需的时间与测试接近1000的质数所需的时间相比如何?你的数据支持这一点吗?你能解释发现的任何差异吗? + + + + mod_timed_prime_test_example mod_timed_prime_test_solution fast_prime_definition @@ -845,10 +754,10 @@ function report_prime(elapsed_time) { display(elapsed_time); } - - - - + + + + mod_timed_prime_test_example mod_timed_prime_test_solution @@ -862,20 +771,20 @@ timed_prime_test(10007); // timed_prime_test(1000033, 3); // timed_prime_test(1000037, 3); - - - - 使用 fast_is_prime 测试接近 1,000,000 附近素数的时间 - 大约为 4 毫秒,是测试接近 1,000 附近素数时间的 4 倍。对比使用 is_prime 达到的 8 毫秒, - 这是更快的。然而,尽管慢了 4 倍,这一事实不能让我们相信它具有比 $\Theta(\log n)$ 更大的增长率, - 因为这需要通过更大的数字进行测试以获得对函数增长更准确的理解。 + + + + 使用fast_is_prime测试接近1,000,000的质数所需时间约为4毫秒,是测试接近1000的质数所需时间的4倍。这比我们使用is_prime时达到的8毫秒要快得多。然而,尽管它速度比后者慢4倍,这一事实不能让我们认为它的增长超过了 + $\Theta(\log n)$,因为要更准确地理解函数的增长,应该用更大的数进行测试。 - + - - Alyssa P. Hacker 抱怨说我们在编写时做了很多额外的工作 - expmod 。毕竟,她说,既然我们已经知道如何计算指数,我们本可以简单地写 - + + + + Alyssa P. Hacker 抱怨我们在编写 + expmod 时做了很多额外的工作。毕竟,她说,既然我们已经知道如何计算指数,我们本可以简单地写成 + expmod no_extra_work expt_log_definition @@ -889,57 +798,61 @@ function expmod(base, exp, m) { return fast_expt(base, exp) % m; } - - 她说得对吗? - 这个 + + 她说得对吗?这 - 过程 + 过程 函数 - - 能否同样用于我们的快速素数测试器?请解释。 - - - 从表面上看,Alyssa 的建议是正确的:她的 - expmod 函数计算 - $\textit{base}^{\textit{exp}}$,然后根据费马测试要求找到其模 $m$ 的余数。 + + 能否同样用于我们的快速质数测试?请解释。 + + + 表面上看,Alyssa 的建议是正确的:她的 + expmod 函数计算了 + $\textit{base}^{\textit{exp}}$ 然后计算其对 + $m$ 的余数,正是费马测试所需的。

    - 然而,对于较大的底数,Alyssa 的方法很快就会遇到限制,因为 JavaScript 使用 64 位表示数字,遵循双精度浮点标准。当数字变得如此之大以至于无法在此标准下精确表示时,结果变得不可靠。更糟糕的是,该方法可能超出了该标准中可表示的最大数字,导致计算错误。 + 但是,对于较大的底数,Alyssa 的方法很快会遇到限制,因为JavaScript使用64位表示数字,遵循双精度浮点标准。当数字变得太大,无法在该标准中精确表示时,结果将变得不可靠。更糟的是,该方法可能超过该标准能表示的最大数字,计算将导致错误。

    - 然而,对于较小的底数,Alyssa 的方法可能比原始 expmod 函数更快,因为它只会执行一次余数运算。 + 然而,对于较小的底数,Alyssa 的方法可能比原始的 + expmod 函数更快,因为它只执行一次取余操作。
    -
    + + + - - Louis Reasoner 很难完成练习。 + + Louis Reasoner 在做练习 + 时遇到了很大困难。 他的 - - fast-prime? - + + fast-prime? + fast_is_prime 测试似乎比他的 - - prime? - + + prime? + is_prime - 测试运行得更慢。Louis 叫来他的朋友 Eva Lu Ator 来帮忙。当他们检查 Louis的代码时,他们发现他重写了 - expmod - - 过程 + 测试运行得更慢。Louis 叫来了他的朋友 Eva Lu Ator 给予帮助。当他们检查 Louis 的代码时,发现他重写了 + expmod + + 过程 函数 - 使用显式乘法,而不是调用 - square : - + ,使用显式乘法,而不是调用 + square: + expmod even_definition expmod_example @@ -967,47 +880,46 @@ function expmod(base, exp, m) { } - “我看不出这样做有什么区别,” - Louis 说。“但我看出来了。” Eva 说。“通过那样编写 + Louis 说:“我不明白这有什么不同。”Eva 回答:“我明白。通过这样写这个 - 过程 + 过程 函数 , - 你已经将 - $\Theta(\log n)$ 过程转换为 - $\Theta(n)$ 过程。” 请解释。 + 你已经把 + $\Theta(\log n)$ 的过程转变成了 + $\Theta(n)$ 的过程。” 请解释。 - Eva 是对的:通过计算表达式: - + Eva 是对的:在计算过程中, + (expmod(base, exp / 2, m) * expmod(base, exp / 2, m)) % m - + 表达式 expmod(base, exp / 2, m) - 在指数为偶数时的每一步计算中被重复计算,消除了快速幂算法的好处——即在指数为偶数时将指数减半——因此消除了使算法更快的特性。 + 在指数为偶数时的每一步计算中被计算了两次,这消除了快速幂算法的优势——该算法在指数为偶数时会将指数减半——因此消除了算法提速的特性。 - + - - 证明脚注中列出的 + + + + 证明 卡迈克尔数 - 卡迈克尔数确实能欺骗费马测试。也就是说,编写一个 + 注释中列出的卡迈克尔数确实能够欺骗费马测试。也就是说,编写一个 - 过程 + 过程 函数 - , - 接收一个整数 $n$ 并测试对于每一个 $a < n$, - $a^n$ 是否同余于 - $a$ 模 $n$,并在给定的卡迈克尔数上试验您的 + + ,该过程接受一个整数 $n$ 并测试对于所有 $a < n$,是否都有 $a^n$ 同余于 $a$ (模 $n$),并在给定的卡迈克尔数上测试你的 - 过程 + 过程 函数 - - carmichael + + carmichael example_1.29 even_definition square_definition @@ -1032,12 +944,12 @@ function carmichael(n) { } return iter(n, 2); } - - - - + + + + - + example_1.29 carmichael true @@ -1049,68 +961,67 @@ carmichael(561); // carmichael(2821); // carmichael(6601); - - - - + + + + + - 费马测试的一种变体,它不能被欺骗,被称为 - 素数米勒–拉宾测试 + 费马测试中有一种不会被欺骗的变体称为 + 素数米勒拉宾测试 费马素性测试的变体 - 米勒–拉宾素性测试 - 米勒,Gary L. - 拉宾,Michael O. - 米勒–拉宾测试米勒 1976; - 拉宾 1980)。它从 - 费马的费马小定理替代形式 - 费马小定理的一个替代形式开始,该定理指出如果 - $n$ 是素数且 - $a$ 是小于的任意正整数 - $n$ ,那么 $a$ 次幂的 $(n-1)$ 次方同余于 1 模 $n$ 。 要测试一个数字的素性 $n$ 通过米勒–拉宾测试,我们选择一个随机数 $a < n$ 并将其提升为 - $a$$(n-1)$ 次方模 $n$ 使用 - expmod + 米勒拉宾素性测试 + Gary L. 米勒 + Michael O. 拉宾 + 米勒拉宾测试米勒 1976; + 拉宾 1980)。该测试基于 + 费马费马小定理的另一种形式 + 费马小定理的另一种形式,其内容为如果 +$n$ 是一个素数且 $a$ 是小于 的任意正整数 $n$ ,那么 $a$ 提出 + 到 $(n-1)$ 第 st 次幂同余于 1 + 模 $n$ . 测试一个数的素性 $n$ 通过米勒-拉宾测试,我们选择一个随机数 $a < n$ 并提升 $a$$(n-1)$ 幂模 $n$ 使用 expmod - 过程。 + 过程。 函数。 - 然而,每当我们在 - expmod ,我们检查是否发现了一个 - 1 的非平凡平方根模$n$, - 也就是不等于 1 或的一个数字 $n-1$ 其平方等于 1 模 $n$ 。可以证明,如果存在这样一个 1 的非平凡平方根,那么 - $n$ 不是素数。也可以证明如果 $n$ 是一个不是素数的奇数,那么至少对半数的数字 $a < n$ ,计算 $a^{n-1}$ 以这种方式将揭示 1 的非平凡平方根模 $n$ 。 - (这就是米勒–拉宾测试无法被欺骗的原因。)修改 + 但是,每当我们执行平方步骤时 + expmod,我们检查是否发现了一个 + $n$的 + 非平凡平方根,即一个不等于1或 $n-1$ 其平方模 $n$。可以证明,如果存在这样的非平凡的1的平方根,那么$n$ 不是质数。同样可以证明,如果 $n$ 是一个非质数的奇数, + 则对于至少一半的数字 $a < n$ , + 计算 $a^{n-1}$ 以这种方式将揭示一个非平凡的模 1 的平方根 $n$ 。 + (这就是为什么 Miller–Rabin 测试不会被欺骗。) 修改 expmod - 过程 + 过程 函数 - 以发出信号如果它发现 1 的非平凡平方根,并使用它来 - 实现米勒–拉宾测试,通过一个 + 以在发现非平凡的 1 的平方根时发出信号,并利用此来实现具有与 - 过程 + 过程 函数 - 类似于 + 类似的 MillerRabin 测试 - fermat-test. - fermat_test. - - 检查您的 + 费马-测试 + fermat_test + + 通过测试各种已知的素数和合数来检查你的 - 过程 - 函数 - 通过测试各种已知的素数和非素数。提示:一种方便的方法是生成一个 -expmod 信号是让它返回0。 - - - - miller_rabin_test - example_1.30 - even_definition - square_definition - + 过程 + 函数 + + 。提示:一个方便的方法是expmod信号是让它返回 0。 + + + + miller_rabin_test + example_1.30 + even_definition + square_definition + function random(n) { return math_floor(math_random() * n); } @@ -1146,12 +1057,12 @@ function do_miller_rabin_test(n, times) { ? do_miller_rabin_test(n, times - 1) : false; } - - - - - - + + + + + + example_1.30 miller_rabin_test true @@ -1162,5 +1073,5 @@ do_miller_rabin_test(104743, 1000); - 素数测试 + 素数<汉语索引>判定</汉语索引> diff --git a/xml/zh/chapter3/section2/subsection4.xml b/xml/zh/chapter3/section2/subsection4.xml new file mode 100644 index 000000000..60855747b --- /dev/null +++ b/xml/zh/chapter3/section2/subsection4.xml @@ -0,0 +1,609 @@ + + + + + 内部 + + 定义 + 声明 + + + + + + + 块结构 + + 环境 + 在环境模型中 + + + + + + 评估环境模型 + 内部声明 + + + + + + 内部声明 + + 环境 + 在环境模型中 + + + + + + + + + 章节引入了这样一个思想,即 + + 过程 + 函数 + + 可以拥有内部的 + + + 定义, + + + 声明, + + + 从而形成如同在 + sqrt环境环境模型 + 中所见的块结构,下面的 + + 过程 + 函数 + + 用于计算平方根: + + another_sqrt + abs_definition + square_definition + average_definition + sqrt_example7 + 2.2360688956433634 + +(define (sqrt x) + (define (good-enough? guess) + (< (abs (- (square guess) x)) 0.001)) + (define (improve guess) + (average guess (/ x guess))) + (define (sqrt-iter guess) + (if (good-enough? guess) + guess + (sqrt-iter (improve guess)))) + (sqrt-iter 1.0)) + + +function sqrt(x) { + function is_good_enough(guess) { + return abs(square(guess) - x) < 0.001; + } + function improve(guess) { + return average(guess, x / guess); + } + function sqrt_iter(guess){ + return is_good_enough(guess) + ? guess + : sqrt_iter(improve(guess)); + } + return sqrt_iter(1); +} + + + 现在我们可以利用环境模型来理解为何这些内部的 + + + 定义 + + + 声明 + + + 会按照预期运行。 + + + 图 + + + 图 + + + 展示了表达式求值过程中的一个关键点, + + (sqrt 2) + sqrt(2) + + 在这一点上,内部的 + + 过程 + 函数 + + + good-enough? + is_good_enough + + 首次被调用时,guess的值为1. + + + +
    +
    + + 带有内部定义的sqrt过程. + + +
    +
    + +
    +
    + 带有内部声明的sqrt函数. + + +
    +
    +
    +
    + + + + + 观察环境的结构。 + + + Sqrt 是一个在全局环境中被绑定的符号 + + + 名称 sqrt 在程序环境中被绑定 + + + 绑定到一个 + + 过程 + 函数 + + 对象,其关联环境是 + + 全局 + 程序 + + 环境。当调用sqrt时,形成了一个新环境E1,此环境隶属于 + + 全局 + 程序 + + 环境,在其中参数x被绑定到2。随后在E1中求值了sqrt的定义体。 + + + 由于sqrt定义体中的第一个表达式是 + + + 该定义体是一个带有局部函数声明的块,因此E1被扩展以包含一个新的框架来保存这些声明,从而产生了新环境E2。块的定义体随后在E2中被求值。由于块中第一个语句是 + + + + abs_definition + square_definition + +(define (good-enough? guess) + (< (abs (- (square guess) x)) 0.001)) + + +function is_good_enough(guess) { + return abs(square(guess) - x) < 0.001; +} + + + + + 求值该表达式在环境E1中定义了过程good-enough?。 + + + 求值该声明在环境E2中创建了函数is_good_enough。 + + + + + 更准确地说,符号good-enough?被加入到E1的第一个框架中,并绑定到一个其关联环境为E1的过程对象上。 + + + 更准确地说,名称is_good_enough在E2的第一个框架中被绑定到一个其关联环境为E2的函数对象上。 + + + 类似地, + improve 和 + + sqrt-iter + sqrt_iter + + 被分别定义为 + + E1中的过程。 + E2中的函数。 + + 为了简洁, + + + 图 + + + 图 + + + 仅展示了 + 过程 + 函数 + + 对象,即 + + good-enough?. + is_good_enough. + + + + + + + 在局部 + + 程序 + 函数 + + 定义完成后,表达式 + + (sqrt-iter 1.0) + sqrt_@iter(1) + + + 仍在环境 + + E1. + E2. + + 中求值。因此,绑定在 + + sqrt-iter + + sqrt_@iter + + + 中的 + + 过程 + 函数 + + 对象以1作为参数被调用。这创建了一个环境E2,在该环境中, + guess,即 + + sqrt-iter, + sqrt_@iter, + + 的参数被绑定为1。 + + sqrt-iter + 函数 sqrt_@iter + + + 又调用了 + + good-enough? + is_@good_@enough + + ,并以guess的值作为参数, + + + (来自E2)作为good-enough?的参数, + + + (来自E3)作为is_@good_@enough的参数。 + + + 这又建立了另一个环境, + + + E3,其中guess(即good-enough?的参数) + + + E4,其中guess(即is_@good_@enough的参数) + + + 被绑定为1。虽然 + + sqrt-iter + sqrt_@iter + + 和 + + good-enough? + is_@good_@enough + + 均拥有一个名为guess的参数,但它们是位于不同框架中的两个独立的局部变量。 + + + 此外,E2和E3均以E1为其封闭环境,因为sqrt-itergood-enough?过程的环境部分均为E1。 + + + 此外,E3和E4均以E2为其封闭环境,因为sqrt_@iteris_@good_@enough函数的环境部分均为E2。 + + + 一个结果是,出现在 + + good-enough? + is_@good_@enough + + 体内的 + x + (符号或名称)将引用E1中出现的的绑定,也就是原始调用 + sqrt + + 过程 + 函数 + + 时所用的的值。 + sqrt环境在环境模型中 + + + + + + 环境模型因此解释了使局部 + + 过程定义 + 函数声明 + + 成为模块化程序的一种有用技术的两个关键特性: +
      +
    • + 局部 + + 过程 + 函数 + + 的名称不会干扰封闭 + + 过程, + 函数, + + 外部的名称,因为局部 + + 过程 + 函数 + + 的名称将在 + + 过程在运行时创建时, + 块在求值时创建时, + + 的帧中绑定,而不是在 + + 全局 + 程序 + + 环境中绑定。 +
    • +
    • + 局部 + + 过程 + 函数 + + 可以仅仅通过使用参数名称作为自由 + + 变量。 + 名称。 + + 来访问封闭 + + 过程, + 函数, + + 的参数。这是因为局部 + + 过程 + 函数 + + 的主体是在一个从属于封闭 + + 过程, + 函数. + + 的求值环境中求值的。 +
    • +
    +
    + + + + 在章节中,我们看到环境模型如何描述具有局部状态的 + + 过程 + 函数 + + 的行为。现在我们已经看到内部 + + + 定义 + + + 声明 + + + 的工作方式。 + 求值的环境模型消息传递 + 消息传递环境模型与 + 一个典型的消息传递 + + 过程 + 函数 + + 同时包含了这两个方面。请考虑章节中的 + 银行账户 + 银行账户 + + 过程 + 函数 + + : + + + make_accountenvironment在环境模型中 + another_make_account + another_make_account_example + 30 + +(define (make-account balance) + (define (withdraw amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + "Insufficient funds")) + (define (deposit amount) + (set! balance (+ balance amount)) + balance) + (define (dispatch m) + (cond ((eq? m 'withdraw) withdraw) + ((eq? m 'deposit) deposit) + (else (error "Unknown request - - MAKE-ACCOUNT" + m)))) + dispatch) + + +function make_account(balance) { + function withdraw(amount) { + if (balance >= amount) { + balance = balance - amount; + return balance; + } else { + return "Insufficient funds"; + } + } + function deposit(amount) { + balance = balance + amount; + return balance; + } + function dispatch(m) { + return m === "withdraw" + ? withdraw + : m === "deposit" + ? deposit + : error(m, "Unknown request: make_account"); + } + return dispatch; +} + + + 展示由交互序列生成的环境结构 + + another_make_account_example + + (define acc (make-account 50)) + + +const acc = make_account(50); + + +const acc = make_account(50); +acc("withdraw")(20); + + + + another_make_account_example_2 + + ((acc 'deposit) 40) + + + 90 + + +acc("deposit")(40); + + +90 + + + + another_make_account_example_3 + + ((acc 'withdraw) 60) + + + 30 + + +acc("withdraw")(60); + + +30 + + + acc 的局部状态存放在哪里?假设我们定义另一个账户 + + + (define acc2 (make-account 100)) + + +const acc2 = make_account(100); + + + 这两个账户的局部状态是如何保持互不干扰的?accacc2 共享环境结构的哪些部分? + + + + + + 关于块的更多信息 + + + + + 正如我们所见,在sqrt中声明的名称的作用域是整个sqrt函数体。这解释了为什么mutual recursion recursionmutual mutual recursion能够工作,就像这种(相当浪费)检查非负整数是否为偶数的方法一样。 + + f_is_even_is_odd + +function f(x) { + function is_even(n) { + return n === 0 + ? true + : is_odd(n - 1); + } + function is_odd(n) { + return n === 0 + ? false + : is_even(n - 1); + } + return is_even(x); +} + + + 当在调用f的过程中调用is_even时,环境图看起来类似于当调用sqrt_iter时在图中所示的那样。函数is_evenis_odd在E2中被绑定到指向E2的函数对象上,该对象作为调用这些函数的求值环境。因此,在is_even函数体内的is_odd引用的是正确的函数。尽管is_odd是在is_even之后定义的,但这与在sqrt_iter函数体内名称improve和名称sqrt_iter本身正确引用相应函数的情况没有什么不同。 + + + + + 装备了处理块内声明的方法后,我们可以重新审视顶层名称的声明。在部分中,我们看到顶层声明的名称被添加到了程序栈帧中。更好的解释是,整个程序被置于一个隐式块中,并在全局环境中求值。上述对块的处理方式随后适用于顶层:全局环境通过包含隐式块中所有名称绑定的栈帧得到了扩展。该栈帧即为程序栈帧,所得到的环境就是程序环境。 + + + + + 我们说,一个块的主体在包含块体内直接声明的所有名称的环境中求值。一个局部声明的名称在进入块时会被加入到环境中,但不附带任何值。在块体求值过程中对其声明的求值,相当于将=右侧表达式求值的结果赋给该名称,就像该声明是一次赋值操作一样。由于名称被加入环境与对声明的求值是分开的,并且整个块都处于该名称的作用域内,因此错误的程序可能会尝试declarationuse of name before在名称声明求值之前访问该名称的值;对未赋值名称的求值将引发错误。 + + 这解释了为什么第1章中脚注的程序会出错。创建名称绑定与求值之间的这段时间被称为 + temporal dead zone (TDZ) + TDZ (temporal dead zone) + temporal dead zone (TDZ)。 + + + + 求值的环境模型 + 块结构环境在环境模型中 + + +
    diff --git a/xml/zh/chapter3/section3/subsection1.xml b/xml/zh/chapter3/section3/subsection1.xml new file mode 100644 index 000000000..172de9d6d --- /dev/null +++ b/xml/zh/chapter3/section3/subsection1.xml @@ -0,0 +1,1875 @@ + + + 可变列表结构 + + + + 可变数据对象列表结构 + 列表结构可变的 + 可变数据对象 + 对偶可变 + + + 关于 + + + 对偶cons, + + + 对偶pair, + + + + car, + head, + + 和 + + cdr + tail能 + + + 这些基本操作可以用来构造列表结构并从列表结构中选择部分,但它们无法修改列表结构。到目前为止我们使用的列表操作,如appendlist也同样如此,因为它们可以用 + + cons, + pair, + + + car, + head, + + 和 + + cdr. + tail. + + 来定义。要修改列表结构,我们需要新的操作. + + + + 对偶的原始变更器为 + set_head (原始函数) + set_head (\textit{ns}) + + set-car! + set_head + + 和 + set_tail (原始函数) + set_tail (\textit{ns}) + + set-cdr!. + set_tail. + + + Set-car! + 函数 set_head + + + 接受两个参数,其中第一个必须是一个对偶。它修改该对偶,用第二个参数的指针替换 + + car + head + + 指针,此替换操作由 + set-car!set_head执行。 + + + Set-car! 和 + set-cdr! 返回依赖于实现的 + set-car! (原始函数)的值 + 未指定的值set-carset-car! + set-cdr! (原始函数)的值 + 未指定的值set-cdrset-cdr! + 值。类似于 set!,它们应仅用于其副作用。 + + + 函数 set_head 和 + set_tail 返回值 + undefined。 + set_head (原始函数)的值 + set_tail (原始函数)的值 + 它们应仅用于其副作用。 + + + + + + 例如,假设 x 被绑定到 + + + 列表 + ((a b) c d) + + + list(list("a", "b"), "c", "d") + + + 以及 y 被绑定到 + + + 列表 + (e f) + + + list("e", "f") + + + 如下图所示 + + + 图. + + + 图. + + + 求值表达式 + + (set-car! x y) + + set_head(x, y) + + 将修改 x 所绑定的对偶,用 y 的值替换其 + + car + head + + 。操作的结果如图所示 + + + 图. + + + 图. + + + 结构 x 已经被修改,并且 + + + 现在将打印为 + ((ef)cd). + + + 现在等价于 + list(list("e", "f"), "c", "d"). + + + 被替换指针所标识的、代表列表 + + (a b), + list("a", "b"), + + 的对偶现在已与原结构分离。由此,我们可以看出,列表上的变更操作可能会产生不属于任何可访问结构的垃圾。我们将在第节中看到, + Lisp + JavaScript + + 内存管理系统包括一个garbage collectionmutation and garbage collector(垃圾回收器),它能够识别并回收未使用对偶所占用的内存空间。 + + + + +
    +
    + + 列表 x: + ((a b) c d) 和 + y: (e f). + + +
    +
    + +
    +
    + + 列表 x: + list(list("a", "b"), "c", "d") + 和 y: + list("e", "f"). + + +
    +
    +
    + + +
    +
    + + (set-car! x y) 对图中列表的作用. + + +
    +
    + +
    +
    + + set_head(x, y) 对图中列表的作用. + + +
    +
    +
    + + +
    +
    + + (define z (cons y (cdr x))) 对图中列表的作用. + + +
    +
    + +
    +
    + + const z = pair(y, tail(x)); 对图中列表的作用. + + +
    +
    +
    + + +
    +
    + + (set-cdr! x y) 对图中列表的作用. + + +
    +
    + +
    +
    + + set_tail(x, y) 对图中列表的作用. + + +
    +
    +
    + + + + 比较 + + + figure + + + figure + + + 与 + + + figure, + + + figure, + + + 它展示了执行 + + mutable_list_example + +(define x '((a b) c d)) +(define y '(e f)) + + +const x = list(list("a", "b"), "c"); +const y = list("e", "f"); + + + 的结果 + + mutable_list_example + +(define z (cons y (cdr x))) + + +const z = pair(y, tail(x)); + + + ,其中 xy + 分别绑定到原始列表 + + + figure. + + + figure. + + + 该 + + + variable + + + name + + + z 现在绑定到由 + cons + pair + + 操作创建的新对;而绑定到 x 的列表保持不变. + + + + + set-cdr! + set_tail + + 操作类似于 + + set-car!. + set_head. + + 。唯一的区别在于,这对的 + + cdr + tail + + 指针被替换为新的指针,而不是 + + car + head + + 指针。 + 执行 + + (set-cdr! x y) + set_tail(x, y) + + 操作对 + + + figure + + + figure + + + 中的列表所产生的效果显示在 + + + figure. + + + figure. + + + 中。 + 此处, + + cdr + tail + + 指针在绑定到 + x + 的位置被替换为了指向 + + (e f). + list("e", "f"). + + 的指针。 + 此外,原来作为 + + cdr + tail + + 部分的列表 + + (c d), + list("c", "d"), + + ,现在已与整个结构分离。 + + + + + Cons + 函数 pair + + + 通过创建新的对来构造新的列表结构, + + set-car! + 而在 JavaScript 中为 set_@head + + 以及 + + set-cdr! + set_tail + + 用于修改现有的对。 + 实际上,我们可以借助这两个变异器, + pair(原始函数)通过变异器实现 + 来实现 + + cons + pair + + ,再配合一个 + 过程函数 + + get-new-pair, + get_new_pair, + + 该函数返回一个不属于任何现有列表结构的新对。 + 我们获得这个新对后,将它的 + + car + head + + 和 + + cdr + tail + + 指针设置为指向指定的对象,并将这个新对作为 + conspair + 的结果返回。 + + + + get-new-pair + 是 Lisp 实现中内存管理必须实现的操作之一。我们将在 节中讨论这一点。 + + + 节将展示内存管理系统如何实现 get_new_pair。 + + + + + + get_new_pair + + + +// 本书提出了一个原始函数 get_new_pair。 +// 由于 JavaScript 不提供这样的函数,为了示例,我们将其定义如下: + +function get_new_pair() { + return pair(undefined, undefined); +} +{ + + + + pair(原始函数)通过变异器实现 + get_new_pair + mutable_pair_example + [ [ 1, 2 ], 4 ] + +(define (cons x y) + (let ((new (get-new-pair))) + (set-car! new x) + (set-cdr! new y) + new)) + + +function pair(x, y) { + const fresh = get_new_pair(); + set_head(fresh, x); + set_tail(fresh, y); + return fresh; +} + + + + + mutable_pair_example + + + +pair(pair(1, 2), 4); +} + + + + + + + 以下 + + 过程 + 函数 + +用于拼接列表的方法是在章节中引入的: + + append_example3 + 9 + +append(list(1, 3), list(5, 7, 9)); + + +list_ref(append(list(1, 3), list(5, 7, 9)), 4); + + + 追加 + + append2 + append_example3 + 9 + +(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)); +} + + + + 追加 + 函数 append + + + 依次构造一个新列表, + + + cons操作,将 x 的元素添加到 y 上。 + + + 将 x 的元素附加到 y 的前端。 + + + 该 + + 过程 + 函数 + + append追加变异器append_mutator vs. + + append! + append_mutator + + + 类似于 +append,但它是一个变异器而不是一个构造器。它通过将这些列表拼接在一起来追加它们,同时修改最后一对x以使其 + + cdr + tail + + 现在是y . (调用 + + append! + append_mutator + + + 时传入空是错误的x 。) + append_mutator + append_mutator + last_pair + +(define (append! x y) + (set-cdr! (last-pair x) y) + x) + + +function append_mutator(x, y) { + set_tail(last_pair(x), y); + return x; +} + + + 这里 + + 最后一对 + last_pair + + 是一个 + + 过程 + 睡觉 + + 返回其参数中最后一对: + + last_pair_example_2 + +last_pair(list(1, 2, 3, 4, 5)); + + + + last_pair + last_pair + last_pair_example_2 + [ 5, null ] + +(define (last-pair x) + (if (null? (cdr x)) + x + (last-pair (cdr x)))) + + +function last_pair(x) { + return is_null(tail(x)) + ? x + : last_pair(tail(x)); +} + + + 考虑交互 + + append_interaction_1 + append2 + append_mutator + +(define x (list 'a 'b)) + + +const x = list("a", "b"); + + + + append_interaction_2 + append_interaction_1 + +(define y (list 'c 'd)) + + +const y = list("c", "d"); + + + + append_interaction_3 + append_interaction_2 + +(define z (append x y)) + + +const z = append(x, y); + + + + append_interaction_4 + append_interaction_3 + +z + + +(a b c d) + + +z; + + +["a", ["b", ["c", ["d, null]]]] + + + + append_interaction_5 + append_interaction_4 + +(cdr x) + + +<response> + + +tail(x); + + +response + + + + append_interaction_6 + append_interaction_5 + +(define w (append! x y)) + + +const w = append_mutator(x, y); + + + + append_interaction_7 + append_interaction_6 + +w + + +(a b c d) + + +w; + + +["a", ["b", ["c", ["d", null]]]] + + + + append_interaction_8 + append_interaction_7 + +(cdr x) + + +<response> + + +tail(x); + + +response + + + 缺少哪些response? + 请绘制盒子与指针图来解释你的答案。 + + + + + 请考虑以下内容 + 列表中的循环 + + make-cycle + make_cycle + + + 过程, + 函数, + + 它使用了 + + + last-pair + + + last_pair + + + + 过程 + 函数 + + 定义在练习中: + + make_cycle + make_cycle + last_pair + make_cycle_example + +(define (make-cycle x) + (set-cdr! (last-pair x) x) + x) + + +function make_cycle(x) { + set_tail(last_pair(x), x); + return x; +} + + + 绘制一个盒子和指针图,展示由 + z + 创建的结构,该结构由 + + make_cycle_example + make_cycle + 'b' + +(define z (make-cycle (list 'a 'b 'c))) + + +const z = make_cycle(list("a", "b", "c")); + + +const z = make_cycle(list("a", "b", "c")); +list_ref(z, 100); + + + 如果我们尝试计算 + + (last-pair z)? + last_pair(z)? + + + + (由 GitHub 用户 jonathantorres 提供) + 如果我们尝试计算 last_pair(z),程序将进入无限循环,因为列表的末尾指向了开头。 + +
    + +
    +
    +
    + + + + + 以下内容 + + 过程 + 函数 + + 非常有用,尽管有点晦涩: + + mystery + mystery_loop + mystery_loop_example_1 + mystery_loop_example_2 + +(define (mystery x) + (define (loop x y) + (if (null? x) + y + (let ((temp (cdr x))) + (set-cdr! x y) + (loop temp x)))) + (loop x '())) + + +function mystery(x) { + function loop(x, y) { + if (is_null(x)) { + return y; + } else { + const temp = tail(x); + set_tail(x, y); + return loop(temp, x); + } + } + return loop(x, null); +} + + + + Loop + 函数 loop + + + 使用了名为 临时 + + + 变量 + + + 名称 + + + temp + 来保存 + cdr + tail + + 的旧值,因为下一行的 + + set-cdr! + set_tail + + 会破坏 + + cdr. + tail. + + 解释 mystery 的一般功能。假设 + v 被如下定义: + + mystery_loop_example_1 + +(define v (list 'a 'b 'c 'd)) + + +const v = list("a", "b", "c", "d"); + + + 绘制一个盒子和指针图,表示 + v 所绑定的列表。假设我们现在执行 + + mystery_loop_example_2 + mystery_loop + +(define w (mystery v)) + + +const w = mystery(v); + + + 绘制盒子和指针图,显示在执行该 + + 表达式。 + 程序。 + + 后,vw 的结构。将会打印出 + vw 的值。 + + + (由 GitHub 用户 jonathantorres 提供) + 调用 mystery(x) 会就地反转列表 x。 + 初始时, + v 如下所示: + +
    + +
    + + 执行完 + const w = mystery(v); + 后, + v 和 + w 的值变为: + +
    + +
    + + 函数 display + 对 v 打印 ["a", null], + 对 w 打印 ["d", ["c", ["b", ["a", null]]]]。 +
    +
    + + + + 可变数据对象列表结构 + + + 列表结构可变 + + + 可变数据对象 + + + 可变</CLOSE> + + + + + 共享与身份 + + + + + 数据共享 + + + 数据共享 + + + + + 我们在章节中提到了赋值引入所引发的关于 + 同一性与变化共享数据及 + 变化与同一性共享数据及 + 同一性变化 的理论问题。这些问题在实践中出现, + 当单个对在不同的数据对象之间被共享时。例如,考虑由下列构成的结构 + + pair_example1 + +(define x (list 'a 'b)) +(define z1 (cons x x)) + + +const x = list("a", "b"); +const z1 = pair(x, x); + + + 如 + + + figure, + + + figure, + + + 所示,z1 是一对,其中的 + + car + head + + 和 + + cdr + tail + + 均指向同一个x。这种x在 + + car + head + + 和 + + cdr + tail + + 之间的共享,是 + + cons + pair + + + 直接实现方式的必然结果。一般来说,使用 + + cons + pair + + 构造列表会产生一个由许多个体对相互连接构成的结构,其中很多单独的对被多个不同结构共享。 + + +
    +
    + + 列表z1(cons x x)构成。 + + +
    +
    + +
    +
    + + 列表z1pair(x, x)构成。 + + +
    +
    +
    + + +
    +
    + + 列表z2(cons (list 'a 'b) (list 'a 'b))构成。 + + +
    +
    + +
    +
    + + 列表z2pair(list("a", "b"), list("a", "b"))构成。 + + +
    +
    +
    +
    + + + + + 相对于 + + + figure, + figure + + + figure, + figure + + + 所展示的结构是由 + + pair_example2 + +(define z2 (cons (list 'a 'b) (list 'a 'b))) + + +const z2 = pair(list("a", "b"), list("a", "b")); + + + 创建的。在该结构中,这两个 + + (a b) + list("a", "b") + + + 列表中的对是不同的,尽管 + + + 实际的符号是共享的。 + 这两个对之所以不同,是因为每次调用 + cons都会返回一个新的对。符号是共享的;在 Scheme 中, + symbol(s)uniqueness of + 表示具有任意给定名称的唯一符号。由于 Scheme 不提供修改符号的方式,所以这种共享是无法被检测的。另请注意,正是这种共享使我们能够使用 eq? 来比较符号,该函数仅仅检查指针是否相等。 + + + 它们包含相同的字符串。这两个对之所以不同,是因为每次调用 pair + 都会返回一个新的对。字符串在 + string(s)uniqueness of + 的意义上是“相同的”,也就是说,它们作为原始数据(就像数字一样)由相同的字符以相同的顺序组成。由于 JavaScript 不提供修改字符串的方式,因此任何 JavaScript 解释器的设计者可能为字符串实现的共享都是无法检测到的。我们认为,诸如数字、布尔值和字符串等原始数据只有在它们不可区分时才被视为相同的 + + + + + + + 当看作一个列表时, +z1 和 + z2 两者都表示 相同的 列表: + + abab + [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +((a b) a b) + + +list(list("a", "b"), "a", "b") + + +list(list("a", "b"), "a", "b"); + + 通常,如果我们只用 + + cons, + pair, + + + car, + head, + +以及 + + cdr. + tail. + +对列表进行操作,那么共享在总体上是完全无法检测到的。然而,若我们允许对列表结构进行修改操作,共享就变得显著起来。作为共享可能造成区别的一个例子,请考虑下面这个 + + procedure, + function, + +它修改了应用它所作用的结构的 + + car + head + +。 + set_to_wow + +(define (set-to-wow! x) + (set-car! (car x) 'wow) + x) + + +function set_to_wow(x) { + set_head(head(x), "wow"); + return x; +} + + 尽管 z1 和 + z2相同的结构, + 应用 + + set-to-wow! + set_to_wow + + 对它们将产生不同的结果. 使用 z1 , 改变 + + car + head + + 也改变 + + cdr, + tail, + + 因为在 z1 这个 + + car + head + + 和这个 + + cdr + tail + + 构成相同的对。 带有 z2 ,这个 + + car + head + + 和 + + cdr + tail + + 是不同的,因此 + + set-to-wow! + set_to_wow + + 仅修改 + + car: + head: + + + set_to_wow_example_1 + pair_example1 + [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +z1 + + + ((a b) a b) + + +z1; + + +[["a", ["b", null]], ["a", ["b", null]]] + + + + set_to_wow_example_2 + set_to_wow + pair_example1 + [ [ 'wow', [ 'b', null ] ], [ 'wow', [ 'b', null ] ] ] + +(set-to-wow! z1) + + +((wow b) wow b) + + +set_to_wow(z1); + + +[["wow", ["b", null]], ["wow", ["b", null]]] + + + + set_to_wow_example_3 + pair_example2 + [ [ 'a', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +z2 + + +((a b) a b) + + +z2; + + > +[["a", ["b", null]], ["a", ["b", null]]] + + + + set_to_wow_example_4 + set_to_wow + pair_example2 + [ [ 'wow', [ 'b', null ] ], [ 'a', [ 'b', null ] ] ] + +(set-to-wow! z2) + + +((wow b) a b) + + +set_to_wow(z2); + + +[["wow", ["b", null]], ["a", ["b", null]]] + + + + + + + + + 检测列表结构共享的一种方法是使用谓词 eq?,我们在章节中介绍了这一方法,用于测试两个符号是否相等。更一般地,(eq? x y)测试的是xy是否为同一对象(也就是说,测试xy在指针意义上是否相等)。 + + + 检测列表结构共享的一种方法是使用===equalityas equality of pointers + ===generalas general comparison operator + ===(用于非原始值) + 原始谓词 ===,我们在章节中介绍了此谓词来测试两个数字是否相等,并在章节中扩展到测试两个字符串是否相等。当对两个非原始值应用时,x === y测试的是xy是否为同一对象(也就是说,测试xy在指针意义上是否相等)。 + + + 因此,按照下列方式定义的 z1z2: + + + 图, + + + 图, + + + + + (eq? (carz1) (cdrz1)) + + + head(z1) === tail(z1) + + + 为真,而 + + + (eq? (car z2) (cdr z2)) + + + head(z2) === tail(z2) + + + 为假。 + + + + + + 正如接下来的各节中所展示的那样,我们可以利用共享来大幅扩展由对构成的数据结构的种类。另一方面,共享也可能使 + 可变数据对象共享数据 + 变得危险,因为对结构所做的修改同样会影响那些恰好共享被修改部分的其他结构。变更操作 + + set-car! + set_head + + 和 + + set-cdr! + set_tail + + 应谨慎使用;除非我们对数据对象的共享方式有充分了解,否则变更可能会产生意想不到的结果。处理可变数据对象共享的微妙问题反映了在第节中提出的samenesschange的根本问题。我们在该节中提到,允许语言中发生变更要求复合对象必须具有一种与其组成部分不同的identity。在 + + Lisp, + JavaScript, + + 中,我们认为这种identity正是通过 + + eq?, + ===, + + 即通过指针相等性来测试的。由于在大多数 + + Lisp + JavaScript + + 实现中,一个指针本质上是一个内存地址,因此我们规定数据对象itself是存储在计算机中某一特定内存位置集合中的信息,从而“解决了问题”。这对于简单的 + + Lisp + JavaScript + + 程序来说已足够,但这决不是一种解决计算模型中sameness问题的通用方法。 + + + + + + + 绘制盒子与指针图,以解释 + + set-to-wow! + set_to_wow + + 对上述结构z1z2的影响。 + + + + + + + + + 本·比特迪德尔决定编写一个 + + 过程 + 函数 + + 用于计算任何列表结构中对的数量。 + 它很简单,他推理道。 在任何结构中,对的数量等于在 + + car + head + + 中的数量,加上在 + + cdr + tail + + 中的数量,再加上一个以计算当前这一对。 因此本·比特迪德尔编写了如下 + + 过程: + 函数 + + + count_pairs + count_pairs + +(define (count-pairs x) + (if (not (pair? x)) + 0 + (+ (count-pairs (car x)) + (count-pairs (cdr x)) + 1))) + + +function count_pairs(x) { + return ! is_pair(x) + ? 0 + : count_pairs(head(x)) + + count_pairs(tail(x)) + + 1; +} + + + 证明这个 + + 过程 + 函数 + + 不正确。特别是,绘制由正好三个对构成的列表结构的框和指针图,对于这些结构,本·比特迪德尔的 + + 过程 + 函数 + + 将分别返回 3;返回 4;返回 7;以及根本不返回任何值。 + + + + exercise_3_16_solution + count_pairs + exercise_3_16_solution_example + + + +const three_list = list("a", "b", "c"); +const one = pair("d", "e"); +const two = pair(one, one); +const four_list = pair(two, "f"); +const seven_list = pair(two, two); +const cycle = list("g", "h", "i"); +set_tail(tail(tail(cycle)), cycle); + + + + + exercise_3_16_solution_example + +// return 3; return 4; return 7; +display(count_pairs(three_list)); +display(count_pairs(four_list)); +display(count_pairs(seven_list)); + +// never return at all +display(count_pairs(cycle)); + + + + + + + + + + 设计一个正确版本的 + + count-pairs + count_pairs + + + 过程 + 函数 + + ,用于返回任意结构中不同对的数量。(提示:遍历该结构,同时维护一个辅助数据结构,用于记录哪些对已经被计数。) + + + exercise_3_17_solution_example + +// GitHub 用户 clean99 提供的解决方案 + +function count_pairs(x) { + let counted_pairs = null; + function is_counted_pair(current_counted_pairs, x) { + return is_null(current_counted_pairs) + ? false + : head(current_counted_pairs) === x + ? true + : is_counted_pair(tail(current_counted_pairs), x); + } + function count(x) { + if(! is_pair(x) || is_counted_pair(counted_pairs, x)) { + return 0; + } else { + counted_pairs = pair(x, counted_pairs); + return count(head(x)) + + count(tail(x)) + + 1; + } + } + return count(x); +} + + + + exercise_3_17_solution_example + +const three_list = list("a", "b", "c"); +const one = pair("d", "e"); +const two = pair(one, one); +const four_list = pair(two, "f"); +const seven_list = pair(two, two); +const cycle = list("g", "h", "i"); +set_tail(tail(tail(cycle)), cycle); + +// 返回 3;返回 3;返回 3; +display(count_pairs(three_list)); +display(count_pairs(four_list)); +display(count_pairs(seven_list)); + +// 返回 3 +display(count_pairs(cycle)); + + + + + + + + Write a + + procedure + function + + that examines a list and + cycle in listdetecting + determines whether it contains a cycle, that is, + whether a program that tried to find the end of the list by taking + successive + + cdrs + tails + + would go into an infinite loop. Exercise + constructed such lists. + + + + exercise_3_18_solution_example + +// solution provided by GitHub user clean99 + +function contains_cycle(x) { + let counted_pairs = null; + function is_counted_pair(counted_pairs, x) { + return is_null(counted_pairs) + ? false + : head(counted_pairs) === x + ? true + : is_counted_pair(tail(counted_pairs), x); + } + function detect_cycle(x) { + if (is_null(x)) { + return false; + } else if (is_counted_pair(counted_pairs, x)) { + return true; + } else { + counted_pairs = pair(x, counted_pairs); + return detect_cycle(tail(x)); + } + } + return detect_cycle(x); +} + + + + exercise_3_18_solution_example + +const three_list = list("a", "b", "c"); +const cycle = list("g", "h", "i"); +set_tail(tail(tail(cycle)), cycle); + +// displays false +display(contains_cycle(three_list)); + +// displays true +display(contains_cycle(cycle)); + + + + + + + + Redo exercise using an algorithm that + takes only a constant amount of space. (This requires a very clever idea.) + + pair2_example + +const x = pair(1, 2); +set_head(x, 3); +head(x); + + + + Define a fast pointer and a slow pointer. The fast pointer goes forward + 2 steps every time, while the slow pointer goes forward 1 step every time. + If there is a cycle in the list, the fast pointer will eventually catch + up with the slow pointer. + + contains_cycle_example + make_cycle + +const c = make_cycle(list("c", "d", "e")); +const c1 = append(list("a", "b"), c); + +contains_cycle(c1); + + + + contains_cycle + contains_cycle_example + +function contains_cycle(x) { + function detect_cycle(fast, slow) { + return is_null(fast) || is_null(tail(fast)) + ? false + : fast === slow + ? true + : detect_cycle(tail(tail(fast)), tail(slow)); + } + return detect_cycle(tail(x), x); +} + + + + + + datashared + shared data + + + Mutation is just assignment + + + mutable data objectsfunctional representation of + mutable data objectsimplemented with assignment + pair(s)functional representation of + functional representation of datamutable data + + + When we introduced compound data, we observed in + section that pairs can be represented purely + in terms of + + procedures: + functions: + + + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + cons_1_2_run + 1 + +(define (cons x y) + (define (dispatch m) + (cond ((eq? m 'car) x) + ((eq? m 'cdr) y) + (else (error "Undefined operation - - CONS" m)))) + dispatch) + +(define (car z) (z 'car)) + +(define (cdr z) (z 'cdr)) + + +function pair(x, y) { + function dispatch(m) { + return m === "head" + ? x + : m === "tail" + ? y + : error(m, "undefined operation -- pair"); + } + return dispatch; +} + +function head(z) { return z("head"); } + +function tail(z) { return z("tail"); } + + + The same observation is true for mutable data. We can implement + mutable data objects as + + procedures + functions + + using assignment and local state. For instance, we can extend the above + pair implementation to handle + + set-car! + set_head + + and + + set-cdr! + set_tail + + in a manner analogous to the way we implemented bank accounts using + + make-account + make_account + + in section: + + cons_1_2_run_3 + +const x = pair(1, 2); +set_head(x, 3); +head(x); + + + + pair (primitive function)functional implementation of + head (primitive function)functional implementation of + tail (primitive function)functional implementation of + set_head (primitive function)functional implementation of + set_tail (primitive function)functional implementation of + pair2 + cons_1_2_run_3 + 3 + +(define (cons x y) + (define (set-x! v) (set! x v)) + (define (set-y! v) (set! y v)) + (define (dispatch m) + (cond ((eq? m 'car) x) + ((eq? m 'cdr) y) + ((eq? m 'set-car!) set-x!) + ((eq? m 'set-cdr!) set-y!) + (else (error "Undefined operation - - CONS" m)))) + dispatch) + +(define (car z) (z 'car)) + +(define (cdr z) (z 'cdr)) + +(define (set-car! z new-value) + ((z 'set-car!) new-value) + z) + +(define (set-cdr! z new-value) + ((z 'set-cdr!) new-value) + z) + + +function pair(x, y) { + function set_x(v) { x = v; } + function set_y(v) { y = v; } + return m => m === "head" + ? x + : m === "tail" + ? y + : m === "set_head" + ? set_x + : m === "set_tail" + ? set_y + : error(m, "undefined operation -- pair"); +} + +function head(z) { return z("head"); } + +function tail(z) { return z("tail"); } + +function set_head(z, new_value) { + z("set_head")(new_value); + return z; +} +function set_tail(z, new_value) { + z("set_tail")(new_value); + return z; +} + + + + + + Assignment is all that is needed, theoretically, to account for the + behavior of mutable data. As soon as we admit + + set! + assignment + + to our language, we raise all the issues, not only of assignment, but of + mutable data in general.On the other hand, from the viewpoint of + implementation, assignment requires us to modify the environment, which is + itself a mutable data structure. Thus, assignment and mutation are + equipotent: Each can be implemented in terms of the other. + + + + Draw environment diagrams to illustrate the evaluation of the sequence + of + + + expressions + + + statements + + + + pair2_example1 + pair2 + +(define x (cons 1 2)) +(define z (cons x x)) +(set-car! (cdr z) 17) + +(car x) + + +const x = pair(1, 2); +const z = pair(x, x); +set_head(tail(z), 17); + + + + pair2_example2 + pair2_example1 + 17 + +(car x) + + + 17 + + +head(x); + + +17 + + + using the + + + procedural + + + functional + + + implementation of pairs given above. (Compare + exercise.) + + + + mutable data objectsfunctional representation of + mutable data objectsimplemented with assignment + pair(s)functional representation of + functional representation of datamutable data + mutable data objects +
    + diff --git a/xml/zh/chapter3/section3/subsection3.xml b/xml/zh/chapter3/section3/subsection3.xml new file mode 100644 index 000000000..297d57f30 --- /dev/null +++ b/xml/zh/chapter3/section3/subsection3.xml @@ -0,0 +1,1065 @@ + + + 表示表格 + + + + + 表格 + + + + + + + 我们首先考虑一个 + 一维 + 一维表,其中每个值都存储在一个单一的键下。我们将该表实现为记录列表,每条记录都实现为由一个键和其关联值构成的二元组。记录通过一对对的 + + cars + heads + + 链接在一起,指向连续的记录。这些连接对被称为 + 骨干 + 骨干。为了在向表中添加新记录时能够有可变更的位置,我们将该表构建为 + 带头列表 + 列表带头 + 带头列表。带头列表在开头有一个特殊的骨干对,其中存放了一个虚拟的记录——在这种情况下,即任意选定的 + + 符号 *table* + 字符串 "*table*" + + + + Figure + + + Figure + + + 展示了该表的盒与指针图 + + +a: 1 +b: 2 +c: 3 + + +a: 1 +b: 2 +c: 3 + + + + +
    +
    + 以带头列表表示的表。 + +
    +
    + +
    +
    + 以带头列表表示的表。 + +
    +
    + </SPLIT> +
    + + + 为了从表中提取信息,我们使用 + lookup + + 过程, + 函数, + + 它以一个键作为参数并返回关联的值(或者 + + false + undefined + + 如果该键下未存储值)。 + + Lookup + 函数 lookup + + + 是基于 + assoc + 操作定义的,该操作期望一个键和一个记录列表作为参数。注意, + assoc + 从不处理虚拟记录。 + + Assoc + 函数 assoc + + + 返回具有给定键作为其 + carhead + 的记录。 + + + 因为 + assoc 使用 + equal?,它能够识别符号、数字或列表结构作为键。 + + + 因为 assoc 使用 + equal,它能够识别作为字符串、数字或列表结构的键。<!-- + --><!-- + --> + + Lookup + 函数 lookup + + + 然后检查由 + assoc + 返回的记录是否不是 + + false, + undefined, + + 并返回该记录的值(即 + + cdr) + tail) + + 。 + + + lookup1_example + make_table1 + insert_into_table1 + +const t = make_table(); +insert("a", 10, t); +lookup("a", t); + + + + lookup在一维表中 + assoc + 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)); +} + + + + + + 为了在表中指定的键下插入一个值,我们首先使用 + assoc + 来检查表中是否已经存在该键对应的记录。如果不存在,则我们通过 + + consing + pairing + + 将该键与值组成一个新的记录,并在伪记录之后,将此记录插入到表头的记录列表中。如果该键对应的记录已存在,则我们将该记录的 + + cdr + tail + + 设为指定的新值。表头为我们提供了一个固定的位置,用于插入新记录的修改。因此,第一个骨干对就是代表表 + 本身的对象;也就是说,指向表的指针实际上就是指向该对的指针。这个骨干对始终作为表的起始,如果不这样安排, + + insert! + insert + + 在添加新记录时就必须返回一个新的起始值。 + + insertin one-dimensional table + 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"; +} + + + + + + 要构造一个新的表,我们只需创建一个包含以下内容的列表: + + + 符号 *table*: + + + 只是字符串 "*table*": + + + + make_table一维表 + make_table1 + +(define (make-table) + (list '*table*)) + + +function make_table() { + return list("*table*"); +} + + + table一维 + + + + 二维表 + + + 二维表二维 + + + +在二维表中,每个值由两个键索引。我们可以将这样的表构建为一维表,其中每个键标识一个子表。 + + + Figure + + + Figure + + +展示了该表的框图与指针图 + + +math: + +: 43 + -: 45 + *: 42 +letters: + a: 97 + b: 98 + + +"math": + "+": 43 + "-": 45 + "*": 42 +"letters": + "a": 97 + "b": 98 + + +该表有两个子表。(子表不需要专用的标题 + + + symbol, + + + string, + + +因为标识子表的键已起到此作用。) + + +
    +
    + 二维表。 + +
    +
    + +
    +
    + 二维表。 + +
    +
    +
    +
    + + +当我们查找一个条目时,我们使用第一个键来确定正确的子表。然后,我们使用第二个键来确定子表中的记录。 + + 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)); +} + + + + lookupin two-dimensional table + 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); + } +} + + + + + +为了在一对键下插入一个新项,我们使用assoc</SCHEMEINLINE>来检查第一键下是否存有子表。如果没有,我们构建一个包含单个记录的新子表 + + (key-2, + (key_2, + +value),并将其插入到第一键对应的表中。如果第一键下已存在子表,我们便把新记录插入该子表中,使用上述一维表的插入方法: + + insertin two-dimensional table + 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"; +} + + + tabletwo-dimensional + + + + + 创建本地表 + + + + + 本地 + + + + + 上面定义的查找和 + + 插入! + 插入 + + 操作以表作为参数。这使我们能够使用访问多个表的程序。另一种处理多个表的方法是为每个表提供单独的查找和 + + 插入! + 插入 + + + 过程 + 函数 + + 。我们可以通过过程化地表示表来实现这一点,即将表表示为一个在其局部状态中维护内部表的对象。当发送适当消息时,这个表对象提供用于操作内部表的 + + 过程 + 函数 + + 。下面是以这种方式表示的二维表生成器: + + make_table消息传递实现 + 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; +} + + + + + + + + 使用 + + make-table, + make_table, + + 我们可以通过 + operation-and-type tableimplementing + 来实现用于数据定向编程中(参见节)所用的 + get 和 + put 操作,方法如下: + + operation_table_example + +put("a", "b", 10); +get("a", "b"); + + + + get + put + operation_table + make_table2 + operation_table_example + 10 + + operation-and-type tableimplementing +(define operation-table (make-table)) +(define get (operation-table 'lookup-proc)) +(define put (operation-table 'insert-proc!)) + + +const operation_table = make_table(); +const get = operation_table("lookup"); +const put = operation_table("insert"); + + + + Get + 函数 get + + + 接受两个键作为参数,而 put 接受两个键和一个值作为参数。两者都访问相同的局部表,该局部表封装于调用 + + make-table. + make_table. + + 创建的对象中。 + tablelocal + + + + + + 在上述表实现中,关键字使用 + tabletesting equality of keys + key of a recordtesting equality of + 通过 + + equal? + equal + + (由 assoc 调用)来测试相等性。这并不总是合适的测试方法。例如,我们可能有一个数字作为关键字的表,在这种情况下,我们不需要与所查找的数字完全匹配,而只需在一定容差范围内。请设计一个表构造器 + + make-table + make_table + + ,该构造器接受一个 + + same-key? + same_key + + + procedure + function + + 作为参数,用来测试关键字的相等性。 + + Make-table + 函数 make_table + + 应返回一个 dispatch + + procedure + function + + ,该函数可用于访问本地表中适当的 lookup 和 + + insert! + insert + + + proceduresfunctions + + 。 + + + + +// GitHub 用户 clean99 的解法 + +function make_table(same_key) { + const local_table = list("*table*"); + function assoc(key, records) { + return is_null(records) + ? undefined + : same_key(key, head(head(records))) + ? head(records) + : assoc(key, tail(records)); + } + function lookup(key) { + const record = assoc(key, tail(local_table)); + return is_undefined(record) + ? undefined + : tail(record); + } + function insert(key, value) { + const record = assoc(key, tail(local_table)); + if (is_undefined(record)) { + set_tail(local_table, + pair(pair(key, value), tail(local_table))); + } else { + set_tail(record, value); + } + return "ok"; + } + function dispatch(m) { + return m === "lookup" + ? lookup + : m === "insert" + ? insert + : error(m, "unknow operation -- table"); + } + return dispatch; +} + +const operation_table = make_table((a, b) => a === b); +const get = operation_table("lookup"); +const put = operation_table("insert"); + + + + + + + + + 概括一维和二维表,展示如何实现一个表,其中的值存储在 tablen$n$-维 的任意数量的键下,并且可以在不同数量的键下存储不同的值。lookup 和 + + insert! + insert + + + procedures + functions + + 应以一个键列表作为输入,用于访问该表。 + + + + solution_3_25 + solution_3_25_example + +// 由 GitHub 用户 tttinkl 提供 + +function assoc(key, records, same_key) { + return is_null(records) + ? undefined + : same_key(key, head(head(records))) + ? head(records) + : assoc(key, tail(records), same_key); +} + + +function make_table(same_key) { + const local_table = list(""*table""); + + const get_value = tail; + + function is_table(t) { + return is_pair(t) && head(t) === ""*table""; + } + + function lookup(keys) { + function lookup_generic(keys, table) { + if (is_null(keys)) { + return table; + } + const key_1 = head(keys); + const key_rest = tail(keys); + const record = assoc(key_1, tail(table), same_key); + if (is_undefined(record)) { + return undefined; + } + if (is_null(key_rest)) { + return get_value(record); + } else if (is_table(get_value(record))) { + return lookup_generic(key_rest, get_value(record)); + } else { + error('无效的键'); + } + } + return lookup_generic(keys, local_table); + } + + + function insert(keys, value) { + function insert_generic(keys, value, table) { + const key_1 = head(keys); + const key_rest = tail(keys); + const record = assoc(key_1, tail(table), same_key); + if (is_undefined(record)) { + if (is_null(key_rest)) { + set_tail( + table, + pair(pair(key_1, value), tail(table))); + } else { + const new_subtable = list(""*table""); + set_tail( + table, + pair(pair(key_1, new_subtable), tail(table)) + ); + insert_generic(key_rest, value, new_subtable); + } + } else { + if (is_null(key_rest)) { + set_tail(record, value); + } else { + if (is_table(get_value(record))) { + insert_generic(key_rest, value, get_value(record)); + } else { + const new_subtable = list(""*table""); + set_tail(record, new_subtable); + insert_generic(key_rest, value, new_subtable); + } + } + } + } + insert_generic(keys, value, local_table); + } + + function dispatch(m) { + return m === "lookup" + ? lookup + : m === "insert" + ? insert + : m === "show" + ? () => { + display(local_table); + return local_table; + } + : error(m, "未知操作 -- 表"); + } + return dispatch; +} + +const table = make_table(equal); + +const get = table('lookup'); +const put = table('insert'); +const show = table('show'); + + + + solution_3_25_example + +put(list("a"), 1); +put(list("b", "c"), 2); +put(list("d", "e", "f"), 3); + +display(get(list("a"))); +display(get(list("b", "c"))); +display(get(list("d", "e", "f"))); + +put(list("a", "b"), 1); +display(get(list("a"))); +put(list("b", "c", "d"), 2); +display(get(list("b", "c"))); +put(list("b"), 1); +display(get(list("b"))); + + + + + + + + + 要搜索上述实现的表,需要扫描记录列表。这基本上是中无序列表的表示。对于较大的表,采用不同的结构实现可能更高效。请描述一种表的实现方法,其中(key, value)记录是使用 + 二叉树表的结构作为 + 二叉树以二叉树与无序列表表示的表 + 来组织的,假设键可以以某种方式排序(例如按数值或字母顺序)。(参见第章的习题。) + + + + ex_3_26_solution + ex_3_26_solution_example + +// 由 GitHub 用户 devinryu 提供 + +function entry(tree) { return head(tree); } +function left_branch(tree) { return head(tail(tree)); } +function right_branch(tree) { return head(tail(tail(tree))); } +function make_tree(entry, left, right) { + return list(entry, left, right); +} + +// kv 是 list(key, value) +function adjoin_set(kv, set) { + return is_null(set) + ? make_tree(kv, null, null) + : head(kv) === head(entry(set)) + ? set + : head(kv) < head(entry(set)) + ? make_tree(entry(set), + adjoin_set(kv, left_branch(set)), + right_branch(set)) + : make_tree(entry(set), + left_branch(set), + adjoin_set(kv, right_branch(set))); +} + +function make_table() { + let local_table = null; + function lookup(given_key, tree_of_records) { + if (is_null(tree_of_records)) { + return null; + } else { + const this_entry = entry(tree_of_records); + const this_key = head(this_entry); + return given_key === this_key + ? this_entry + : given_key < this_key + ? lookup(given_key, + left_branch(tree_of_records)) + : lookup(given_key, + right_branch(tree_of_records)); + } + } + function insert(k, v) { + let record = lookup(k, local_table); + if(is_null(record)) { + local_table = adjoin_set(list(k, v), local_table); + } else { + // 不做处理 + } + } + function get(k) { + return head(tail(lookup(k, local_table))); + } + function print() { + return display(local_table); + } + function dispatch(m) { + return m === "lookup" + ? get + : m === "insert" + ? insert + : m === "print" + ? print + : error(m, "error"); + } + return dispatch; +} + + + + ex_3_26_solution_example + +const t = make_table(); +const get = t("lookup"); +const put = t("insert"); +const print = t("print"); + +// 测试结果 + +put(3, "d"); +put(1, "a"); +put(2, "b"); +put(2, "c"); +put(4, "e"); +put(5, "f"); + +print(); + +display(get(2)); // 显示: "b" + + + + + + + + + 记忆化 + 记忆化 + 表格化 + 用于存储计算后的值 + 已记忆化 + (也叫表格化) 是一种技术,它使得一个 + + 过程 + 函数 + + 能够在局部表中记录先前计算过的值。这项技术能够显著提升程序的性能。一个已记忆化的 + + 过程 + 函数 + + 维护着一个表,其中存储了之前调用的值,并且使用产生这些值的参数作为键。当已记忆化的 + + 过程 + 函数 + + 被要求计算一个值时,它首先会检查表中是否已经存在该值;如果存在,则直接返回该值。否则,它就以常规方式计算新值并将其存入表中。作为记忆化的一个例子,请回忆第节中用于计算斐波那契数的指数级过程: + + fib_example + 8 + +(define (fib n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else (+ (fib (- n 1)) + (fib (- n 2)))))) + + +function fib(n) { + return n === 0 + ? 0 + : n === 1 + ? 1 + : fib(n - 1) + fib(n - 2); +} + + + 同样的已记忆化 + + 过程 + 函数 + + 是 + + memorize_example + +memo_fib(5); + + + + fibwith memoization + memo_fib + memo_fib + memorize + memorize_example + 5 + +(define memo-fib + (memoize (lambda (n) + (cond ((= n 0) 0) + ((= n 1) 1) + (else (+ (memo-fib (- n 1)) + (memo-fib (- n 2)))))))) + + +const memo_fib = memoize(n => n === 0 + ? 0 + : n === 1 + ? 1 + : memo_fib(n - 1) + + memo_fib(n - 2) + ); + + + 其中,memoizer 被定义为 + + memoize + make_table1 + lookup1 + insert_into_table1 + memorize + +(define (memoize f) + (let ((table (make-table))) + (lambda (x) + (let ((previously-computed-result (lookup x table))) + (or previously-computed-result + (let ((result (f x))) + (insert! x result table) + result)))))) + + +function memoize(f) { + const table = make_table(); + return x => { + const previously_computed_result = + lookup(x, table); + if (is_undefined(previously_computed_result)) { + const result = f(x); + insert(x, result, table); + return result; + } else { + return previously_computed_result; + } + }; +} + + + 画一个环境图以分析 + + (memo-fib 3) + memo_fib(3) + + 解释为什么 + + memo-fib + memo_fib + + 计算 +$n$求第 n 个斐波那契数所需的步骤数与 n 成正比$n$。如果我们只是简单地将 + + memo-fib + memo_fib + + 定义为 + + (memoize fib)? + memoize(fib)? + + ,这个方案依然有效吗? + + 表格 +
    diff --git a/xml/zh/chapter3/section3/subsection4.xml b/xml/zh/chapter3/section3/subsection4.xml new file mode 100644 index 000000000..2faf0d90d --- /dev/null +++ b/xml/zh/chapter3/section3/subsection4.xml @@ -0,0 +1,1881 @@ + + + + + 数字电路仿真 + + + 设计复杂的数字系统,例如计算机,是一项重要的工程活动。 数字系统是通过互联简单元件构成的。 尽管这些单个元件的行为很简单,但由它们构成的网络可能表现出非常复杂的行为。 对拟议电路设计的计算机仿真是数字系统工程师使用的重要工具。 在本节中,我们设计一个用于执行数字逻辑仿真的系统。 该系统代表了一类被称为 + 事件驱动仿真 + 仿真事件驱动 + 事件驱动仿真 的程序,其中动作 (事件) 会触发后续在稍后发生的事件,而这些事件又触发更多事件,依此类推。 + + + + 我们的电路计算模型将由对应于构成电路的基本组件的对象组成。 存在 + wire, in digital circuit + wires,它们传输 + signal, digital + digital signal + digital signals。 一个数字信号在任何时刻只能取两个可能的值,0 和 1。 还有各种类型的数字 + function box, in digital circuit + function boxes,它们将传递输入信号的导线连接到其他输出导线上。 这些功能块根据其输入信号计算产生输出信号。 输出信号经过 + delaydelay, in digital circuit + 延迟一段时间,该时间取决于功能块的类型。 例如,一个 + inverter + inverter 是一个原始功能块,用于反转其输入。 如果反相器的输入信号变为 0,则在一个 inverter-delay 之后,反相器会将其输出信号变为 1。 如果反相器的输入信号变为 1,则在一个 inverter-delay 之后,反相器会将其输出信号变为 0。 我们在图中符号化地绘制了反相器,如图所示。 一个 + and-gate + and-gate,也如图所示,是一个具有两个输入和一个输出的原始功能块。 它将其输出信号驱动到一个值,该值为输入信号的 + logical and (digital logic) + logical and。 也就是说,如果它的两个输入信号均变为1,则在一个 and-gate-delay 时间之后,与门会将其输出信号设为 1;否则,输出为 0。 一个 + or-gate + or-gate 是一个类似的两输入原始功能块,其输出信号驱动到一个值,该值为输入信号的 + logical or (digital logic) + logical or。 也就是说,如果至少有一个输入信号为 1,则输出将变为 1;否则,输出将变为0。 + + +
    +
    + 数字逻辑模拟器中的原始功能块。 + +
    + + + 我们可以将原始功能块连接在一起,以构造更复杂的功能。为了实现这一点,我们将一些功能块的输出连接到其他功能块的输入端。例如,图中所示的半加器 + 加法器 + 半加器电路由一个或门、两个与门和一个反相器构成。它接受两个输入信号,$A$$B$,并产生两个输出信号,$S$$C$。当且仅当$A$$B$中恰有一个为1时,$S$将变为1,而当$A$$B$均为1时,$C$将变为1。从图中可以看出,由于涉及延迟,输出可能在不同时间产生。数字电路设计中的许多难点正源于这一事实。 +
    +
    + 半加器电路。 + + +
    +
    + + + 我们现在将构建一个程序,用于建模我们希望研究的数字逻辑电路。该程序将构造模拟导线的计算对象,这些导线将保存信号。功能块将由 + + 过程 + 函数 + + 模拟,以确保信号之间关系的正确性。 + + + + 我们模拟中的一个基本元素将是一个 + + 过程 + 函数 + + make_wire + ,用于构造导线。例如,我们可以如下构造六根导线: + + make_wire_usage + make_wire + +(define a (make-wire)) +(define b (make-wire)) +(define c (make-wire)) +(define d (make-wire)) +(define e (make-wire)) +(define s (make-wire)) + + +const a = make_wire(); +const b = make_wire(); +const c = make_wire(); +const d = make_wire(); +const e = make_wire(); +const s = make_wire(); + + + 我们通过调用一个构造该类盒子的 + + 过程 + 函数 + + 将一个函数盒附加到一组导线上。该构造函数的参数为要连接到盒子上的导线。例如,既然我们可以构造与门、或门和反相器,就可以按照图所示连接出半加器: + + or_gate_example + or_gate + make_wire_usage + 'ok' + +(or-gate a b d) + + +ok + + +or_gate(a, b, d); + + +"ok" + + + + and_gate_example + and_gate + make_wire_usage + 'ok' + +(and-gate a b c) + + +ok + + +and_gate(a, b, c); + + +"ok" + + + + inverter_example + inverter + make_wire_usage + 'ok' + +(inverter c e) + + +ok + + +inverter(c, e); + + +"ok" + + + + and_gate_example_2 + and_gate + make_wire_usage + 'ok' + +(and-gate d e s) + + +ok + + +and_gate(d, e, s); + + +"ok" + + + + + + 更好的是,我们可以通过定义一个 + + 过程 + 函数 + + + 半加器 + half_@adder + + 来明确命名这个操作,该操作构造出这个电路,给出要连接到半加器上的四根外部导线: + + half_adder_example + half_adder + +const a = make_wire(); +const b = make_wire(); +const s = make_wire(); +const c = make_wire(); +half_adder(a, b, s, c); + + + + 半加器half_adder + half_adder + make_wire + or_gate + and_gate + inverter + half_adder_example + 'ok' + +(define (half-adder a b s c) + (let ((d (make-wire)) (e (make-wire))) + (or-gate a b d) + (and-gate a b c) + (inverter c e) + (and-gate d e s) + 'ok)) + + +function half_adder(a, b, s, c) { + const d = make_wire(); + const e = make_wire(); + or_gate(a, b, d); + and_gate(a, b, c); + inverter(c, e); + and_gate(d, e, s); + return "ok"; +} + + + 这样定义的优点在于,我们可以将 + + 半加器 + half_adder + + 本身作为构建更复杂电路的积木块使用。例如,图展示了一个由两个半加器和一个或门组成的 + 全加器 + 加器 + 全加器全加器是一种在二进制数相加中使用的基本电路元素。这里 $A$$B$ 表示要相加的两个数字中对应位置上的位,$C_{\mathit{in}}$ 为来自右侧一位的进位。该电路生成 $\mathit{SUM}$,即对应位置上的和位,以及 $C_{\mathit{out}}$,即需传播到左侧的进位。 我们可以按照如下方式构造一个全加器: + + full_adder_example + full_adder + +const a = make_wire(); +const b = make_wire(); +const c_in = make_wire(); +const sum = make_wire(); +const c_out = make_wire(); +full_adder(a, b, c_in, sum, c_out); + + + + 全加器full_adder + full_adder + make_wire + half_adder + or_gate + full_adder_example + 'ok' + +(define (full-adder a b c-in sum c-out) + (let ((s (make-wire)) + (c1 (make-wire)) + (c2 (make-wire))) + (half-adder b c-in s c1) + (half-adder a s sum c2) + (or-gate c1 c2 c-out) + 'ok)) + + +function full_adder(a, b, c_in, sum, c_out) { + const s = make_wire(); + const c1 = make_wire(); + const c2 = make_wire(); + half_adder(b, c_in, s, c1); + half_adder(a, s, sum, c2); + or_gate(c1, c2, c_out); + return "ok"; +} + + + 定义了 + + 全加器 + full_adder + + 作为一个 + + 过程, + 函数, + + 后,我们就可以利用它作为构建更复杂电路的积木块。(例如,参见练习。) + + <!-- 图形因适应 SICP JS 分页稍后移动 --> + <!-- 图形代码在本文件的 PDF_ONLY 部分中重复 --> +
    +
    + 一个全加器电路。 + + +
    +
    +
    + + + 从本质上讲,我们的模拟器为我们提供了构建电路语言的工具。如果我们采用在 一节中研究 + + Lisp + JavaScript + + 时所持的一般语言视角,我们可以认为原始函数盒构成了该语言的基本元素,将盒子连线在一起提供了一种组合方式,以及将接线模式指定为 + + 过程 + 函数 + + 则构成了一种抽象手段。 + + + 原始函数盒 + + + + 原始函数盒 + 数字电路仿真原始函数盒 + 实现了当一根导线上的信号发生变化时对其他导线信号产生影响的作用。为了构建函数盒,我们在导线上使用以下操作: +
      +
    • + + (get-signal wire): + + get_signal(wire) + + + get_signal +

      + 返回导线上信号的当前值。 +
    • +
    • + + (set-signal! wire new-value): + + set_signal(wire, new-value): + + + set_signal +

      + 将导线上信号的值更改为新值。 +
    • +
    • + + + (add-action! wire procedure-of-no-arguments): + + + add_action(wire, function-of-no-arguments): + + + add_action +

      + 声明当导线上信号发生变化时,应执行指定的 + + 过程 + 函数 + + 。这样的 + + 过程 + 函数 + + 是传递导线上信号变化至其他导线的载体。 +
    • +
    + 此外,我们还将使用一个 + + 过程 + 函数 + + after_delay + + + after-delay + + + after_delay + + + ,它接受一个时间延迟和一个待执行的 + + 过程 + 函数 + + ,并在给定延迟后执行该过程/函数。 +
    + + + 利用这些 + + 过程, + 函数, + + 我们可以定义原始的数字逻辑函数。要通过反相器将输入连接到输出,我们使用 + + add-action! + add_action + + + 将输入导线与一个将在输入导线上信号改变时执行的 + + 过程 + 函数 + + 关联在一起。该 + + 过程 + 函数 + + 计算输入信号的 + + logical-not + logical_not + + + ,然后,在经历一个 + + inverter-delay, + inverter_delay, + + + 之后,将输出信号设置为这个新值: + + inverterinverter + logical_not + inverter + get_signal + after_delay + inverter_example + 'ok' + +(define (inverter input output) + (define (invert-input) + (let ((new-value (logical-not (get-signal input)))) + (after-delay inverter-delay + (lambda () + (set-signal! output new-value))))) + (add-action! input invert-input) + 'ok) + +(define (logical-not s) + (cond ((= s 0) 1) + ((= s 1) 0) + (else (error "Invalid signal" s)))) + + +function inverter(input, output) { + function invert_input() { + const new_value = logical_not(get_signal(input)); + after_delay(inverter_delay, + () => set_signal(output, new_value)); + } + add_action(input, invert_input); + return "ok"; +} +function logical_not(s) { + return s === 0 + ? 1 + : s === 1 + ? 0 + : error(s, "invalid signal"); +} + + + + + + logical_and + +function logical_and(s1, s2) { + return s1 === 1 && s2 === 1 + ? 1 + : s1 === 0 || s1 === 1 + ? s2 === 0 || s2 === 1 + ? 0 + : error(s2, "invalid signal") + : error(s1, "invalid signal"); +} + + + + <!-- 将图形从前面移至此处以适应 SICP JS 分页 --> + <!-- 图形代码是该文件中较早 WEB_ONLY 部分代码的复制 --> +
    +
    + 全加器电路。 + + +
    +
    + + + 一个与门稍微复杂一些。只要与门的任一输入发生变化,就必须执行 + + 过程 + 函数 + + 的动作。它计算输入导线上信号值的 + + 逻辑与 + (使用一个类似于 + 逻辑非 的过程) + + + logical_and + (使用一个类似于 + logical_not 的函数) + + + 并安排在经过一个 + + 与门延迟 + and_gate_delay。 + + + 之后,让输出导线更新为新的信号值。 + + and-gateand_gate + and_gate + get_signal + after_delay + logical_and + and_gate_example + 'ok' + +(define (and-gate a1 a2 output) + (define (and-action-procedure) + (let ((new-value + (logical-and (get-signal a1) (get-signal a2)))) + (after-delay and-gate-delay + (lambda () + (set-signal! output new-value))))) + (add-action! a1 and-action-procedure) + (add-action! a2 and-action-procedure) + 'ok) + + +function and_gate(a1, a2, output) { + function and_action_function() { + const new_value = logical_and(get_signal(a1), + get_signal(a2)); + after_delay(and_gate_delay, + () => set_signal(output, new_value)); + } + add_action(a1, and_action_function); + add_action(a2, and_action_function); + return "ok"; +} + + + + + + 定义一个 + 或门或门or-gateor_gate + 作为一个基本函数盒。你的 + + or-gate + or_gate + + + 构造器应该类似于 + + and-gate + and_gate。 + + + + + + logical_or + +// contributed by GitHub user clean99 + +function logical_or(s1, s2) { + return s1 === 0 && s2 === 0 + ? 0 + : s1 === 0 || s1 === 1 + ? s2 === 0 || s2 === 1 + ? 1 + : error(s2, "invalid signal") + : error(s1, "invalid signal"); +} + + + + or_gate + get_signal + after_delay + logical_or + +// contributed by GitHub user clean99 + +function or_gate(a1, a2, output) { + function or_action_function() { + const new_value = logical_or(get_signal(a1), + get_signal(a2)); + after_delay(or_gate_delay, + () => set_signal(output, new_value)); + } + add_action(a1, or_action_function); + add_action(a2, or_action_function); + return "ok"; +} + + + + + + + 构造一个 + 或门或门或门or_gate + 的另一种方法是将其作为一个由与门和反相器构成的复合数字逻辑设备来构造。定义一个 + + 过程 + 函数 + + + 或门 + or_gate + + 来实现这一目的。该或门的延迟时间以 + + 与门延迟 + and_gate_delay + + 和 + + 反相器延迟? + inverter_delay? + + + 进行表示。 + + (由 GitHub 用户 taimoon 贡献) + + 思路:~(~a & ~b) = nand(~a, ~b) = ~~a | ~~b = a | b + + +function nand_gate(a1, a2, out){ + const tmp = make_wire(); + and_gate(a1, a2, tmp); + inverter(tmp, out); +} + +function or_gate(a1, a2, out){ + const not_a1 = make_wire(); + const not_a2 = make_wire(); + inverter(a1, not_a1); + inverter(a2, not_a2); + nand_gate(not_a1, not_a2, out); +} + + + 与非门的延迟时间为 + nand_gate_delay = and_gate_delay + inverter_delay 而上述或门的延迟时间为 + or_gate_delay = nand_gate_delay + inverter_delay = and_gate_delay + 2 * inverter_delay。 + + + + + + 图展示了一个 + 串行进位加法器 + 加法器串行进位加法器 + 串行进位加法器,由串联$n$个全加器构成。 + 这是用于相加两个$n$位二进制数的最简单形式的并行加法器。 + 输入$A_{1}$、 + $A_{2}$、 + $A_{3}$、 + $A_{n}$及 + $B_{1}$、 + $B_{2}$、 + $B_{3}$、 + $B_{n}$ + 是待相加的两个二进制数(每个$A_{k}$和 + $B_{k}$均为0或1)。 + 该电路生成 + $S_{1}$、 + $S_{2}$、 + $S_{3}$、 + $S_{n}$,即$n$位的和,以及 + $C$,表示加法的进位。 + 编写一个 + + 过程 + 函数 + + + 串行进位加法器 + ripple_carry_adder + + + 来生成该电路。 + 该 + + 过程 + 函数 + + 应接受三个列表作为参数,每个列表包含$n$根导线,分别表示 + $A_{k}$$B_{k}$和 + $S_{k}$同时还接受另一根导线$C$。 + 串行进位加法器的主要缺点是需要等待进位信号传播。获得一个$n$位串行进位加法器完整输出所需的延迟是多少?请以与门、或门和反相器的延迟来表达该延迟。 + + + +
    +
    + + 用于$n$-位数字的串行进位加法器。 + + +
    + + 数字电路仿真原语功能盒 + + + 表示导线 + + + 一条导线 + 数字电路仿真表示导线 + 在我们的仿真中将是一个具有两个局部状态变量的计算对象: + + 一个信号值 + 一个信号值 + + + (初始时取值为 0)以及一组 + + 动作过程 + 动作函数 + + + ,在信号值改变时运行。我们实现导线时, + 采用 + 消息传递在数字电路仿真中 + 消息传递风格,作为一组局部 + + 过程 + 函数 + + 再加上一个 +dispatch + + 过程 + 函数 + + 用于选择合适的局部操作,就像我们在节中对简单的银行账户对象所做的一样: + + make_wire + make_wire + call_each + make_wire_usage + +(define (make-wire) + (let ((signal-value 0) (action-procedures '())) + (define (set-my-signal! new-value) + (if (not (= signal-value new-value)) + (begin (set! signal-value new-value) + (call-each action-procedures)) + 'done)) + +(define (accept-action-procedure! proc) + (set! action-procedures (cons proc action-procedures)) + (proc)) + +(define (dispatch m) + (cond ((eq? m 'get-signal) signal-value) + ((eq? m 'set-signal!) set-my-signal!) + ((eq? m 'add-action!) accept-action-procedure!) + (else (error "Unknown operation -- WIRE" m)))) + dispatch)) + + +function make_wire() { + let signal_value = 0; + let action_functions = null; + function set_my_signal(new_value) { + if (signal_value !== new_value) { + signal_value = new_value; + return call_each(action_functions); + } else { + return "done"; + } + } + function accept_action_function(fun) { + action_functions = pair(fun, action_functions); + fun(); + } + function dispatch(m) { + return m === "get_signal" + ? signal_value + : m === "set_signal" + ? set_my_signal + : m === "add_action" + ? accept_action_function + : error(m, "unknown operation -- wire"); + } + return dispatch; +} + + + 局部的 + + 过程 + 函数 + + + set-my-signal + set_my_signal + + + 测试新的信号值是否改变了线路上的信号。 + 如果是这样,则依次运行每个操作 + + 过程, + 函数, + + 使用下面这个 + + 过程 + 函数 + + + call-each, + call_each, + + 它会依次调用列表中每个无参数的 + + 过程: + 函数: + + + call_each + call_each + +(define (call-each procedures) + (if (null? procedures) + 'done + (begin + ((car procedures)) + (call-each (cdr procedures))))) + + +function call_each(functions) { + if (is_null(functions)) { + return "done"; + } else { + head(functions)(); + return call_each(tail(functions)); + } +} + + + 局部的 + + 过程 + 函数 + + + + accept-action-procedure + accept_action_function + + + 将给定的 + + 过程 + 函数 + + 添加到要运行的 + + 过程 + 函数 + + 列表中,然后运行新的 + + 过程 + 函数 + + 一次。(参见练习。) + + + + + 使用局部的 dispatch + + 过程 + 函数 + + 按照指定方式设置后,我们可以提供以下 + + 过程 + 函数 + + 以访问线路上的局部操作:这些 + + 过程 + 函数 + + 仅仅是语法糖,它们通过 + + + 语法糖过程与数据的 + + + 语法糖函数与数据的 + + + 语法接口 + 允许我们使用普通的 + + 过程式 + 函数式 + + 语法来访问对象的局部 + + 过程 + 函数 + + 。 令人惊讶的是,我们可以如此简单地互换 + + 过程 + 函数 + + 和 + 数据的角色。例如,如果我们写下 + + (wire 'get-signal) + wire("get_signal") + + + 我们将 wire 视为一个被传入消息 + + get-signal + "get_signal" + + + 调用的 + + 过程 + 函数 + + 。 或者,写作 + + (get-signal wire) + get_signal(wire) + + + 则使我们更倾向于将 wire 看作一个作为 + + 过程 + 函数 + + + get-signal. + get_signal. + + + 的输入数据对象。 实际情况是,在一种可以将 + + 过程 + 函数 + + 作为对象处理的语言中, + 过程函数 + 与 数据 之间没有本质区别,我们可以选择合适的语法糖以允许我们以任意风格编程。 + + + get_signal + set_signal + add_action + get_signal + +(define (get-signal wire) + (wire 'get-signal)) + +(define (set-signal! wire new-value) + ((wire 'set-signal!) new-value)) + + +(define (add-action! wire action-procedure) + ((wire 'add-action!) action-procedure)) + + +function get_signal(wire) { + return wire("get_signal"); +} +function set_signal(wire, new_value) { + return wire("set_signal")(new_value); +} +function add_action(wire, action_function) { + return wire("add_action")(action_function); +} + + + + + + + + 线路具有时变信号,并且可能会被逐步附加到设备上,这些都是可变对象的典型特征。我们将它们建模为 + + 过程 + 函数 + + ,它们具有通过赋值修改的局部状态变量。当创建一个新的线路时,将分配一组新的状态变量(由 + + let expression in + + let statements in + + + + make-wire) + make_wire) + + 负责,并且一个新的 dispatch + + 过程 + 函数 + + 被构造并返回,同时捕获了带有新状态变量的环境。 + + + + + + 这些线路被连接到它们的各种设备共享。因此,与某个设备的交互而引起的更改会影响所有连接到该线路的其他设备。该线路通过调用在建立连接时提供给它的行动 + + procedures + functions + + 来向其邻居传递这一更改。 + + + + + 数字电路仿真 + 表示导线 + + + + + + 议程 + + + + digital-circuit simulationagenda + + + 完成模拟器所需的唯一内容是 + + after-delay. + after_delay. + + 这里的理念在于维护一个称为agenda的数据结构,其中包含了待执行事项的安排。 + 以下为agenda所定义的操作: +
      +
    • + + (make-agenda): + make_agenda(): + + make_agenda +

      + 返回一个新的空agenda。 +
    • +
    • + + (empty-agenda? agenda): + + is_empty_agenda(agenda) + + + is_empty_agenda +

      + 当指定的agenda为空时,结果为真。 +
    • +
    • + + (first-agenda-item agenda): + + first_agenda_item(agenda) + + + first_agenda_item +

      + 返回agenda中的第一个项目。 +
    • +
    • + + + (remove-first-agenda-item! agenda): + + + remove_first_agenda_item(agenda) + + + remove_first_agenda_item +

      + 通过移除第一个项目来修改agenda。 +
    • +
    • + + + (add-to-agenda! time action agenda): + + + add_to_agenda(time, action, agenda) + + + add_to_agenda +

      + 通过将给定的 + + procedure + function + + (在指定时间执行)添加到agenda中,从而对agenda进行修改。 +
    • +
    • + + (current-time agenda): + + current_time(agenda) + + + current_time +

      + 返回当前仿真时间。 +
    • +
    +
    + + + 我们使用的特定 agenda 用以下方式表示: + + the-agenda + the_agenda + + 该 + + procedure + function + + + after-delay + after_delay + + 将新元素添加到 + + the-agenda + the_agenda + + + after_delay + after_delay + add_to_agenda + make_agenda + the_agenda + +(define (after-delay delay action) + (add-to-agenda! (+ delay (current-time the-agenda)) + action + the-agenda)) + + +function after_delay(delay, action) { + add_to_agenda(delay + current_time(the_agenda), + action, + the_agenda); +} + + + + + + + + 模拟由过程 + propagate 驱动,该过程操作 + the-agenda, + 依次执行议程上每个过程。 + + + 模拟由函数 + propagate 驱动,该函数依次执行 + the_agenda 上的每个函数。 + + + 通常情况下,随着模拟运行,新项目会被添加到议程中,只要议程中还有项目, + propagate 就会继续模拟: + + propagate + propagate + remove_first_agenda_item + first_agenda_item + the_agenda + +(define (propagate) + (if (empty-agenda? the-agenda) + 'done + (let ((first-item (first-agenda-item the-agenda))) + (first-item) + (remove-first-agenda-item! the-agenda) + (propagate)))) + + +function propagate() { + if (is_empty_agenda(the_agenda)) { + return "done"; + } else { + const first_item = first_agenda_item(the_agenda); + first_item(); + remove_first_agenda_item(the_agenda); + return propagate(); + } +} + + + + + 数字电路仿真议程 + + + + + + + + + + 以下内容 + + 过程, + 函数, + + ,它在一条导线上放置一个探针,展示了仿真器的运行。探针告知导线,每当其信号发生变化时,应打印出新的信号值,同时显示当前时间以及一个用于标识 + + 导线: + + 导线. + + + + 探针在数字电路仿真器中 + 探针 + 议程 + get_signal + +(define (probe name wire) + (add-action! wire + (lambda () + (newline) + (display name) + (display " ") + (display (current-time the-agenda)) + (display " New-value = ") + (display (get-signal wire)))) + + +function probe(name, wire) { + add_action(wire, + () => display(name + " " + + stringify(current_time(the_agenda)) + + ", new value = " + + stringify(get_signal(wire)))); +} + + +function probe(name, wire) { + add_action(wire, + () => name + " " + + stringify(current_time(the_agenda)) + + ", new value = " + + stringify(get_signal(wire))); +} + + + + + + + + 我们首先初始化议程,并为原始函数盒指定延时: + + the_agenda + make_agenda + +(define the-agenda (make-agenda)) +(define inverter-delay 2) +(define and-gate-delay 3) +(define or-gate-delay 5) + + +const the_agenda = make_agenda(); +const inverter_delay = 2; +const and_gate_delay = 3; +const or_gate_delay = 5; + + + 现在我们定义四根导线,并在其中两根上放置探针: + + probing_two_wires + make_wire + probe + +(define input-1 (make-wire)) +(define input-2 (make-wire)) +(define sum (make-wire)) +(define carry (make-wire)) + +(probe 'sum sum) + + + sum 0 New-value = 0 + + +const input_1 = make_wire(); +const input_2 = make_wire(); +const sum = make_wire(); +const carry = make_wire(); + +probe("sum", sum); + + +"sum 0, new value = 0" + + + + probe_carry + probing_two_wires + +(probe 'carry carry) + + +carry 0 New-value = 0 + + +probe("carry", carry); + + +"carry 0, new value = 0" + + + 接下来我们将导线连接到半加器电路中(如 图所示),将 + + input-1 + input_1 + + 的信号设为 1,并运行仿真: + + half_adder_example_2 + half_adder + probe_carry + 'ok' + +(half-adder input-1 input-2 sum carry) + + +ok + + +half_adder(input_1, input_2, sum, carry); + + +"ok" + + + + set_signal_example + half_adder_example_2 + 'done' + +(set-signal! input-1 1) + + +done + + +set_signal(input_1, 1); + + +"done" + + + + propagate_example_1 + set_signal_example + propagate + 'done' + +(propagate) + + +sum 8 New-value = 1 +done + + +propagate(); + + +"sum 8, new value = 1" +"done" + + + sum 信号在时间 8 时变为 1。 + 此时距仿真开始已过去八个时间单位。 + 在此时,我们可以将 + + input-2 + input_2 + + 的信号设为 1,并允许信号传播: + + set_signal_example_2 + propagate_example_1 + 'done' + +(set-signal! input-2 1) + + + done + + +set_signal(input_2, 1); + + +"done" + + + + propagate_example_2 + set_signal_example_2 + 'done' + +(propagate) + + +carry 11 New value = 1 +sum 16 New value = 0 +done + + +propagate(); + + +"carry 11, new value = 1" +"sum 16, new value = 0" +"done" + + + carry 信号在时间 11 时变为 1,而 + sum 信号在时间 16 时变为 0。 + + + + + 数字电路仿真示例仿真 + + + 半加器仿真</CLOSE> + + + + + + 内部的 + + procedure + function + + + accept-action-procedure! + accept_action_function + + 在 + make_wire + + make-wire + make_wire + + 中规定,当一个新的动作 + + procedure + function + + 被添加到连线时,该 + + procedure + function + + 会立即运行。请解释为什么这种初始化是必要的。特别是,跟踪上文段落中的半加器示例,并说明如果我们将 + + accept-action-procedure! + accept_action_function + + 定义为 + + +(define (accept-action-procedure! proc) + (set! action-procedures (cons proc action-procedures))) + + +function accept_action_function(fun) { + action_functions = pair(fun, action_functions); +} + + + + + + + + 实现议程 + + + + + 数字电路仿真实现议程 + + + + + 最后,我们详细介绍了议程数据结构,它保存了预定将来执行的 + + 过程 + 函数 + + 。 + + + + + + 议程由 + 议程中的时间段 + 时间段组成。 每个时间段是一对,由一个数字(表示时间)和一个 + 队列仿真在仿真议程中 + 队列构成(参见练习),该队列保存了在该时间段内预定执行的 + + 过程 + 函数 + + 。 + + make_time_segment + segment_time + segment_queue + make_time_segment + +(define (make-time-segment time queue) + (cons time queue)) + +(define (segment-time s) (car s)) + +(define (segment-queue s) (cdr s)) + + +function make_time_segment(time, queue) { + return pair(time, queue); +} +function segment_time(s) { return head(s); } + +function segment_queue(s) { return tail(s); } + + + 我们将使用在部分中描述的队列操作来操作时间段队列。 + + + + + + 议程本身是一个一维的 + 在仿真议程中使用 + 时间段表。它不同于在部分中描述的表,因为这些时间段将按照递增的时间排序。此外,我们在议程的首部存储了 + 仿真议程中的当前时间 + 当前时间(即,上次处理动作的时间)。新构造的议程没有时间段,且当前时间为0:议程是一个 + 有头列表 + 列表(s)有头 + 的有头列表,类似于在部分中的表,但由于列表以时间作为头部,我们不需要额外的虚拟头(例如使用表时所用的 + + + *table* 符号 + + + "*table*" 字符串 + + + )。 + + make_agenda + current_time + set_current_time + segments + set_segments + first_segment + rest_segments + make_agenda + +(define (make-agenda) (list 0)) + +(define (current-time agenda) (car agenda)) + +(define (set-current-time! agenda time) + (set-car! agenda time)) + +(define (segments agenda) (cdr agenda)) + +(define (set-segments! agenda segments) + (set-cdr! agenda segments)) + +(define (first-segment agenda) (car (segments agenda))) + +(define (rest-segments agenda) (cdr (segments agenda))) + + +function make_agenda() { return list(0); } + +function current_time(agenda) { return head(agenda); } + +function set_current_time(agenda, time) { + set_head(agenda, time); +} +function segments(agenda) { return tail(agenda); } + +function set_segments(agenda, segs) { + set_tail(agenda, segs); +} +function first_segment(agenda) { return head(segments(agenda)); } + +function rest_segments(agenda) { return tail(segments(agenda)); } + + + + + + + + 如果议程没有时间段,则议程为空: + + is_empty_agenda + is_empty_agenda + make_agenda + +(define (empty-agenda? agenda) + (null? (segments agenda))) + + +function is_empty_agenda(agenda) { + return is_null(segments(agenda)); +} + + + + + + + To add an action to an agenda, we first check if the agenda is empty. + If so, we create a time segment for the action and install this in + the agenda. Otherwise, we scan the agenda, examining the time of each + segment. If we find a segment for our appointed time, we add the + action to the associated queue. If we reach a time later than the one + to which we are appointed, we insert a new time segment into the + agenda just before it. If we reach the end of the agenda, we must + create a new time segment at the end. + + add_to_agenda + add_to_agenda + make_time_segment + make_queue + insert_queue + make_time_segment + make_agenda + +(define (add-to-agenda! time action agenda) + (define (belongs-before? segments) + (or (null? segments) + (< time (segment-time (car segments))))) + (define (make-new-time-segment time action) + (let ((q (make-queue))) + (insert-queue! q action) + (make-time-segment time q))) + (define (add-to-segments! segments) + (if (= (segment-time (car segments)) time) + (insert-queue! (segment-queue (car segments)) + action) + (let ((rest (cdr segments))) + (if (belongs-before? rest) + (set-cdr! + segments + (cons (make-new-time-segment time action) + (cdr segments))) + (add-to-segments! rest))))) + (let ((segments (segments agenda))) + (if (belongs-before? segments) + (set-segments! + agenda + (cons (make-new-time-segment time action) + segments)) + (add-to-segments! segments)))) + + +function add_to_agenda(time, action, agenda) { + function belongs_before(segs) { + return is_null(segs) || time < segment_time(head(segs)); + } + function make_new_time_segment(time, action) { + const q = make_queue(); + insert_queue(q, action); + return make_time_segment(time, q); + } + function add_to_segments(segs) { + if (segment_time(head(segs)) === time) { + insert_queue(segment_queue(head(segs)), action); + } else { + const rest = tail(segs); + if (belongs_before(rest)) { + set_tail(segs, pair(make_new_time_segment(time, action), + tail(segs))); + } else { + add_to_segments(rest); + } + } + } + const segs = segments(agenda); + if (belongs_before(segs)) { + set_segments(agenda, + pair(make_new_time_segment(time, action), segs)); + } else { + add_to_segments(segs); + } +} + + + + + + The + + procedure + function + + that removes the first item from the agenda deletes the + item at the front of the queue in the first time segment. If this + deletion makes the time segment empty, we remove it from the list of + segments:Observe that the + + + + if + expression in this + procedure + has no alternative expression. + + + conditional statement in this + function has an + blockempty + empty block as its alternative statement. + + + Such a + conditional statementone-armed (without alternative) + + + one-armed if expression + + + one-armed conditional statement + + + is used to decide whether to do something, rather than to select between two + + + expressions. + An if expression returns an + unspecified value if the predicate is false and there is no + alternative. + + statements. + + + + remove_first_agenda_item + remove_first_agenda_item + make_agenda + is_empty_queue + delete_queue + make_time_segment + +(define (remove-first-agenda-item! agenda) + (let ((q (segment-queue (first-segment agenda)))) + (delete-queue! q) + (if (empty-queue? q) + (set-segments! agenda (rest-segments agenda))))) + + +function remove_first_agenda_item(agenda) { + const q = segment_queue(first_segment(agenda)); + delete_queue(q); + if (is_empty_queue(q)) { + set_segments(agenda, rest_segments(agenda)); + } else {} +} + + + + + + The first agenda item is found at the head of the queue in the first + time segment. Whenever we extract an item, we also update the current + time:In this way, the current time will always be the time + of the action most recently processed. Storing this time at the head + of the agenda ensures that it will still be available even if the + associated time segment has been deleted. + + first_agenda_item + first_agenda_item + is_empty_agenda + make_time_segment + front_queue + +(define (first-agenda-item agenda) + (if (empty-agenda? agenda) + (error "Agenda is empty -- FIRST-AGENDA-ITEM") + (let ((first-seg (first-segment agenda))) + (set-current-time! agenda (segment-time first-seg)) + (front-queue (segment-queue first-seg))))) + + +function first_agenda_item(agenda) { + if (is_empty_agenda(agenda)) { + error("agenda is empty -- first_agenda_item"); + } else { + const first_seg = first_segment(agenda); + set_current_time(agenda, segment_time(first_seg)); + return front_queue(segment_queue(first_seg)); + } +} + + + + + + The + + procedures + functions + + to be run during each time segment of the agenda are kept in a queue. + Thus, the + + procedures + functions + + for each segment are called in the order in which they were added to the + agenda (first in, first out). Explain why this order must be used. In + particular, trace the behavior of an and-gate whose inputs change from + 0,1 to 1,0 in the same segment and say how the behavior would differ if + we stored a segments + + procedures + functions + + in an ordinary list, adding and removing + + procedures + functions + + only at the front (last in, first out). + + + + digital-circuit simulation + digital-circuit simulationagenda implementation + +
    From da45b5ebc56af85bac7b657cf30cf339ec3199e2 Mon Sep 17 00:00:00 2001 From: coder114514 <98397499+coder114514@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:13:30 +0800 Subject: [PATCH 43/55] fix bugs --- i18n/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/i18n/index.ts b/i18n/index.ts index 168205056..bd3e539ad 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -174,18 +174,18 @@ async function setupCleanupHandlers() { }); } -function needsTranslation(enFilePath: string, lang: string): boolean { +async function needsTranslation(enFilePath: string, lang: string): boolean { const cnFilePath = enFilePath.replace( - enFilePath.sep + "en" + enFilePath.sep, - enFilePath.sep + ".." + enFilePath.sep + "i18n" + enFilePath.sep + "translation_output" + enFilePath.sep + lang + enFilePath.sep + path.sep + "en" + path.sep, + path.sep + ".." + path.sep + "i18n" + path.sep + "translation_output" + path.sep + lang + path.sep ); try { - const cnStats = await fs.promise.stat(cnFilePath); + const cnStats = await fs.promises.stat(cnFilePath); if (!cnStats.isFile()) { return true; } - const enStats = await fs.promise.stat(enFilePath); + const enStats = await fs.promises.stat(enFilePath); return enStats.mtime > cnStats.mtime; } catch (error) { return true; @@ -244,7 +244,7 @@ async function findAllXmlFiles(directory: string): Promise { } // Find all XML files console.log(`Scanning directory: ${enDirPath}`); - filestoTranslate = await findAllXmlFiles(enDirPath); + filesToTranslate = await findAllXmlFiles(enDirPath); } else { const [, , ...xmlFiles] = process.argv; filesToTranslate = xmlFiles.map(file => path.join(__dirname, "..", file)); @@ -273,7 +273,7 @@ async function findAllXmlFiles(directory: string): Promise { const results = await Promise.allSettled( batch.map(async file => { if (absent) { - if (!needsTranslation(file, lang)) { + if (!(await needsTranslation(file, lang))) { console.log(`Skipped translation for ${file} to language ${lang} (yarn trans abs)`); return { file, success: true }; } From ffdb9ae29d55c291fcfe9a45b891323f09a260d0 Mon Sep 17 00:00:00 2001 From: yihao Date: Wed, 16 Apr 2025 14:24:13 +0800 Subject: [PATCH 44/55] corrected minor bugs --- i18n/index.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/i18n/index.ts b/i18n/index.ts index 426795d5e..6cb74e662 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -173,10 +173,21 @@ async function setupCleanupHandlers() { }); } -async function needsTranslation(enFilePath: string, lang: string): boolean { +async function needsTranslation( + enFilePath: string, + lang: string +): Promise { const cnFilePath = enFilePath.replace( path.sep + "en" + path.sep, - path.sep + ".." + path.sep + "i18n" + path.sep + "translation_output" + path.sep + lang + path.sep + path.sep + + ".." + + path.sep + + "i18n" + + path.sep + + "translation_output" + + path.sep + + lang + + path.sep ); try { const cnStats = await fs.promises.stat(cnFilePath); @@ -285,7 +296,9 @@ export default async function fancyName(path: string, language: string) { batch.map(async file => { if (absent) { if (!(await needsTranslation(file, lang))) { - console.log(`Skipped translation for ${file} to language ${lang} (yarn trans abs)`); + console.log( + `Skipped translation for ${file} to language ${lang} (yarn trans abs)` + ); return { file, success: true }; } } From ee03f0b6080e2ec10462c2daa6c8c108aa027fb1 Mon Sep 17 00:00:00 2001 From: yihao Date: Wed, 16 Apr 2025 14:46:29 +0800 Subject: [PATCH 45/55] renamed folder to use longer language code --- i18n/index.ts | 10 +--------- xml/{zh => zh_CN}/chapter1/chapter1.xml | 0 xml/{zh => zh_CN}/chapter1/section1/section1.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection1.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection2.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection3.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection4.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection5.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection6.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection7.xml | 0 xml/{zh => zh_CN}/chapter1/section1/subsection8.xml | 0 xml/{zh => zh_CN}/chapter1/section2/section2.xml | 0 xml/{zh => zh_CN}/chapter1/section2/subsection1.xml | 0 xml/{zh => zh_CN}/chapter1/section2/subsection2.xml | 0 xml/{zh => zh_CN}/chapter1/section2/subsection3.xml | 0 xml/{zh => zh_CN}/chapter1/section2/subsection4.xml | 0 xml/{zh => zh_CN}/chapter1/section2/subsection5.xml | 0 xml/{zh => zh_CN}/chapter1/section2/subsection6.xml | 0 xml/{zh => zh_CN}/chapter1/section3/section3.xml | 0 xml/{zh => zh_CN}/chapter1/section3/subsection1.xml | 0 xml/{zh => zh_CN}/chapter1/section3/subsection2.xml | 0 xml/{zh => zh_CN}/chapter1/section3/subsection3.xml | 0 xml/{zh => zh_CN}/chapter1/section3/subsection4.xml | 0 xml/{zh => zh_CN}/chapter2/chapter2.xml | 0 xml/{zh => zh_CN}/chapter2/section1/section1.xml | 0 xml/{zh => zh_CN}/chapter2/section1/subsection1.xml | 0 xml/{zh => zh_CN}/chapter2/section1/subsection2.xml | 0 xml/{zh => zh_CN}/chapter2/section1/subsection3.xml | 0 xml/{zh => zh_CN}/chapter2/section1/subsection4.xml | 0 xml/{zh => zh_CN}/chapter2/section2/section2.xml | 0 xml/{zh => zh_CN}/chapter2/section2/subsection1.xml | 0 xml/{zh => zh_CN}/chapter2/section2/subsection2.xml | 0 xml/{zh => zh_CN}/chapter2/section2/subsection3.xml | 0 xml/{zh => zh_CN}/chapter2/section2/subsection4.xml | 0 xml/{zh => zh_CN}/chapter2/section3/section3.xml | 0 xml/{zh => zh_CN}/chapter2/section3/subsection1.xml | 0 xml/{zh => zh_CN}/chapter2/section3/subsection2.xml | 0 xml/{zh => zh_CN}/chapter2/section3/subsection3.xml | 0 xml/{zh => zh_CN}/chapter2/section3/subsection4.xml | 0 xml/{zh => zh_CN}/chapter2/section4/section4.xml | 0 xml/{zh => zh_CN}/chapter2/section4/subsection1.xml | 0 xml/{zh => zh_CN}/chapter2/section4/subsection2.xml | 0 xml/{zh => zh_CN}/chapter2/section4/subsection3.xml | 0 xml/{zh => zh_CN}/chapter2/section5/section5.xml | 0 xml/{zh => zh_CN}/chapter2/section5/subsection1.xml | 0 xml/{zh => zh_CN}/chapter2/section5/subsection2.xml | 0 xml/{zh => zh_CN}/chapter2/section5/subsection3.xml | 0 xml/{zh => zh_CN}/chapter3/chapter3.xml | 0 xml/{zh => zh_CN}/chapter3/section1/section1.xml | 0 xml/{zh => zh_CN}/chapter3/section1/subsection1.xml | 0 xml/{zh => zh_CN}/chapter3/section1/subsection2.xml | 0 xml/{zh => zh_CN}/chapter3/section1/subsection3.xml | 0 xml/{zh => zh_CN}/chapter3/section2/section2.xml | 0 xml/{zh => zh_CN}/chapter3/section2/subsection1.xml | 0 xml/{zh => zh_CN}/chapter3/section2/subsection2.xml | 0 xml/{zh => zh_CN}/chapter3/section2/subsection3.xml | 0 xml/{zh => zh_CN}/chapter3/section2/subsection4.xml | 0 xml/{zh => zh_CN}/chapter3/section2/subsection5.xml | 0 xml/{zh => zh_CN}/chapter3/section3/section3.xml | 0 xml/{zh => zh_CN}/chapter3/section3/subsection1.xml | 0 xml/{zh => zh_CN}/chapter3/section3/subsection2.xml | 0 xml/{zh => zh_CN}/chapter3/section3/subsection3.xml | 0 xml/{zh => zh_CN}/chapter3/section3/subsection4.xml | 0 xml/{zh => zh_CN}/chapter3/section3/subsection5.xml | 0 xml/{zh => zh_CN}/chapter3/section4/section4.xml | 0 xml/{zh => zh_CN}/chapter3/section4/subsection1.xml | 0 xml/{zh => zh_CN}/chapter3/section4/subsection2.xml | 0 xml/{zh => zh_CN}/chapter3/section5/section5.xml | 0 xml/{zh => zh_CN}/chapter3/section5/subsection1.xml | 0 xml/{zh => zh_CN}/chapter3/section5/subsection2.xml | 0 xml/{zh => zh_CN}/chapter3/section5/subsection3.xml | 0 xml/{zh => zh_CN}/chapter3/section5/subsection4.xml | 0 xml/{zh => zh_CN}/chapter3/section5/subsection5.xml | 0 xml/{zh => zh_CN}/chapter4/chapter4.xml | 0 xml/{zh => zh_CN}/chapter4/section1/section1.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection1.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection2.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection3.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection4.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection5.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection6.xml | 0 xml/{zh => zh_CN}/chapter4/section1/subsection7.xml | 0 xml/{zh => zh_CN}/chapter4/section2/section2.xml | 0 xml/{zh => zh_CN}/chapter4/section2/subsection1.xml | 0 xml/{zh => zh_CN}/chapter4/section2/subsection2.xml | 0 xml/{zh => zh_CN}/chapter4/section2/subsection3.xml | 0 xml/{zh => zh_CN}/chapter4/section3/section3.xml | 0 xml/{zh => zh_CN}/chapter4/section3/subsection1.xml | 0 xml/{zh => zh_CN}/chapter4/section3/subsection2.xml | 0 xml/{zh => zh_CN}/chapter4/section3/subsection3.xml | 0 xml/{zh => zh_CN}/chapter4/section4/section4.xml | 0 xml/{zh => zh_CN}/chapter4/section4/subsection1.xml | 0 xml/{zh => zh_CN}/chapter4/section4/subsection2.xml | 0 xml/{zh => zh_CN}/chapter4/section4/subsection3.xml | 0 xml/{zh => zh_CN}/chapter4/section4/subsection4.xml | 0 xml/{zh => zh_CN}/chapter5/chapter5.xml | 0 xml/{zh => zh_CN}/chapter5/section1/section1.xml | 0 xml/{zh => zh_CN}/chapter5/section1/subsection1.xml | 0 xml/{zh => zh_CN}/chapter5/section1/subsection2.xml | 0 xml/{zh => zh_CN}/chapter5/section1/subsection3.xml | 0 xml/{zh => zh_CN}/chapter5/section1/subsection4.xml | 0 xml/{zh => zh_CN}/chapter5/section1/subsection5.xml | 0 xml/{zh => zh_CN}/chapter5/section2/section2.xml | 0 xml/{zh => zh_CN}/chapter5/section2/subsection1.xml | 0 xml/{zh => zh_CN}/chapter5/section2/subsection2.xml | 0 xml/{zh => zh_CN}/chapter5/section2/subsection3.xml | 0 xml/{zh => zh_CN}/chapter5/section2/subsection4.xml | 0 xml/{zh => zh_CN}/chapter5/section3/section3.xml | 0 xml/{zh => zh_CN}/chapter5/section3/subsection1.xml | 0 xml/{zh => zh_CN}/chapter5/section3/subsection2.xml | 0 xml/{zh => zh_CN}/chapter5/section4/section4.xml | 0 xml/{zh => zh_CN}/chapter5/section4/subsection1.xml | 0 xml/{zh => zh_CN}/chapter5/section4/subsection2.xml | 0 xml/{zh => zh_CN}/chapter5/section4/subsection3.xml | 0 xml/{zh => zh_CN}/chapter5/section4/subsection4.xml | 0 xml/{zh => zh_CN}/chapter5/section5/section5.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection1.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection2.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection3.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection4.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection5.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection6.xml | 0 xml/{zh => zh_CN}/chapter5/section5/subsection7.xml | 0 xml/{zh => zh_CN}/others/02foreword02.xml | 0 xml/{zh => zh_CN}/others/02foreword84.xml | 0 xml/{zh => zh_CN}/others/03prefaces03.xml | 0 xml/{zh => zh_CN}/others/03prefaces96.xml | 0 xml/{zh => zh_CN}/others/04acknowledgements04.xml | 0 xml/{zh => zh_CN}/others/06see06.xml | 0 xml/{zh => zh_CN}/others/97references97.xml | 0 xml/{zh => zh_CN}/others/98indexpreface98.xml | 0 xml/{zh => zh_CN}/others/99making99.xml | 0 132 files changed, 1 insertion(+), 9 deletions(-) rename xml/{zh => zh_CN}/chapter1/chapter1.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/section1.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection6.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection7.xml (100%) rename xml/{zh => zh_CN}/chapter1/section1/subsection8.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/section2.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter1/section2/subsection6.xml (100%) rename xml/{zh => zh_CN}/chapter1/section3/section3.xml (100%) rename xml/{zh => zh_CN}/chapter1/section3/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter1/section3/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter1/section3/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter1/section3/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter2/chapter2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section1/section1.xml (100%) rename xml/{zh => zh_CN}/chapter2/section1/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter2/section1/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section1/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter2/section1/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter2/section2/section2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section2/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter2/section2/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section2/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter2/section2/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter2/section3/section3.xml (100%) rename xml/{zh => zh_CN}/chapter2/section3/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter2/section3/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section3/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter2/section3/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter2/section4/section4.xml (100%) rename xml/{zh => zh_CN}/chapter2/section4/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter2/section4/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section4/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter2/section5/section5.xml (100%) rename xml/{zh => zh_CN}/chapter2/section5/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter2/section5/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter2/section5/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter3/chapter3.xml (100%) rename xml/{zh => zh_CN}/chapter3/section1/section1.xml (100%) rename xml/{zh => zh_CN}/chapter3/section1/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter3/section1/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter3/section1/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter3/section2/section2.xml (100%) rename xml/{zh => zh_CN}/chapter3/section2/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter3/section2/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter3/section2/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter3/section2/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter3/section2/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter3/section3/section3.xml (100%) rename xml/{zh => zh_CN}/chapter3/section3/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter3/section3/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter3/section3/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter3/section3/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter3/section3/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter3/section4/section4.xml (100%) rename xml/{zh => zh_CN}/chapter3/section4/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter3/section4/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter3/section5/section5.xml (100%) rename xml/{zh => zh_CN}/chapter3/section5/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter3/section5/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter3/section5/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter3/section5/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter3/section5/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter4/chapter4.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/section1.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection6.xml (100%) rename xml/{zh => zh_CN}/chapter4/section1/subsection7.xml (100%) rename xml/{zh => zh_CN}/chapter4/section2/section2.xml (100%) rename xml/{zh => zh_CN}/chapter4/section2/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter4/section2/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter4/section2/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter4/section3/section3.xml (100%) rename xml/{zh => zh_CN}/chapter4/section3/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter4/section3/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter4/section3/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter4/section4/section4.xml (100%) rename xml/{zh => zh_CN}/chapter4/section4/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter4/section4/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter4/section4/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter4/section4/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter5/chapter5.xml (100%) rename xml/{zh => zh_CN}/chapter5/section1/section1.xml (100%) rename xml/{zh => zh_CN}/chapter5/section1/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter5/section1/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter5/section1/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter5/section1/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter5/section1/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter5/section2/section2.xml (100%) rename xml/{zh => zh_CN}/chapter5/section2/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter5/section2/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter5/section2/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter5/section2/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter5/section3/section3.xml (100%) rename xml/{zh => zh_CN}/chapter5/section3/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter5/section3/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter5/section4/section4.xml (100%) rename xml/{zh => zh_CN}/chapter5/section4/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter5/section4/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter5/section4/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter5/section4/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/section5.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection1.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection2.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection3.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection4.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection5.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection6.xml (100%) rename xml/{zh => zh_CN}/chapter5/section5/subsection7.xml (100%) rename xml/{zh => zh_CN}/others/02foreword02.xml (100%) rename xml/{zh => zh_CN}/others/02foreword84.xml (100%) rename xml/{zh => zh_CN}/others/03prefaces03.xml (100%) rename xml/{zh => zh_CN}/others/03prefaces96.xml (100%) rename xml/{zh => zh_CN}/others/04acknowledgements04.xml (100%) rename xml/{zh => zh_CN}/others/06see06.xml (100%) rename xml/{zh => zh_CN}/others/97references97.xml (100%) rename xml/{zh => zh_CN}/others/98indexpreface98.xml (100%) rename xml/{zh => zh_CN}/others/99making99.xml (100%) diff --git a/i18n/index.ts b/i18n/index.ts index 6cb74e662..aba8d4e33 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -179,15 +179,7 @@ async function needsTranslation( ): Promise { const cnFilePath = enFilePath.replace( path.sep + "en" + path.sep, - path.sep + - ".." + - path.sep + - "i18n" + - path.sep + - "translation_output" + - path.sep + - lang + - path.sep + path.sep + lang + path.sep ); try { const cnStats = await fs.promises.stat(cnFilePath); diff --git a/xml/zh/chapter1/chapter1.xml b/xml/zh_CN/chapter1/chapter1.xml similarity index 100% rename from xml/zh/chapter1/chapter1.xml rename to xml/zh_CN/chapter1/chapter1.xml diff --git a/xml/zh/chapter1/section1/section1.xml b/xml/zh_CN/chapter1/section1/section1.xml similarity index 100% rename from xml/zh/chapter1/section1/section1.xml rename to xml/zh_CN/chapter1/section1/section1.xml diff --git a/xml/zh/chapter1/section1/subsection1.xml b/xml/zh_CN/chapter1/section1/subsection1.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection1.xml rename to xml/zh_CN/chapter1/section1/subsection1.xml diff --git a/xml/zh/chapter1/section1/subsection2.xml b/xml/zh_CN/chapter1/section1/subsection2.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection2.xml rename to xml/zh_CN/chapter1/section1/subsection2.xml diff --git a/xml/zh/chapter1/section1/subsection3.xml b/xml/zh_CN/chapter1/section1/subsection3.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection3.xml rename to xml/zh_CN/chapter1/section1/subsection3.xml diff --git a/xml/zh/chapter1/section1/subsection4.xml b/xml/zh_CN/chapter1/section1/subsection4.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection4.xml rename to xml/zh_CN/chapter1/section1/subsection4.xml diff --git a/xml/zh/chapter1/section1/subsection5.xml b/xml/zh_CN/chapter1/section1/subsection5.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection5.xml rename to xml/zh_CN/chapter1/section1/subsection5.xml diff --git a/xml/zh/chapter1/section1/subsection6.xml b/xml/zh_CN/chapter1/section1/subsection6.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection6.xml rename to xml/zh_CN/chapter1/section1/subsection6.xml diff --git a/xml/zh/chapter1/section1/subsection7.xml b/xml/zh_CN/chapter1/section1/subsection7.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection7.xml rename to xml/zh_CN/chapter1/section1/subsection7.xml diff --git a/xml/zh/chapter1/section1/subsection8.xml b/xml/zh_CN/chapter1/section1/subsection8.xml similarity index 100% rename from xml/zh/chapter1/section1/subsection8.xml rename to xml/zh_CN/chapter1/section1/subsection8.xml diff --git a/xml/zh/chapter1/section2/section2.xml b/xml/zh_CN/chapter1/section2/section2.xml similarity index 100% rename from xml/zh/chapter1/section2/section2.xml rename to xml/zh_CN/chapter1/section2/section2.xml diff --git a/xml/zh/chapter1/section2/subsection1.xml b/xml/zh_CN/chapter1/section2/subsection1.xml similarity index 100% rename from xml/zh/chapter1/section2/subsection1.xml rename to xml/zh_CN/chapter1/section2/subsection1.xml diff --git a/xml/zh/chapter1/section2/subsection2.xml b/xml/zh_CN/chapter1/section2/subsection2.xml similarity index 100% rename from xml/zh/chapter1/section2/subsection2.xml rename to xml/zh_CN/chapter1/section2/subsection2.xml diff --git a/xml/zh/chapter1/section2/subsection3.xml b/xml/zh_CN/chapter1/section2/subsection3.xml similarity index 100% rename from xml/zh/chapter1/section2/subsection3.xml rename to xml/zh_CN/chapter1/section2/subsection3.xml diff --git a/xml/zh/chapter1/section2/subsection4.xml b/xml/zh_CN/chapter1/section2/subsection4.xml similarity index 100% rename from xml/zh/chapter1/section2/subsection4.xml rename to xml/zh_CN/chapter1/section2/subsection4.xml diff --git a/xml/zh/chapter1/section2/subsection5.xml b/xml/zh_CN/chapter1/section2/subsection5.xml similarity index 100% rename from xml/zh/chapter1/section2/subsection5.xml rename to xml/zh_CN/chapter1/section2/subsection5.xml diff --git a/xml/zh/chapter1/section2/subsection6.xml b/xml/zh_CN/chapter1/section2/subsection6.xml similarity index 100% rename from xml/zh/chapter1/section2/subsection6.xml rename to xml/zh_CN/chapter1/section2/subsection6.xml diff --git a/xml/zh/chapter1/section3/section3.xml b/xml/zh_CN/chapter1/section3/section3.xml similarity index 100% rename from xml/zh/chapter1/section3/section3.xml rename to xml/zh_CN/chapter1/section3/section3.xml diff --git a/xml/zh/chapter1/section3/subsection1.xml b/xml/zh_CN/chapter1/section3/subsection1.xml similarity index 100% rename from xml/zh/chapter1/section3/subsection1.xml rename to xml/zh_CN/chapter1/section3/subsection1.xml diff --git a/xml/zh/chapter1/section3/subsection2.xml b/xml/zh_CN/chapter1/section3/subsection2.xml similarity index 100% rename from xml/zh/chapter1/section3/subsection2.xml rename to xml/zh_CN/chapter1/section3/subsection2.xml diff --git a/xml/zh/chapter1/section3/subsection3.xml b/xml/zh_CN/chapter1/section3/subsection3.xml similarity index 100% rename from xml/zh/chapter1/section3/subsection3.xml rename to xml/zh_CN/chapter1/section3/subsection3.xml diff --git a/xml/zh/chapter1/section3/subsection4.xml b/xml/zh_CN/chapter1/section3/subsection4.xml similarity index 100% rename from xml/zh/chapter1/section3/subsection4.xml rename to xml/zh_CN/chapter1/section3/subsection4.xml diff --git a/xml/zh/chapter2/chapter2.xml b/xml/zh_CN/chapter2/chapter2.xml similarity index 100% rename from xml/zh/chapter2/chapter2.xml rename to xml/zh_CN/chapter2/chapter2.xml diff --git a/xml/zh/chapter2/section1/section1.xml b/xml/zh_CN/chapter2/section1/section1.xml similarity index 100% rename from xml/zh/chapter2/section1/section1.xml rename to xml/zh_CN/chapter2/section1/section1.xml diff --git a/xml/zh/chapter2/section1/subsection1.xml b/xml/zh_CN/chapter2/section1/subsection1.xml similarity index 100% rename from xml/zh/chapter2/section1/subsection1.xml rename to xml/zh_CN/chapter2/section1/subsection1.xml diff --git a/xml/zh/chapter2/section1/subsection2.xml b/xml/zh_CN/chapter2/section1/subsection2.xml similarity index 100% rename from xml/zh/chapter2/section1/subsection2.xml rename to xml/zh_CN/chapter2/section1/subsection2.xml diff --git a/xml/zh/chapter2/section1/subsection3.xml b/xml/zh_CN/chapter2/section1/subsection3.xml similarity index 100% rename from xml/zh/chapter2/section1/subsection3.xml rename to xml/zh_CN/chapter2/section1/subsection3.xml diff --git a/xml/zh/chapter2/section1/subsection4.xml b/xml/zh_CN/chapter2/section1/subsection4.xml similarity index 100% rename from xml/zh/chapter2/section1/subsection4.xml rename to xml/zh_CN/chapter2/section1/subsection4.xml diff --git a/xml/zh/chapter2/section2/section2.xml b/xml/zh_CN/chapter2/section2/section2.xml similarity index 100% rename from xml/zh/chapter2/section2/section2.xml rename to xml/zh_CN/chapter2/section2/section2.xml diff --git a/xml/zh/chapter2/section2/subsection1.xml b/xml/zh_CN/chapter2/section2/subsection1.xml similarity index 100% rename from xml/zh/chapter2/section2/subsection1.xml rename to xml/zh_CN/chapter2/section2/subsection1.xml diff --git a/xml/zh/chapter2/section2/subsection2.xml b/xml/zh_CN/chapter2/section2/subsection2.xml similarity index 100% rename from xml/zh/chapter2/section2/subsection2.xml rename to xml/zh_CN/chapter2/section2/subsection2.xml diff --git a/xml/zh/chapter2/section2/subsection3.xml b/xml/zh_CN/chapter2/section2/subsection3.xml similarity index 100% rename from xml/zh/chapter2/section2/subsection3.xml rename to xml/zh_CN/chapter2/section2/subsection3.xml diff --git a/xml/zh/chapter2/section2/subsection4.xml b/xml/zh_CN/chapter2/section2/subsection4.xml similarity index 100% rename from xml/zh/chapter2/section2/subsection4.xml rename to xml/zh_CN/chapter2/section2/subsection4.xml diff --git a/xml/zh/chapter2/section3/section3.xml b/xml/zh_CN/chapter2/section3/section3.xml similarity index 100% rename from xml/zh/chapter2/section3/section3.xml rename to xml/zh_CN/chapter2/section3/section3.xml diff --git a/xml/zh/chapter2/section3/subsection1.xml b/xml/zh_CN/chapter2/section3/subsection1.xml similarity index 100% rename from xml/zh/chapter2/section3/subsection1.xml rename to xml/zh_CN/chapter2/section3/subsection1.xml diff --git a/xml/zh/chapter2/section3/subsection2.xml b/xml/zh_CN/chapter2/section3/subsection2.xml similarity index 100% rename from xml/zh/chapter2/section3/subsection2.xml rename to xml/zh_CN/chapter2/section3/subsection2.xml diff --git a/xml/zh/chapter2/section3/subsection3.xml b/xml/zh_CN/chapter2/section3/subsection3.xml similarity index 100% rename from xml/zh/chapter2/section3/subsection3.xml rename to xml/zh_CN/chapter2/section3/subsection3.xml diff --git a/xml/zh/chapter2/section3/subsection4.xml b/xml/zh_CN/chapter2/section3/subsection4.xml similarity index 100% rename from xml/zh/chapter2/section3/subsection4.xml rename to xml/zh_CN/chapter2/section3/subsection4.xml diff --git a/xml/zh/chapter2/section4/section4.xml b/xml/zh_CN/chapter2/section4/section4.xml similarity index 100% rename from xml/zh/chapter2/section4/section4.xml rename to xml/zh_CN/chapter2/section4/section4.xml diff --git a/xml/zh/chapter2/section4/subsection1.xml b/xml/zh_CN/chapter2/section4/subsection1.xml similarity index 100% rename from xml/zh/chapter2/section4/subsection1.xml rename to xml/zh_CN/chapter2/section4/subsection1.xml diff --git a/xml/zh/chapter2/section4/subsection2.xml b/xml/zh_CN/chapter2/section4/subsection2.xml similarity index 100% rename from xml/zh/chapter2/section4/subsection2.xml rename to xml/zh_CN/chapter2/section4/subsection2.xml diff --git a/xml/zh/chapter2/section4/subsection3.xml b/xml/zh_CN/chapter2/section4/subsection3.xml similarity index 100% rename from xml/zh/chapter2/section4/subsection3.xml rename to xml/zh_CN/chapter2/section4/subsection3.xml diff --git a/xml/zh/chapter2/section5/section5.xml b/xml/zh_CN/chapter2/section5/section5.xml similarity index 100% rename from xml/zh/chapter2/section5/section5.xml rename to xml/zh_CN/chapter2/section5/section5.xml diff --git a/xml/zh/chapter2/section5/subsection1.xml b/xml/zh_CN/chapter2/section5/subsection1.xml similarity index 100% rename from xml/zh/chapter2/section5/subsection1.xml rename to xml/zh_CN/chapter2/section5/subsection1.xml diff --git a/xml/zh/chapter2/section5/subsection2.xml b/xml/zh_CN/chapter2/section5/subsection2.xml similarity index 100% rename from xml/zh/chapter2/section5/subsection2.xml rename to xml/zh_CN/chapter2/section5/subsection2.xml diff --git a/xml/zh/chapter2/section5/subsection3.xml b/xml/zh_CN/chapter2/section5/subsection3.xml similarity index 100% rename from xml/zh/chapter2/section5/subsection3.xml rename to xml/zh_CN/chapter2/section5/subsection3.xml diff --git a/xml/zh/chapter3/chapter3.xml b/xml/zh_CN/chapter3/chapter3.xml similarity index 100% rename from xml/zh/chapter3/chapter3.xml rename to xml/zh_CN/chapter3/chapter3.xml diff --git a/xml/zh/chapter3/section1/section1.xml b/xml/zh_CN/chapter3/section1/section1.xml similarity index 100% rename from xml/zh/chapter3/section1/section1.xml rename to xml/zh_CN/chapter3/section1/section1.xml diff --git a/xml/zh/chapter3/section1/subsection1.xml b/xml/zh_CN/chapter3/section1/subsection1.xml similarity index 100% rename from xml/zh/chapter3/section1/subsection1.xml rename to xml/zh_CN/chapter3/section1/subsection1.xml diff --git a/xml/zh/chapter3/section1/subsection2.xml b/xml/zh_CN/chapter3/section1/subsection2.xml similarity index 100% rename from xml/zh/chapter3/section1/subsection2.xml rename to xml/zh_CN/chapter3/section1/subsection2.xml diff --git a/xml/zh/chapter3/section1/subsection3.xml b/xml/zh_CN/chapter3/section1/subsection3.xml similarity index 100% rename from xml/zh/chapter3/section1/subsection3.xml rename to xml/zh_CN/chapter3/section1/subsection3.xml diff --git a/xml/zh/chapter3/section2/section2.xml b/xml/zh_CN/chapter3/section2/section2.xml similarity index 100% rename from xml/zh/chapter3/section2/section2.xml rename to xml/zh_CN/chapter3/section2/section2.xml diff --git a/xml/zh/chapter3/section2/subsection1.xml b/xml/zh_CN/chapter3/section2/subsection1.xml similarity index 100% rename from xml/zh/chapter3/section2/subsection1.xml rename to xml/zh_CN/chapter3/section2/subsection1.xml diff --git a/xml/zh/chapter3/section2/subsection2.xml b/xml/zh_CN/chapter3/section2/subsection2.xml similarity index 100% rename from xml/zh/chapter3/section2/subsection2.xml rename to xml/zh_CN/chapter3/section2/subsection2.xml diff --git a/xml/zh/chapter3/section2/subsection3.xml b/xml/zh_CN/chapter3/section2/subsection3.xml similarity index 100% rename from xml/zh/chapter3/section2/subsection3.xml rename to xml/zh_CN/chapter3/section2/subsection3.xml diff --git a/xml/zh/chapter3/section2/subsection4.xml b/xml/zh_CN/chapter3/section2/subsection4.xml similarity index 100% rename from xml/zh/chapter3/section2/subsection4.xml rename to xml/zh_CN/chapter3/section2/subsection4.xml diff --git a/xml/zh/chapter3/section2/subsection5.xml b/xml/zh_CN/chapter3/section2/subsection5.xml similarity index 100% rename from xml/zh/chapter3/section2/subsection5.xml rename to xml/zh_CN/chapter3/section2/subsection5.xml diff --git a/xml/zh/chapter3/section3/section3.xml b/xml/zh_CN/chapter3/section3/section3.xml similarity index 100% rename from xml/zh/chapter3/section3/section3.xml rename to xml/zh_CN/chapter3/section3/section3.xml diff --git a/xml/zh/chapter3/section3/subsection1.xml b/xml/zh_CN/chapter3/section3/subsection1.xml similarity index 100% rename from xml/zh/chapter3/section3/subsection1.xml rename to xml/zh_CN/chapter3/section3/subsection1.xml diff --git a/xml/zh/chapter3/section3/subsection2.xml b/xml/zh_CN/chapter3/section3/subsection2.xml similarity index 100% rename from xml/zh/chapter3/section3/subsection2.xml rename to xml/zh_CN/chapter3/section3/subsection2.xml diff --git a/xml/zh/chapter3/section3/subsection3.xml b/xml/zh_CN/chapter3/section3/subsection3.xml similarity index 100% rename from xml/zh/chapter3/section3/subsection3.xml rename to xml/zh_CN/chapter3/section3/subsection3.xml diff --git a/xml/zh/chapter3/section3/subsection4.xml b/xml/zh_CN/chapter3/section3/subsection4.xml similarity index 100% rename from xml/zh/chapter3/section3/subsection4.xml rename to xml/zh_CN/chapter3/section3/subsection4.xml diff --git a/xml/zh/chapter3/section3/subsection5.xml b/xml/zh_CN/chapter3/section3/subsection5.xml similarity index 100% rename from xml/zh/chapter3/section3/subsection5.xml rename to xml/zh_CN/chapter3/section3/subsection5.xml diff --git a/xml/zh/chapter3/section4/section4.xml b/xml/zh_CN/chapter3/section4/section4.xml similarity index 100% rename from xml/zh/chapter3/section4/section4.xml rename to xml/zh_CN/chapter3/section4/section4.xml diff --git a/xml/zh/chapter3/section4/subsection1.xml b/xml/zh_CN/chapter3/section4/subsection1.xml similarity index 100% rename from xml/zh/chapter3/section4/subsection1.xml rename to xml/zh_CN/chapter3/section4/subsection1.xml diff --git a/xml/zh/chapter3/section4/subsection2.xml b/xml/zh_CN/chapter3/section4/subsection2.xml similarity index 100% rename from xml/zh/chapter3/section4/subsection2.xml rename to xml/zh_CN/chapter3/section4/subsection2.xml diff --git a/xml/zh/chapter3/section5/section5.xml b/xml/zh_CN/chapter3/section5/section5.xml similarity index 100% rename from xml/zh/chapter3/section5/section5.xml rename to xml/zh_CN/chapter3/section5/section5.xml diff --git a/xml/zh/chapter3/section5/subsection1.xml b/xml/zh_CN/chapter3/section5/subsection1.xml similarity index 100% rename from xml/zh/chapter3/section5/subsection1.xml rename to xml/zh_CN/chapter3/section5/subsection1.xml diff --git a/xml/zh/chapter3/section5/subsection2.xml b/xml/zh_CN/chapter3/section5/subsection2.xml similarity index 100% rename from xml/zh/chapter3/section5/subsection2.xml rename to xml/zh_CN/chapter3/section5/subsection2.xml diff --git a/xml/zh/chapter3/section5/subsection3.xml b/xml/zh_CN/chapter3/section5/subsection3.xml similarity index 100% rename from xml/zh/chapter3/section5/subsection3.xml rename to xml/zh_CN/chapter3/section5/subsection3.xml diff --git a/xml/zh/chapter3/section5/subsection4.xml b/xml/zh_CN/chapter3/section5/subsection4.xml similarity index 100% rename from xml/zh/chapter3/section5/subsection4.xml rename to xml/zh_CN/chapter3/section5/subsection4.xml diff --git a/xml/zh/chapter3/section5/subsection5.xml b/xml/zh_CN/chapter3/section5/subsection5.xml similarity index 100% rename from xml/zh/chapter3/section5/subsection5.xml rename to xml/zh_CN/chapter3/section5/subsection5.xml diff --git a/xml/zh/chapter4/chapter4.xml b/xml/zh_CN/chapter4/chapter4.xml similarity index 100% rename from xml/zh/chapter4/chapter4.xml rename to xml/zh_CN/chapter4/chapter4.xml diff --git a/xml/zh/chapter4/section1/section1.xml b/xml/zh_CN/chapter4/section1/section1.xml similarity index 100% rename from xml/zh/chapter4/section1/section1.xml rename to xml/zh_CN/chapter4/section1/section1.xml diff --git a/xml/zh/chapter4/section1/subsection1.xml b/xml/zh_CN/chapter4/section1/subsection1.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection1.xml rename to xml/zh_CN/chapter4/section1/subsection1.xml diff --git a/xml/zh/chapter4/section1/subsection2.xml b/xml/zh_CN/chapter4/section1/subsection2.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection2.xml rename to xml/zh_CN/chapter4/section1/subsection2.xml diff --git a/xml/zh/chapter4/section1/subsection3.xml b/xml/zh_CN/chapter4/section1/subsection3.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection3.xml rename to xml/zh_CN/chapter4/section1/subsection3.xml diff --git a/xml/zh/chapter4/section1/subsection4.xml b/xml/zh_CN/chapter4/section1/subsection4.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection4.xml rename to xml/zh_CN/chapter4/section1/subsection4.xml diff --git a/xml/zh/chapter4/section1/subsection5.xml b/xml/zh_CN/chapter4/section1/subsection5.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection5.xml rename to xml/zh_CN/chapter4/section1/subsection5.xml diff --git a/xml/zh/chapter4/section1/subsection6.xml b/xml/zh_CN/chapter4/section1/subsection6.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection6.xml rename to xml/zh_CN/chapter4/section1/subsection6.xml diff --git a/xml/zh/chapter4/section1/subsection7.xml b/xml/zh_CN/chapter4/section1/subsection7.xml similarity index 100% rename from xml/zh/chapter4/section1/subsection7.xml rename to xml/zh_CN/chapter4/section1/subsection7.xml diff --git a/xml/zh/chapter4/section2/section2.xml b/xml/zh_CN/chapter4/section2/section2.xml similarity index 100% rename from xml/zh/chapter4/section2/section2.xml rename to xml/zh_CN/chapter4/section2/section2.xml diff --git a/xml/zh/chapter4/section2/subsection1.xml b/xml/zh_CN/chapter4/section2/subsection1.xml similarity index 100% rename from xml/zh/chapter4/section2/subsection1.xml rename to xml/zh_CN/chapter4/section2/subsection1.xml diff --git a/xml/zh/chapter4/section2/subsection2.xml b/xml/zh_CN/chapter4/section2/subsection2.xml similarity index 100% rename from xml/zh/chapter4/section2/subsection2.xml rename to xml/zh_CN/chapter4/section2/subsection2.xml diff --git a/xml/zh/chapter4/section2/subsection3.xml b/xml/zh_CN/chapter4/section2/subsection3.xml similarity index 100% rename from xml/zh/chapter4/section2/subsection3.xml rename to xml/zh_CN/chapter4/section2/subsection3.xml diff --git a/xml/zh/chapter4/section3/section3.xml b/xml/zh_CN/chapter4/section3/section3.xml similarity index 100% rename from xml/zh/chapter4/section3/section3.xml rename to xml/zh_CN/chapter4/section3/section3.xml diff --git a/xml/zh/chapter4/section3/subsection1.xml b/xml/zh_CN/chapter4/section3/subsection1.xml similarity index 100% rename from xml/zh/chapter4/section3/subsection1.xml rename to xml/zh_CN/chapter4/section3/subsection1.xml diff --git a/xml/zh/chapter4/section3/subsection2.xml b/xml/zh_CN/chapter4/section3/subsection2.xml similarity index 100% rename from xml/zh/chapter4/section3/subsection2.xml rename to xml/zh_CN/chapter4/section3/subsection2.xml diff --git a/xml/zh/chapter4/section3/subsection3.xml b/xml/zh_CN/chapter4/section3/subsection3.xml similarity index 100% rename from xml/zh/chapter4/section3/subsection3.xml rename to xml/zh_CN/chapter4/section3/subsection3.xml diff --git a/xml/zh/chapter4/section4/section4.xml b/xml/zh_CN/chapter4/section4/section4.xml similarity index 100% rename from xml/zh/chapter4/section4/section4.xml rename to xml/zh_CN/chapter4/section4/section4.xml diff --git a/xml/zh/chapter4/section4/subsection1.xml b/xml/zh_CN/chapter4/section4/subsection1.xml similarity index 100% rename from xml/zh/chapter4/section4/subsection1.xml rename to xml/zh_CN/chapter4/section4/subsection1.xml diff --git a/xml/zh/chapter4/section4/subsection2.xml b/xml/zh_CN/chapter4/section4/subsection2.xml similarity index 100% rename from xml/zh/chapter4/section4/subsection2.xml rename to xml/zh_CN/chapter4/section4/subsection2.xml diff --git a/xml/zh/chapter4/section4/subsection3.xml b/xml/zh_CN/chapter4/section4/subsection3.xml similarity index 100% rename from xml/zh/chapter4/section4/subsection3.xml rename to xml/zh_CN/chapter4/section4/subsection3.xml diff --git a/xml/zh/chapter4/section4/subsection4.xml b/xml/zh_CN/chapter4/section4/subsection4.xml similarity index 100% rename from xml/zh/chapter4/section4/subsection4.xml rename to xml/zh_CN/chapter4/section4/subsection4.xml diff --git a/xml/zh/chapter5/chapter5.xml b/xml/zh_CN/chapter5/chapter5.xml similarity index 100% rename from xml/zh/chapter5/chapter5.xml rename to xml/zh_CN/chapter5/chapter5.xml diff --git a/xml/zh/chapter5/section1/section1.xml b/xml/zh_CN/chapter5/section1/section1.xml similarity index 100% rename from xml/zh/chapter5/section1/section1.xml rename to xml/zh_CN/chapter5/section1/section1.xml diff --git a/xml/zh/chapter5/section1/subsection1.xml b/xml/zh_CN/chapter5/section1/subsection1.xml similarity index 100% rename from xml/zh/chapter5/section1/subsection1.xml rename to xml/zh_CN/chapter5/section1/subsection1.xml diff --git a/xml/zh/chapter5/section1/subsection2.xml b/xml/zh_CN/chapter5/section1/subsection2.xml similarity index 100% rename from xml/zh/chapter5/section1/subsection2.xml rename to xml/zh_CN/chapter5/section1/subsection2.xml diff --git a/xml/zh/chapter5/section1/subsection3.xml b/xml/zh_CN/chapter5/section1/subsection3.xml similarity index 100% rename from xml/zh/chapter5/section1/subsection3.xml rename to xml/zh_CN/chapter5/section1/subsection3.xml diff --git a/xml/zh/chapter5/section1/subsection4.xml b/xml/zh_CN/chapter5/section1/subsection4.xml similarity index 100% rename from xml/zh/chapter5/section1/subsection4.xml rename to xml/zh_CN/chapter5/section1/subsection4.xml diff --git a/xml/zh/chapter5/section1/subsection5.xml b/xml/zh_CN/chapter5/section1/subsection5.xml similarity index 100% rename from xml/zh/chapter5/section1/subsection5.xml rename to xml/zh_CN/chapter5/section1/subsection5.xml diff --git a/xml/zh/chapter5/section2/section2.xml b/xml/zh_CN/chapter5/section2/section2.xml similarity index 100% rename from xml/zh/chapter5/section2/section2.xml rename to xml/zh_CN/chapter5/section2/section2.xml diff --git a/xml/zh/chapter5/section2/subsection1.xml b/xml/zh_CN/chapter5/section2/subsection1.xml similarity index 100% rename from xml/zh/chapter5/section2/subsection1.xml rename to xml/zh_CN/chapter5/section2/subsection1.xml diff --git a/xml/zh/chapter5/section2/subsection2.xml b/xml/zh_CN/chapter5/section2/subsection2.xml similarity index 100% rename from xml/zh/chapter5/section2/subsection2.xml rename to xml/zh_CN/chapter5/section2/subsection2.xml diff --git a/xml/zh/chapter5/section2/subsection3.xml b/xml/zh_CN/chapter5/section2/subsection3.xml similarity index 100% rename from xml/zh/chapter5/section2/subsection3.xml rename to xml/zh_CN/chapter5/section2/subsection3.xml diff --git a/xml/zh/chapter5/section2/subsection4.xml b/xml/zh_CN/chapter5/section2/subsection4.xml similarity index 100% rename from xml/zh/chapter5/section2/subsection4.xml rename to xml/zh_CN/chapter5/section2/subsection4.xml diff --git a/xml/zh/chapter5/section3/section3.xml b/xml/zh_CN/chapter5/section3/section3.xml similarity index 100% rename from xml/zh/chapter5/section3/section3.xml rename to xml/zh_CN/chapter5/section3/section3.xml diff --git a/xml/zh/chapter5/section3/subsection1.xml b/xml/zh_CN/chapter5/section3/subsection1.xml similarity index 100% rename from xml/zh/chapter5/section3/subsection1.xml rename to xml/zh_CN/chapter5/section3/subsection1.xml diff --git a/xml/zh/chapter5/section3/subsection2.xml b/xml/zh_CN/chapter5/section3/subsection2.xml similarity index 100% rename from xml/zh/chapter5/section3/subsection2.xml rename to xml/zh_CN/chapter5/section3/subsection2.xml diff --git a/xml/zh/chapter5/section4/section4.xml b/xml/zh_CN/chapter5/section4/section4.xml similarity index 100% rename from xml/zh/chapter5/section4/section4.xml rename to xml/zh_CN/chapter5/section4/section4.xml diff --git a/xml/zh/chapter5/section4/subsection1.xml b/xml/zh_CN/chapter5/section4/subsection1.xml similarity index 100% rename from xml/zh/chapter5/section4/subsection1.xml rename to xml/zh_CN/chapter5/section4/subsection1.xml diff --git a/xml/zh/chapter5/section4/subsection2.xml b/xml/zh_CN/chapter5/section4/subsection2.xml similarity index 100% rename from xml/zh/chapter5/section4/subsection2.xml rename to xml/zh_CN/chapter5/section4/subsection2.xml diff --git a/xml/zh/chapter5/section4/subsection3.xml b/xml/zh_CN/chapter5/section4/subsection3.xml similarity index 100% rename from xml/zh/chapter5/section4/subsection3.xml rename to xml/zh_CN/chapter5/section4/subsection3.xml diff --git a/xml/zh/chapter5/section4/subsection4.xml b/xml/zh_CN/chapter5/section4/subsection4.xml similarity index 100% rename from xml/zh/chapter5/section4/subsection4.xml rename to xml/zh_CN/chapter5/section4/subsection4.xml diff --git a/xml/zh/chapter5/section5/section5.xml b/xml/zh_CN/chapter5/section5/section5.xml similarity index 100% rename from xml/zh/chapter5/section5/section5.xml rename to xml/zh_CN/chapter5/section5/section5.xml diff --git a/xml/zh/chapter5/section5/subsection1.xml b/xml/zh_CN/chapter5/section5/subsection1.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection1.xml rename to xml/zh_CN/chapter5/section5/subsection1.xml diff --git a/xml/zh/chapter5/section5/subsection2.xml b/xml/zh_CN/chapter5/section5/subsection2.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection2.xml rename to xml/zh_CN/chapter5/section5/subsection2.xml diff --git a/xml/zh/chapter5/section5/subsection3.xml b/xml/zh_CN/chapter5/section5/subsection3.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection3.xml rename to xml/zh_CN/chapter5/section5/subsection3.xml diff --git a/xml/zh/chapter5/section5/subsection4.xml b/xml/zh_CN/chapter5/section5/subsection4.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection4.xml rename to xml/zh_CN/chapter5/section5/subsection4.xml diff --git a/xml/zh/chapter5/section5/subsection5.xml b/xml/zh_CN/chapter5/section5/subsection5.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection5.xml rename to xml/zh_CN/chapter5/section5/subsection5.xml diff --git a/xml/zh/chapter5/section5/subsection6.xml b/xml/zh_CN/chapter5/section5/subsection6.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection6.xml rename to xml/zh_CN/chapter5/section5/subsection6.xml diff --git a/xml/zh/chapter5/section5/subsection7.xml b/xml/zh_CN/chapter5/section5/subsection7.xml similarity index 100% rename from xml/zh/chapter5/section5/subsection7.xml rename to xml/zh_CN/chapter5/section5/subsection7.xml diff --git a/xml/zh/others/02foreword02.xml b/xml/zh_CN/others/02foreword02.xml similarity index 100% rename from xml/zh/others/02foreword02.xml rename to xml/zh_CN/others/02foreword02.xml diff --git a/xml/zh/others/02foreword84.xml b/xml/zh_CN/others/02foreword84.xml similarity index 100% rename from xml/zh/others/02foreword84.xml rename to xml/zh_CN/others/02foreword84.xml diff --git a/xml/zh/others/03prefaces03.xml b/xml/zh_CN/others/03prefaces03.xml similarity index 100% rename from xml/zh/others/03prefaces03.xml rename to xml/zh_CN/others/03prefaces03.xml diff --git a/xml/zh/others/03prefaces96.xml b/xml/zh_CN/others/03prefaces96.xml similarity index 100% rename from xml/zh/others/03prefaces96.xml rename to xml/zh_CN/others/03prefaces96.xml diff --git a/xml/zh/others/04acknowledgements04.xml b/xml/zh_CN/others/04acknowledgements04.xml similarity index 100% rename from xml/zh/others/04acknowledgements04.xml rename to xml/zh_CN/others/04acknowledgements04.xml diff --git a/xml/zh/others/06see06.xml b/xml/zh_CN/others/06see06.xml similarity index 100% rename from xml/zh/others/06see06.xml rename to xml/zh_CN/others/06see06.xml diff --git a/xml/zh/others/97references97.xml b/xml/zh_CN/others/97references97.xml similarity index 100% rename from xml/zh/others/97references97.xml rename to xml/zh_CN/others/97references97.xml diff --git a/xml/zh/others/98indexpreface98.xml b/xml/zh_CN/others/98indexpreface98.xml similarity index 100% rename from xml/zh/others/98indexpreface98.xml rename to xml/zh_CN/others/98indexpreface98.xml diff --git a/xml/zh/others/99making99.xml b/xml/zh_CN/others/99making99.xml similarity index 100% rename from xml/zh/others/99making99.xml rename to xml/zh_CN/others/99making99.xml From 9ca1eae9391c8cde5b584cb2c95129df3a2d3d78 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 17:12:42 +0800 Subject: [PATCH 46/55] refactor: clean up code and improve error logging in recurTranslate.ts --- i18n/controllers/recurTranslate.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 718678a5f..639335697 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -29,7 +29,6 @@ const ignoredTags = [ "HISTORY", "REF", "FIGURE", - ]; const MAXLEN = Number(process.env.MAX_LEN) || 3000; @@ -44,7 +43,7 @@ const errorMessages = new Set(); // Track errors by file for summary reporting const fileErrors: Record = {}; -function logError(message: string, error?: any, filePath?: string) { +function logError(message: string, error: any, filePath: string) { // Create a unique key for this error message const errorKey = message + (error ? error.toString() : ""); // Only log if we haven't seen this exact message before @@ -90,7 +89,7 @@ async function translate(langCode: string, filePath: string): Promise { throw new Error('Undefined language'); } - if (!troubleshoot) assistant = await createAssistant(langCode, language, ai as any); + if (!troubleshoot) assistant = await createAssistant(langCode, language, ai); // Generate output path by replacing "/en/" with "/../i18n/translation_output/zh_CN/" in the path const output_path = filePath.replace( @@ -111,9 +110,7 @@ async function translate(langCode: string, filePath: string): Promise { fs.writeFileSync(output_path, translated); console.log(`Translation saved to ${output_path}`); } catch (parseErr) { - logError(`Error translating file ${filePath}:`, parseErr, filePath); - // Re-throw the error to propagate it to the caller - throw parseErr; + logError(`Error processing file ${filePath}:`, parseErr, filePath); } finally { if (assistant) { await ai.beta.assistants.del(assistant.id).catch(err => { From 2653bd98d84e18f9b37d49f3a7c8811f1a8294df Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 17:13:04 +0800 Subject: [PATCH 47/55] moved constants to config.ts --- i18n/config.ts | 8 ++++++++ i18n/index.ts | 13 +++++-------- javascript/index.js | 9 +++------ 3 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 i18n/config.ts diff --git a/i18n/config.ts b/i18n/config.ts new file mode 100644 index 000000000..f1c584dab --- /dev/null +++ b/i18n/config.ts @@ -0,0 +1,8 @@ +// maximum permissible concurrent translations +const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; + +// log file configs +const translationSummaryPrefix = "translation-summary"; +const jsonSummaryPrefix = "json-summary"; + +export {max_trans_num, translationSummaryPrefix, jsonSummaryPrefix} \ No newline at end of file diff --git a/i18n/index.ts b/i18n/index.ts index aba8d4e33..a0f6249e5 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,18 +1,19 @@ import PathGenerator from "./controllers/path.ts"; import translate, { getFileErrors } from "./controllers/recurTranslate.ts"; -import fs from "fs"; +import fs, { Dirent } from "fs"; import util from "util"; import path from "path"; import { fileURLToPath } from "url"; import { dirname } from "path"; import OpenAI from "openai"; +import { max_trans_num, translationSummaryPrefix } from "./config.ts"; // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const readdir = util.promisify(fs.readdir); -const getDirectories = async source => +const getDirectories = async (source: fs.PathLike) => (await readdir(source, { withFileTypes: true })) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); @@ -26,8 +27,6 @@ let failureCount = 0; let processedCount = 0; let failures: { file: string; error: any }[] = []; -const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; - // Function to save summary log - can be called from signal handlers async function saveSummaryLog() { try { @@ -115,7 +114,7 @@ Success rate: ${filesToTranslate.length > 0 ? ((successCount / filesToTranslate. fs.mkdirSync(logDir, { recursive: true }); } - const logPath = path.join(logDir, `translation-summary-${timestamp}.log`); + const logPath = path.join(logDir, `${translationSummaryPrefix}-${timestamp}.log`); fs.writeFileSync(logPath, summaryLog); console.log( `Summary log saved to logs/translation-summary-${timestamp}.log` @@ -190,7 +189,7 @@ async function needsTranslation( const enStats = await fs.promises.stat(enFilePath); return enStats.mtime > cnStats.mtime; } catch (error) { - return true; + throw error; } } @@ -313,7 +312,6 @@ export default async function fancyName(path: string, language: string) { if (result.value.success) { successCount++; } else { - failureCount++; failures.push({ file: result.value.file, error: result.value.error @@ -324,7 +322,6 @@ export default async function fancyName(path: string, language: string) { } } else { // This is for Promise rejections (should be rare with our error handling) - failureCount++; failures.push({ file: "Unknown file in batch", error: result.reason diff --git a/javascript/index.js b/javascript/index.js index 7a72ca70e..0d9852ce4 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -37,15 +37,12 @@ import { setupSnippetsJs } from "./processingFunctions/processSnippetJs"; import { getAnswers } from "./processingFunctions/processExercisePdf"; // json (for cadet frontend) -import { testIndexSearch } from "./searchRewriteTest"; import { parseXmlJson } from "./parseXmlJson"; import { writeRewritedSearchData } from "./searchRewrite"; import { setupSnippetsJson } from "./processingFunctions/processSnippetJson"; import { createTocJson } from "./generateTocJson"; import { setupReferencesJson } from "./processingFunctions/processReferenceJson"; -import { SourceTextModule } from "vm"; -import { threadId } from "worker_threads"; -import { exitCode } from "process"; +import { jsonSummaryPrefix } from "../i18n/config"; export let parseType; let version; @@ -407,10 +404,10 @@ async function main() { fs.mkdirSync(logDir, { recursive: true }); } - const logPath = path.join(logDir, `json-summary-${timestamp}.log`); + const logPath = path.join(logDir, `${jsonSummaryPrefix}-${timestamp}.log`); fs.writeFileSync(logPath, summaryLog); console.log( - `Summary log saved to logs/translation-summary-${timestamp}.log` + `Summary log saved to logs/json-summary-${timestamp}.log` ); } catch (logError) { console.error("Failed to save log file:", logError); From e6a13cf0a74bddbbad585115cfbccc3f1ce619f9 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 17:13:56 +0800 Subject: [PATCH 48/55] refactor: update OpenAI import and enhance error handling for file streams --- i18n/initializers/initialize.ts | 13 +++++++++++-- i18n/package-lock.json | 8 ++++---- i18n/package.json | 11 +++++++---- i18n/yarn.lock | 8 ++++---- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index 062706d76..1eec3ad59 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -1,6 +1,7 @@ import fs from "fs"; -import OpenAI from "openai/index.mjs"; +import OpenAI from "openai"; import path, { dirname } from "path"; +import Stream from "stream"; import { fileURLToPath } from "url"; // Get the directory name of the current module @@ -24,7 +25,15 @@ export default async function createAssistant(langCode: string, language: string }); const fileStreams = [path.join(__dirname, "../ai_files", langCode, "dictionary.txt")].map( - path => fs.createReadStream(path) + filePath => { + const stream = fs.createReadStream(filePath); + + stream.on('error', err => { + throw new Error(`Failed to read dictionary file at ${filePath}: ${err.message}`) + }) + + return stream; + } ); // Create a vector store including our two files. diff --git a/i18n/package-lock.json b/i18n/package-lock.json index a3d0f5e26..104fe1889 100644 --- a/i18n/package-lock.json +++ b/i18n/package-lock.json @@ -5,16 +5,16 @@ "packages": { "": { "devDependencies": { - "@types/node": "^22.14.1", + "@types/node": "^22.15.3", "dotenv": "^16.4.7", "openai": "^4.81.0", "sax": "^1.4.1" } }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/i18n/package.json b/i18n/package.json index 705a9576f..551836898 100644 --- a/i18n/package.json +++ b/i18n/package.json @@ -10,10 +10,13 @@ "url": "https://github.com/coder114514" } ], + "dependencies": { + "sicp": "^1.1.4", + "openai": "^4.96.2", + "sax": "^1.4.1", + "dotenv": "^16.5.0" + }, "devDependencies": { - "@types/node": "^22.14.1", - "dotenv": "^16.4.7", - "openai": "^4.81.0", - "sax": "^1.4.1" + "@types/node": "^22.15.3" } } diff --git a/i18n/yarn.lock b/i18n/yarn.lock index ad8c233b0..9d4d14a44 100644 --- a/i18n/yarn.lock +++ b/i18n/yarn.lock @@ -10,10 +10,10 @@ "@types/node" "*" form-data "^4.0.0" -"@types/node@*", "@types/node@^22.14.1": - version "22.14.1" - resolved "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz" - integrity sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw== +"@types/node@*", "@types/node@^22.15.3": + version "22.15.3" + resolved "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz" + integrity sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw== dependencies: undici-types "~6.21.0" From 48d7c93c13e11ad55170adddfa406e17b1e78b49 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 21:40:35 +0800 Subject: [PATCH 49/55] ran prettier --- javascript/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/index.js b/javascript/index.js index 0d9852ce4..362c150eb 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -151,7 +151,7 @@ async function translateXml(filepath, filename, option) { filepath, filename.replace(/\.xml$/, "") + "" ); - ensureDirectoryExists(path.join(outputDir, filepath), err => {}); + ensureDirectoryExists(path.join(outputDir, filepath), err => { }); ensureDirectoryExists(relativeFileDir, err => { if (err) { //console.log(err); From ba25b24a1219a849212c7539ea84c89bfe7da153 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 21:57:16 +0800 Subject: [PATCH 50/55] ran prettier --- javascript/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/javascript/index.js b/javascript/index.js index 362c150eb..1490252ae 100644 --- a/javascript/index.js +++ b/javascript/index.js @@ -151,7 +151,7 @@ async function translateXml(filepath, filename, option) { filepath, filename.replace(/\.xml$/, "") + "" ); - ensureDirectoryExists(path.join(outputDir, filepath), err => { }); + ensureDirectoryExists(path.join(outputDir, filepath), err => {}); ensureDirectoryExists(relativeFileDir, err => { if (err) { //console.log(err); @@ -406,9 +406,7 @@ async function main() { const logPath = path.join(logDir, `${jsonSummaryPrefix}-${timestamp}.log`); fs.writeFileSync(logPath, summaryLog); - console.log( - `Summary log saved to logs/json-summary-${timestamp}.log` - ); + console.log(`Summary log saved to logs/json-summary-${timestamp}.log`); } catch (logError) { console.error("Failed to save log file:", logError); } From f9947905ac0b3dc6d18bd828f1a20abe99418ea8 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 22:28:26 +0800 Subject: [PATCH 51/55] refactored recurTranslate and split functionalities to different files --- i18n/config.ts | 21 +- i18n/controllers/parsers.ts | 306 +++++++++++++++++++++ i18n/controllers/recurTranslate.ts | 412 ++++------------------------- i18n/controllers/xmlUtilities.ts | 25 ++ i18n/initializers/initialize.ts | 30 ++- 5 files changed, 420 insertions(+), 374 deletions(-) create mode 100644 i18n/controllers/parsers.ts create mode 100644 i18n/controllers/xmlUtilities.ts diff --git a/i18n/config.ts b/i18n/config.ts index f1c584dab..ec4e10190 100644 --- a/i18n/config.ts +++ b/i18n/config.ts @@ -1,8 +1,19 @@ // maximum permissible concurrent translations -const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; +export const max_trans_num = Number(process.env.MAX_TRANSLATION_NO) || 5; // log file configs -const translationSummaryPrefix = "translation-summary"; -const jsonSummaryPrefix = "json-summary"; - -export {max_trans_num, translationSummaryPrefix, jsonSummaryPrefix} \ No newline at end of file +export const translationSummaryPrefix: string = "translation-summary"; +export const jsonSummaryPrefix: string = "json-summary"; +export const ignoredTags: string[] = [ + "LATEXINLINE", + "LATEX", + "SNIPPET", + "SCHEMEINLINE", + "SCHEME", + "LONG_PAGE", + "LABEL", + "HISTORY", + "REF", + "FIGURE", +]; +export const max_chunk_len: Number = Number(process.env.MAX_LEN) || 3000; \ No newline at end of file diff --git a/i18n/controllers/parsers.ts b/i18n/controllers/parsers.ts new file mode 100644 index 000000000..ec884146b --- /dev/null +++ b/i18n/controllers/parsers.ts @@ -0,0 +1,306 @@ +import sax from "sax"; +import { escapeXML, formatAttributes, strongEscapeXML } from "./xmlUtilities"; +import { Readable } from "stream"; +import { ignoredTags, max_chunk_len } from "../config"; +import fs, { PathLike } from "fs"; +import { FileLike } from "openai/uploads.mjs"; + +const MAXLEN = max_chunk_len; +const createParser = () => + (sax as any).createStream(false, { trim: false }, { strictEntities: true }); + +export async function cleanParser( + text: string, + filePath: string, + logError: Function +): Promise { + let translatedChunk = ""; + const safeText = escapeXML(text); + const textStream = Readable.from("" + safeText + ""); + await new Promise((resolve, reject) => { + // Create a SAX parser in strict mode for cleaning up translations. + const clean = createParser(); + + // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags + let currDepth = -1; + + clean.on("text", text => { + if (currDepth >= 1) { + translatedChunk += strongEscapeXML(text); + } + }); + + clean.on("opentag", node => { + currDepth++; + if (node.name != "WRAPPER" && node.name != "TRANSLATE") { + translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; + } + }); + + clean.on("closetag", tagName => { + if (tagName != "WRAPPER" && tagName != "TRANSLATE") { + translatedChunk += ``; + } + currDepth--; + }); + + clean.on("cdata", cdata => { + translatedChunk += ``; + }); + + clean.on("comment", comment => { + translatedChunk += ``; + }); + + clean.on("error", error => { + // Log only once with abbreviated content + logError(`Error validating AI response for ${filePath}`, error, filePath); + + // Attempt to recover using the internal parser + try { + clean._parser.error = null; + clean._parser.resume(); + // Continue processing despite the error + resolve(); + } catch (e) { + // Add error comment and resolve instead of rejecting + translatedChunk += ``; + resolve(); + } + }); + + clean.once("end", resolve); + + textStream.pipe(clean); + }); + return translatedChunk; +} + +export async function splitParser(filePath: PathLike, logError: Function): Promise<[boolean, string][]> { + // Create a SAX parser in strict mode to split source into chunks. + const parser = createParser(); + + const segments: [boolean, string][] = []; + await new Promise((resolve, reject) => { + // Variables to track current depth and segments. + let currentDepth = 0; + let currentSegment = ""; + + // In this context: + // - Depth 0: Before any element is opened. + // - Depth 1: The root element (). + // - Depth 2: Each direct child of the root that we want to capture. + let isRecording = false; + + parser.on("opentag", node => { + currentDepth++; + + if (currentDepth === 2 || isRecording) { + isRecording = true; + currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; + } else { + segments.push([ + false, + `<${node.name}${formatAttributes(node.attributes)}>` + ]); + } + }); + + parser.on("text", text => { + text = strongEscapeXML(text); + + if (isRecording) { + currentSegment += text; + } else { + segments.push([false, text]); + } + }); + + parser.on("cdata", cdata => { + if (isRecording) { + currentSegment += ``; + } + }); + + parser.on("closetag", tagName => { + if (isRecording) { + currentSegment += ``; + } + + if (currentDepth === 2) { + isRecording = false; + // We are closing a segment element. + if (ignoredTags.includes(tagName)) { + segments.push([false, currentSegment]); + } else { + if ( + segments.length > 0 && + segments[segments.length - 1][0] && + segments[segments.length - 1][1].length + currentSegment.length < + Number(MAXLEN) + ) { + segments[segments.length - 1][1] += currentSegment; + } else { + segments.push([true, currentSegment]); + } + } + currentSegment = ""; + } + + if (currentDepth === 1) { + // We are closing the root element. + segments.push([false, ``]); + } + + currentDepth--; + }); + + parser.on("comment", comment => { + if (isRecording) { + currentSegment += ``; + } else { + segments.push([false, ``]); + } + }); + + parser.on("end", async () => { + resolve(); + }); + + parser.on("error", err => { + logError(`Parser error in ${filePath}:`, err, filePath); + // Try to recover and continue + try { + parser._parser.error = null; + parser._parser.resume(); + } catch (resumeErr) { + logError(`Could not recover from parser error:`, resumeErr, filePath); + reject(err); + } + }); + + // Use the file path directly without modification + fs.createReadStream(filePath).pipe(parser); + }); + + return segments; +} + +export async function recurSplitParser(ori: string, filePath: PathLike, logError: Function): Promise { + let subTranslated: string[] = []; + // continue splitting the chunk + // Create a SAX parser in strict mode to split source into chunks. + await new Promise((resolve, reject) => { + const subParser = createParser(); + + let subCurrentDepth = 0; + let subCurrentSegment = ""; + const subSegments: [boolean, string][] = []; + let subIsRecording = false; + + subParser.on("opentag", node => { + if (node.name === "WRAPPER") return; + + subCurrentDepth++; + + if (subCurrentDepth === 2) subIsRecording = true; + + if (subIsRecording) { + subCurrentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; + } else { + subSegments.push([ + false, + `<${node.name}${formatAttributes(node.attributes)}>` + ]); + } + }); + + subParser.on("text", text => { + text = strongEscapeXML(text); + if (subIsRecording) { + subCurrentSegment += text; + } else if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] + ) { + subSegments[subSegments.length - 1][1] += text; + } else if ( + text.trim() === "" || + text.trim() === "," || + text.trim() === "." + ) { + subSegments.push([false, text]); + } else { + subSegments.push([true, text]); + } + }); + + subParser.on("cdata", cdata => { + if (subIsRecording) { + subCurrentSegment += ``; + } + }); + + subParser.on("closetag", tagName => { + if (tagName === "WRAPPER") { + return; + } + + subCurrentSegment += ``; + + if (subCurrentDepth === 2) { + // We are closing a segment element. + if (ignoredTags.includes(tagName)) { + subSegments.push([false, subCurrentSegment]); + } else if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] && + subSegments[subSegments.length - 1][1].length + + subCurrentSegment.length < + Number(MAXLEN) + ) { + subSegments[subSegments.length - 1][1] += subCurrentSegment; + } else { + subSegments.push([true, subCurrentSegment]); + } + subCurrentSegment = ""; + subIsRecording = false; + } + + if (subCurrentDepth === 1) { + subSegments.push([false, ``]); + subCurrentSegment = ""; + } + + subCurrentDepth--; + }); + + subParser.on("comment", comment => { + if (subIsRecording) { + subCurrentSegment += ``; + } else { + subSegments.push([false, ``]); + } + }); + + subParser.on("end", async () => + resolve() + ); + + subParser.on("error", err => { + logError(`Error in subParser for ${filePath}:`, err, filePath); + // Try to recover and continue + try { + subParser._parser.error = null; + subParser._parser.resume(); + } catch (resumeErr) { + logError(`Could not recover from parser error:`, resumeErr, filePath); + reject(err); + } + }); + + Readable.from("" + ori + "").pipe(subParser); + }); + + return subTranslated; +} diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index 639335697..c6faf4587 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -4,7 +4,7 @@ import path from "path"; import createAssistant from "../initializers/initialize"; import dotenv from "dotenv"; import sax from "sax"; -import { Readable } from "stream"; +import { cleanParser, recurSplitParser, splitParser } from "./parsers"; dotenv.config(); @@ -18,19 +18,6 @@ const ai = new OpenAI({ baseURL: process.env.AI_BASEURL }); -const ignoredTags = [ - "LATEXINLINE", - "LATEX", - "SNIPPET", - "SCHEMEINLINE", - "SCHEME", - "LONG_PAGE", - "LABEL", - "HISTORY", - "REF", - "FIGURE", -]; - const MAXLEN = Number(process.env.MAX_LEN) || 3000; // change to true to avoid calling openai api, useful for troubleshooting @@ -83,18 +70,27 @@ async function translate(langCode: string, filePath: string): Promise { try { // Use the provided file path directly without modification const input_path = filePath; - const language = languageNames.of(langCode.replace('_', '-')); + const language = languageNames.of(langCode.replace("_", "-")); if (language === undefined) { - throw new Error('Undefined language'); + throw new Error("Undefined language"); } - if (!troubleshoot) assistant = await createAssistant(langCode, language, ai); + if (!troubleshoot) + assistant = await createAssistant(langCode, language, ai); // Generate output path by replacing "/en/" with "/../i18n/translation_output/zh_CN/" in the path const output_path = filePath.replace( path.sep + "en" + path.sep, - path.sep + ".." + path.sep + "i18n" + path.sep + "translation_output" + path.sep + langCode + path.sep + path.sep + + ".." + + path.sep + + "i18n" + + path.sep + + "translation_output" + + path.sep + + langCode + + path.sep ); const translated: string = await recursivelyTranslate( @@ -134,272 +130,63 @@ async function recursivelyTranslate( return await translateChunk(ori); // translate the chunk } + let subSegments: string[] = await recurSplitParser(ori, filePath, logError); let subTranslated: string[] = []; - // continue splitting the chunk - // Create a SAX parser in strict mode to split source into chunks. - await new Promise((resolve, reject) => { - const subParser = createParser(); - - let subCurrentDepth = 0; - let subCurrentSegment = ""; - const subSegments: [boolean, string][] = []; - let subIsRecording = false; - - subParser.on("opentag", node => { - if (node.name === "WRAPPER") return; - - subCurrentDepth++; - - if (subCurrentDepth === 2) subIsRecording = true; - - if (subIsRecording) { - subCurrentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; - } else { - subSegments.push([ - false, - `<${node.name}${formatAttributes(node.attributes)}>` - ]); - } - }); - - subParser.on("text", text => { - text = strongEscapeXML(text); - if (subIsRecording) { - subCurrentSegment += text; - } else if ( - subSegments.length > 0 && - subSegments[subSegments.length - 1][0] - ) { - subSegments[subSegments.length - 1][1] += text; - } else if ( - text.trim() === "" || - text.trim() === "," || - text.trim() === "." - ) { - subSegments.push([false, text]); - } else { - subSegments.push([true, text]); - } - }); - - subParser.on("cdata", cdata => { - if (subIsRecording) { - subCurrentSegment += ``; - } - }); - - subParser.on("closetag", tagName => { - if (tagName === "WRAPPER") { - return; - } - - subCurrentSegment += ``; - - if (subCurrentDepth === 2) { - // We are closing a segment element. - if (ignoredTags.includes(tagName)) { - subSegments.push([false, subCurrentSegment]); - } else if ( - subSegments.length > 0 && - subSegments[subSegments.length - 1][0] && - subSegments[subSegments.length - 1][1].length + - subCurrentSegment.length < - MAXLEN - ) { - subSegments[subSegments.length - 1][1] += subCurrentSegment; - } else { - subSegments.push([true, subCurrentSegment]); - } - subCurrentSegment = ""; - subIsRecording = false; - } - - if (subCurrentDepth === 1) { - subSegments.push([false, ``]); - subCurrentSegment = ""; - } - subCurrentDepth--; - }); - - subParser.on("comment", comment => { - if (subIsRecording) { - subCurrentSegment += ``; + for (const segment of subSegments) { + try { + if (segment[0]) { + subTranslated.push(await helper(segment[1])); } else { - subSegments.push([false, ``]); - } - }); - - subParser.on("end", async () => { - for (const segment of subSegments) { - try { - if (segment[0]) { - subTranslated.push(await helper(segment[1])); - } else { - subTranslated.push(segment[1]); - } - } catch (error) { - logError( - `Error translating segment in ${filePath}:`, - error, - filePath - ); - // Add error comment and continue with next segment - subTranslated.push( - segment[1] + `` - ); - } + subTranslated.push(segment[1]); } - resolve(); - }); - - subParser.on("error", err => { - logError(`Error in subParser for ${filePath}:`, err, filePath); - // Try to recover and continue - try { - subParser._parser.error = null; - subParser._parser.resume(); - } catch (resumeErr) { - logError(`Could not recover from parser error:`, resumeErr, filePath); - reject(err); - } - }); - - Readable.from("" + ori + "").pipe(subParser); - }); + } catch (error) { + logError( + `Error translating segment in ${filePath}:`, + error, + filePath + ); + // Add error comment and continue with next segment + subTranslated.push( + segment[1] + `` + ); + } + } return subTranslated.join(""); } // Create a SAX parser in strict mode to split source into chunks. - const parser = createParser(); const thread = await ai.beta.threads.create(); + + console.log( + "Translating " + filePath + " to " + language + " at " + thread.id + ); let translated: String[] = []; try { - await new Promise((resolve, reject) => { - console.log("Translating " + filePath + " to " + language + " at " + thread.id); - // Variables to track current depth and segments. - let currentDepth = 0; - let currentSegment = ""; - const segments: [boolean, string][] = []; - - // In this context: - // - Depth 0: Before any element is opened. - // - Depth 1: The root element (). - // - Depth 2: Each direct child of the root that we want to capture. - let isRecording = false; - - parser.on("opentag", node => { - currentDepth++; - - if (currentDepth === 2 || isRecording) { - isRecording = true; - currentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; - } else { - segments.push([ - false, - `<${node.name}${formatAttributes(node.attributes)}>` - ]); - } - }); - - parser.on("text", text => { - text = strongEscapeXML(text); + const segments: [boolean, string][] = await splitParser(filePath, logError); - if (isRecording) { - currentSegment += text; + for (const segment of segments) { + try { + if (segment[0]) { + translated.push(await helper(segment[1])); } else { - segments.push([false, text]); - } - }); - - parser.on("cdata", cdata => { - if (isRecording) { - currentSegment += ``; - } - }); - - parser.on("closetag", tagName => { - if (isRecording) { - currentSegment += ``; - } - - if (currentDepth === 2) { - isRecording = false; - // We are closing a segment element. - if (ignoredTags.includes(tagName)) { - segments.push([false, currentSegment]); - } else { - if ( - segments.length > 0 && - segments[segments.length - 1][0] && - segments[segments.length - 1][1].length + currentSegment.length < - MAXLEN - ) { - segments[segments.length - 1][1] += currentSegment; - } else { - segments.push([true, currentSegment]); - } - } - currentSegment = ""; - } - - if (currentDepth === 1) { - // We are closing the root element. - segments.push([false, ``]); - } - - currentDepth--; - }); - - parser.on("comment", comment => { - if (isRecording) { - currentSegment += ``; - } else { - segments.push([false, ``]); - } - }); - - parser.on("end", async () => { - for (const segment of segments) { - try { - if (segment[0]) { - translated.push(await helper(segment[1])); - } else { - translated.push(segment[1]); - } - } catch (error) { - logError( - `Error translating segment in ${filePath}:`, - error, - filePath - ); - // Add error comment and continue with next segment - translated.push( - segment[1] + `` - ); - } - } - resolve(); - }); - - parser.on("error", err => { - logError(`Parser error in ${filePath}:`, err, filePath); - // Try to recover and continue - try { - parser._parser.error = null; - parser._parser.resume(); - } catch (resumeErr) { - logError(`Could not recover from parser error:`, resumeErr, filePath); - reject(err); + translated.push(segment[1]); } - }); - - // Use the file path directly without modification - fs.createReadStream(filePath).pipe(parser); - }); + } catch (error) { + logError( + `Error translating segment in ${filePath}:`, + error, + filePath + ); + // Add error comment and continue with next segment + translated.push( + segment[1] + `` + ); + } + } return translated.join(""); } catch (parseErr) { @@ -460,71 +247,7 @@ async function recursivelyTranslate( const text = message.content[0].text; - const safeText = escapeXML(text.value); - const textStream = Readable.from("" + safeText + ""); - - await new Promise((resolve, reject) => { - // Create a SAX parser in strict mode for cleaning up translations. - const clean = createParser(); - - // SAX parser to remove any excess text (artifacts, annotations etc.) from LLM outside of XML tags - let currDepth = -1; - - clean.on("text", text => { - if (currDepth >= 1) { - translatedChunk += strongEscapeXML(text); - } - }); - - clean.on("opentag", node => { - currDepth++; - if (node.name != "WRAPPER" && node.name != "TRANSLATE") { - translatedChunk += `<${node.name}${formatAttributes(node.attributes)}>`; - } - }); - - clean.on("closetag", tagName => { - if (tagName != "WRAPPER" && tagName != "TRANSLATE") { - translatedChunk += ``; - } - currDepth--; - }); - - clean.on("cdata", cdata => { - translatedChunk += ``; - }); - - clean.on("comment", comment => { - translatedChunk += ``; - }); - - clean.on("error", error => { - // Log only once with abbreviated content - logError( - `Error validating AI response for ${filePath}`, - error, - filePath - ); - - // Attempt to recover using the internal parser - try { - clean._parser.error = null; - clean._parser.resume(); - // Continue processing despite the error - resolve(); - } catch (e) { - // Add error comment and resolve instead of rejecting - translatedChunk += ``; - resolve(); - } - }); - - clean.once("end", resolve); - - textStream.pipe(clean); - }); - - return translatedChunk; + return await cleanParser(text.value, filePath, logError); } catch (err) { logError( `Error occurred while translating chunk in ${filePath}:`, @@ -538,28 +261,3 @@ async function recursivelyTranslate( } export default translate; - -// Helper function to format attributes into a string. -function formatAttributes(attrs: string) { - const attrStr = Object.entries(attrs) - .map(([key, val]) => `${key}="${val}"`) - .join(" "); - return attrStr ? " " + attrStr : ""; -} - -function escapeXML(str: string): string { - return str - .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") - .replace(/<([^a-zA-Z\/])/g, "<$1") // Fix lone < characters - .replace(/([^a-zA-Z0-9"'\s\/])>/g, "$1>"); // Fix lone > characters; -} - -function strongEscapeXML(str: string): string { - return str - .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") - .replace(/【([\s\S]*?)】/g, ""); -} diff --git a/i18n/controllers/xmlUtilities.ts b/i18n/controllers/xmlUtilities.ts new file mode 100644 index 000000000..6b3db70fe --- /dev/null +++ b/i18n/controllers/xmlUtilities.ts @@ -0,0 +1,25 @@ +function formatAttributes(attrs: string) { + const attrStr = Object.entries(attrs) + .map(([key, val]) => `${key}="${val}"`) + .join(" "); + return attrStr ? " " + attrStr : ""; +} + +function escapeXML(str: string): string { + return str + .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") + .replace(/<([^a-zA-Z\/])/g, "<$1") // Fix lone < characters + .replace(/([^a-zA-Z0-9"'\s\/])>/g, "$1>"); // Fix lone > characters; +} + +function strongEscapeXML(str: string): string { + return str + .replace(/&(?!(?:amp;|lt;|gt;|apos;|quot;))/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/【([\s\S]*?)】/g, ""); +} + +export { formatAttributes, escapeXML, strongEscapeXML }; diff --git a/i18n/initializers/initialize.ts b/i18n/initializers/initialize.ts index 1eec3ad59..3944cf1b4 100644 --- a/i18n/initializers/initialize.ts +++ b/i18n/initializers/initialize.ts @@ -8,7 +8,11 @@ import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -export default async function createAssistant(langCode: string, language: string, ai: OpenAI) { +export default async function createAssistant( + langCode: string, + language: string, + ai: OpenAI +) { const assistant = await ai.beta.assistants.create({ name: "SICP Translator", instructions: `You are a professional translator with high technical skills in computer science. @@ -24,17 +28,19 @@ export default async function createAssistant(langCode: string, language: string tools: [{ type: "file_search" }] }); - const fileStreams = [path.join(__dirname, "../ai_files", langCode, "dictionary.txt")].map( - filePath => { - const stream = fs.createReadStream(filePath); - - stream.on('error', err => { - throw new Error(`Failed to read dictionary file at ${filePath}: ${err.message}`) - }) - - return stream; - } - ); + const fileStreams = [ + path.join(__dirname, "../ai_files", langCode, "dictionary.txt") + ].map(filePath => { + const stream = fs.createReadStream(filePath); + + stream.on("error", err => { + throw new Error( + `Failed to read dictionary file at ${filePath}: ${err.message}` + ); + }); + + return stream; + }); // Create a vector store including our two files. const vectorStore = await ai.vectorStores.create({ From 4c118b79a377b7149b4d631c7134d902c39488e0 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 22:28:43 +0800 Subject: [PATCH 52/55] chore: update dotenv and openai dependencies to latest versions --- i18n/package-lock.json | 63 ++++++++---------------------------------- i18n/package.json | 7 ++--- i18n/yarn.lock | 16 +++++------ 3 files changed, 23 insertions(+), 63 deletions(-) diff --git a/i18n/package-lock.json b/i18n/package-lock.json index 104fe1889..d7f48a842 100644 --- a/i18n/package-lock.json +++ b/i18n/package-lock.json @@ -4,18 +4,19 @@ "requires": true, "packages": { "": { - "devDependencies": { - "@types/node": "^22.15.3", - "dotenv": "^16.4.7", - "openai": "^4.81.0", + "dependencies": { + "dotenv": "^16.5.0", + "openai": "^4.96.2", "sax": "^1.4.1" + }, + "devDependencies": { + "@types/node": "^22.15.3" } }, "node_modules/@types/node": { "version": "22.15.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -25,7 +26,6 @@ "version": "2.6.12", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -36,7 +36,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" @@ -49,7 +48,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, "license": "MIT", "dependencies": { "humanize-ms": "^1.2.1" @@ -62,14 +60,12 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -83,7 +79,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -96,17 +91,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "dev": true, + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -119,7 +112,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -134,7 +126,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -144,7 +135,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -154,7 +144,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -167,7 +156,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -183,7 +171,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -193,7 +180,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -209,14 +195,12 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "dev": true, "license": "MIT" }, "node_modules/formdata-node": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dev": true, "license": "MIT", "dependencies": { "node-domexception": "1.0.0", @@ -230,7 +214,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -240,7 +223,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -265,7 +247,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -279,7 +260,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -292,7 +272,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -305,7 +284,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -321,7 +299,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -334,7 +311,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.0.0" @@ -344,7 +320,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -354,7 +329,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -364,7 +338,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -377,14 +350,12 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, "funding": [ { "type": "github", @@ -404,7 +375,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" @@ -422,10 +392,9 @@ } }, "node_modules/openai": { - "version": "4.87.3", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.87.3.tgz", - "integrity": "sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ==", - "dev": true, + "version": "4.96.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.96.2.tgz", + "integrity": "sha512-R2XnxvMsizkROr7BV3uNp1q/3skwPZ7fmPjO1bXLnfB4Tu5xKxrT1EVwzjhxn0MZKBKAvOaGWS63jTMN6KrIXA==", "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", @@ -456,7 +425,6 @@ "version": "18.19.86", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -466,35 +434,30 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, "license": "MIT" }, "node_modules/sax": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "dev": true, "license": "ISC" }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, "license": "MIT" }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -504,14 +467,12 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "license": "MIT", "dependencies": { "tr46": "~0.0.3", diff --git a/i18n/package.json b/i18n/package.json index 551836898..c2771cebc 100644 --- a/i18n/package.json +++ b/i18n/package.json @@ -10,11 +10,10 @@ "url": "https://github.com/coder114514" } ], - "dependencies": { - "sicp": "^1.1.4", + "dependencies": { + "dotenv": "^16.5.0", "openai": "^4.96.2", - "sax": "^1.4.1", - "dotenv": "^16.5.0" + "sax": "^1.4.1" }, "devDependencies": { "@types/node": "^22.15.3" diff --git a/i18n/yarn.lock b/i18n/yarn.lock index 9d4d14a44..c2ef39b55 100644 --- a/i18n/yarn.lock +++ b/i18n/yarn.lock @@ -63,10 +63,10 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -dotenv@^16.4.7: - version "16.4.7" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz" - integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== +dotenv@^16.5.0: + version "16.5.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz" + integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== dunder-proto@^1.0.1: version "1.0.1" @@ -226,10 +226,10 @@ node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -openai@^4.81.0: - version "4.87.3" - resolved "https://registry.npmjs.org/openai/-/openai-4.87.3.tgz" - integrity sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ== +openai@^4.96.2: + version "4.96.2" + resolved "https://registry.npmjs.org/openai/-/openai-4.96.2.tgz" + integrity sha512-R2XnxvMsizkROr7BV3uNp1q/3skwPZ7fmPjO1bXLnfB4Tu5xKxrT1EVwzjhxn0MZKBKAvOaGWS63jTMN6KrIXA== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" From 23da84ce3faacfffb026d2c7ecab466941b83e3e Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 22:30:28 +0800 Subject: [PATCH 53/55] ran prettier --- i18n/controllers/parsers.ts | 205 +++++++++++++++-------------- i18n/controllers/recurTranslate.ts | 34 ++--- 2 files changed, 116 insertions(+), 123 deletions(-) diff --git a/i18n/controllers/parsers.ts b/i18n/controllers/parsers.ts index ec884146b..4bca32e53 100644 --- a/i18n/controllers/parsers.ts +++ b/i18n/controllers/parsers.ts @@ -76,7 +76,10 @@ export async function cleanParser( return translatedChunk; } -export async function splitParser(filePath: PathLike, logError: Function): Promise<[boolean, string][]> { +export async function splitParser( + filePath: PathLike, + logError: Function +): Promise<[boolean, string][]> { // Create a SAX parser in strict mode to split source into chunks. const parser = createParser(); @@ -137,7 +140,7 @@ export async function splitParser(filePath: PathLike, logError: Function): Promi segments.length > 0 && segments[segments.length - 1][0] && segments[segments.length - 1][1].length + currentSegment.length < - Number(MAXLEN) + Number(MAXLEN) ) { segments[segments.length - 1][1] += currentSegment; } else { @@ -186,121 +189,123 @@ export async function splitParser(filePath: PathLike, logError: Function): Promi return segments; } -export async function recurSplitParser(ori: string, filePath: PathLike, logError: Function): Promise { - let subTranslated: string[] = []; - // continue splitting the chunk - // Create a SAX parser in strict mode to split source into chunks. - await new Promise((resolve, reject) => { - const subParser = createParser(); +export async function recurSplitParser( + ori: string, + filePath: PathLike, + logError: Function +): Promise { + let subTranslated: string[] = []; + // continue splitting the chunk + // Create a SAX parser in strict mode to split source into chunks. + await new Promise((resolve, reject) => { + const subParser = createParser(); - let subCurrentDepth = 0; - let subCurrentSegment = ""; - const subSegments: [boolean, string][] = []; - let subIsRecording = false; + let subCurrentDepth = 0; + let subCurrentSegment = ""; + const subSegments: [boolean, string][] = []; + let subIsRecording = false; - subParser.on("opentag", node => { - if (node.name === "WRAPPER") return; + subParser.on("opentag", node => { + if (node.name === "WRAPPER") return; - subCurrentDepth++; + subCurrentDepth++; - if (subCurrentDepth === 2) subIsRecording = true; + if (subCurrentDepth === 2) subIsRecording = true; - if (subIsRecording) { - subCurrentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; - } else { - subSegments.push([ - false, - `<${node.name}${formatAttributes(node.attributes)}>` - ]); - } - }); + if (subIsRecording) { + subCurrentSegment += `<${node.name}${formatAttributes(node.attributes)}>`; + } else { + subSegments.push([ + false, + `<${node.name}${formatAttributes(node.attributes)}>` + ]); + } + }); - subParser.on("text", text => { - text = strongEscapeXML(text); - if (subIsRecording) { - subCurrentSegment += text; - } else if ( - subSegments.length > 0 && - subSegments[subSegments.length - 1][0] - ) { - subSegments[subSegments.length - 1][1] += text; - } else if ( - text.trim() === "" || - text.trim() === "," || - text.trim() === "." - ) { - subSegments.push([false, text]); - } else { - subSegments.push([true, text]); - } - }); + subParser.on("text", text => { + text = strongEscapeXML(text); + if (subIsRecording) { + subCurrentSegment += text; + } else if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] + ) { + subSegments[subSegments.length - 1][1] += text; + } else if ( + text.trim() === "" || + text.trim() === "," || + text.trim() === "." + ) { + subSegments.push([false, text]); + } else { + subSegments.push([true, text]); + } + }); - subParser.on("cdata", cdata => { - if (subIsRecording) { - subCurrentSegment += ``; - } - }); + subParser.on("cdata", cdata => { + if (subIsRecording) { + subCurrentSegment += ``; + } + }); - subParser.on("closetag", tagName => { - if (tagName === "WRAPPER") { - return; - } + subParser.on("closetag", tagName => { + if (tagName === "WRAPPER") { + return; + } - subCurrentSegment += ``; + subCurrentSegment += ``; - if (subCurrentDepth === 2) { - // We are closing a segment element. - if (ignoredTags.includes(tagName)) { - subSegments.push([false, subCurrentSegment]); - } else if ( - subSegments.length > 0 && - subSegments[subSegments.length - 1][0] && - subSegments[subSegments.length - 1][1].length + + if (subCurrentDepth === 2) { + // We are closing a segment element. + if (ignoredTags.includes(tagName)) { + subSegments.push([false, subCurrentSegment]); + } else if ( + subSegments.length > 0 && + subSegments[subSegments.length - 1][0] && + subSegments[subSegments.length - 1][1].length + subCurrentSegment.length < Number(MAXLEN) - ) { - subSegments[subSegments.length - 1][1] += subCurrentSegment; - } else { - subSegments.push([true, subCurrentSegment]); - } - subCurrentSegment = ""; - subIsRecording = false; + ) { + subSegments[subSegments.length - 1][1] += subCurrentSegment; + } else { + subSegments.push([true, subCurrentSegment]); } + subCurrentSegment = ""; + subIsRecording = false; + } - if (subCurrentDepth === 1) { - subSegments.push([false, ``]); - subCurrentSegment = ""; - } + if (subCurrentDepth === 1) { + subSegments.push([false, ``]); + subCurrentSegment = ""; + } - subCurrentDepth--; - }); + subCurrentDepth--; + }); - subParser.on("comment", comment => { - if (subIsRecording) { - subCurrentSegment += ``; - } else { - subSegments.push([false, ``]); - } - }); - - subParser.on("end", async () => - resolve() - ); - - subParser.on("error", err => { - logError(`Error in subParser for ${filePath}:`, err, filePath); - // Try to recover and continue - try { - subParser._parser.error = null; - subParser._parser.resume(); - } catch (resumeErr) { - logError(`Could not recover from parser error:`, resumeErr, filePath); - reject(err); - } - }); + subParser.on("comment", comment => { + if (subIsRecording) { + subCurrentSegment += ``; + } else { + subSegments.push([false, ``]); + } + }); + + subParser.on("end", async () => resolve()); - Readable.from("" + ori + "").pipe(subParser); + subParser.on("error", err => { + logError(`Error in subParser for ${filePath}:`, err, filePath); + // Try to recover and continue + try { + subParser._parser.error = null; + subParser._parser.resume(); + } catch (resumeErr) { + logError(`Could not recover from parser error:`, resumeErr, filePath); + reject(err); + } }); - return subTranslated; + Readable.from("" + ori + "").pipe(subParser); + }); + + return subTranslated; } diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/recurTranslate.ts index c6faf4587..9f50baf7e 100644 --- a/i18n/controllers/recurTranslate.ts +++ b/i18n/controllers/recurTranslate.ts @@ -83,14 +83,14 @@ async function translate(langCode: string, filePath: string): Promise { const output_path = filePath.replace( path.sep + "en" + path.sep, path.sep + - ".." + - path.sep + - "i18n" + - path.sep + - "translation_output" + - path.sep + - langCode + - path.sep + ".." + + path.sep + + "i18n" + + path.sep + + "translation_output" + + path.sep + + langCode + + path.sep ); const translated: string = await recursivelyTranslate( @@ -141,11 +141,7 @@ async function recursivelyTranslate( subTranslated.push(segment[1]); } } catch (error) { - logError( - `Error translating segment in ${filePath}:`, - error, - filePath - ); + logError(`Error translating segment in ${filePath}:`, error, filePath); // Add error comment and continue with next segment subTranslated.push( segment[1] + `` @@ -176,15 +172,9 @@ async function recursivelyTranslate( translated.push(segment[1]); } } catch (error) { - logError( - `Error translating segment in ${filePath}:`, - error, - filePath - ); + logError(`Error translating segment in ${filePath}:`, error, filePath); // Add error comment and continue with next segment - translated.push( - segment[1] + `` - ); + translated.push(segment[1] + ``); } } @@ -201,8 +191,6 @@ async function recursivelyTranslate( return chunk; } - let translatedChunk = ""; - try { await ai.beta.threads.messages.create(thread.id, { role: "user", From 12531b301d226c977c0d68fa1f79fddf468f99ee Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 22:49:24 +0800 Subject: [PATCH 54/55] refactored index.ts --- i18n/controllers/fileUtilities.ts | 45 +++++++ i18n/controllers/loggers.ts | 111 +++++++++++++++ i18n/index.ts | 201 ++-------------------------- i18n/initializers/processHandler.ts | 55 ++++++++ 4 files changed, 219 insertions(+), 193 deletions(-) create mode 100644 i18n/controllers/fileUtilities.ts create mode 100644 i18n/controllers/loggers.ts create mode 100644 i18n/initializers/processHandler.ts diff --git a/i18n/controllers/fileUtilities.ts b/i18n/controllers/fileUtilities.ts new file mode 100644 index 000000000..a6b27bc0e --- /dev/null +++ b/i18n/controllers/fileUtilities.ts @@ -0,0 +1,45 @@ +import path from "path"; +import fs from "fs"; + +export async function needsTranslation( + enFilePath: string, + lang: string +): Promise { + const cnFilePath = enFilePath.replace( + path.sep + "en" + path.sep, + path.sep + lang + path.sep + ); + try { + const cnStats = await fs.promises.stat(cnFilePath); + if (!cnStats.isFile()) { + return true; + } + + const enStats = await fs.promises.stat(enFilePath); + return enStats.mtime > cnStats.mtime; + } catch (error) { + throw error; + } +} + +// Function to recursively find all XML files in a directory +export async function findAllXmlFiles(directory: string): Promise { + const files = await fs.promises.readdir(directory); + const xmlFiles: string[] = []; + + for (const file of files) { + const fullPath = path.join(directory, file); + const stat = await fs.promises.stat(fullPath); + + if (stat.isDirectory()) { + // Recursively search subdirectories + const subDirFiles = await findAllXmlFiles(fullPath); + xmlFiles.push(...subDirFiles); + } else if (path.extname(file).toLowerCase() === ".xml") { + // Add XML files to the list + xmlFiles.push(fullPath); + } + } + + return xmlFiles; +} diff --git a/i18n/controllers/loggers.ts b/i18n/controllers/loggers.ts new file mode 100644 index 000000000..72c6fa6a8 --- /dev/null +++ b/i18n/controllers/loggers.ts @@ -0,0 +1,111 @@ +import OpenAI from "openai"; +import path from "path"; +import fs from "fs"; +import { getFileErrors } from "./recurTranslate"; +import { translationSummaryPrefix } from "../config"; + +// Function to save summary log - can be called from signal handlers +export async function saveSummaryLog( + xmlFiles: string[], + failures: { file: string; error: any }[], + translateNum: number, + failureCount: number, + successCount: number +) { + try { + const ai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.AI_BASEURL + }); + + // list and delete all assistants + const assistants = await ai.beta.assistants.list({ limit: 100 }); + const failedDel: string[] = []; + await Promise.all( + assistants.data.map(async assistant => { + try { + await ai.beta.assistants.del(assistant.id); + } catch (error) { + failedDel.push(assistant.id); + } + }) + ).then(() => console.log("successfully removed all assistants")); + + // list and delete all uploaded files + const files = await ai.files.list(); + await Promise.all( + files.data.map(async file => { + try { + await ai.files.del(file.id); + } catch (error) { + failedDel.push(file.id); + } + }) + ).then(() => console.log("successfully deleted all files")); + + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + let summaryLog = ` +Translation Summary (${timestamp}) +================================ +Total files scanned: ${xmlFiles.length} +Files needing translation: ${translateNum} +Successfully translated: ${successCount} +Failed translations: ${failureCount} +Success rate: ${translateNum > 0 ? ((successCount / translateNum) * 100).toFixed(2) : 0}% +`; + + if (failedDel.length > 0) { + summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; + failedDel.forEach( + (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) + ); + } + + // Add failed translations to the log + if (failures.length > 0) { + summaryLog += `\nFailed Translations (High-level errors):\n`; + failures.forEach((failure, index) => { + summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; + }); + } + + // Add detailed errors captured during translation process + const fileErrors = getFileErrors(); + if (Object.keys(fileErrors).length > 0) { + failureCount = Object.keys(fileErrors).length; + summaryLog += `\nDetailed Translation Errors:\n`; + summaryLog += `============================\n`; + + for (const [filePath, errors] of Object.entries(fileErrors)) { + summaryLog += `\nFile: ${filePath}\n`; + errors.forEach((error, index) => { + summaryLog += ` ${index + 1}. ${error.error}\n`; + if (error.error) { + // Format the error object/message for better readability + const errorStr = + typeof error.error === "object" + ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors + : String(error.error); + summaryLog += ` Details: ${errorStr}\n`; + } + }); + } + } + + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const logPath = path.join( + logDir, + `${translationSummaryPrefix}-${timestamp}.log` + ); + fs.writeFileSync(logPath, summaryLog); + console.log( + `Summary log saved to logs/translation-summary-${timestamp}.log` + ); + } catch (logError) { + console.error("Failed to save log file:", logError); + } +} diff --git a/i18n/index.ts b/i18n/index.ts index a0f6249e5..331cce955 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,12 +1,14 @@ import PathGenerator from "./controllers/path.ts"; import translate, { getFileErrors } from "./controllers/recurTranslate.ts"; -import fs, { Dirent } from "fs"; +import fs from "fs"; import util from "util"; import path from "path"; import { fileURLToPath } from "url"; import { dirname } from "path"; -import OpenAI from "openai"; -import { max_trans_num, translationSummaryPrefix } from "./config.ts"; +import { max_trans_num } from "./config.ts"; +import { saveSummaryLog } from "./controllers/loggers.ts"; +import { setupCleanupHandlers } from "./initializers/processHandler.ts"; +import { findAllXmlFiles, needsTranslation } from "./controllers/fileUtilities.ts"; // Get the directory name of the current module const __filename = fileURLToPath(import.meta.url); @@ -27,201 +29,14 @@ let failureCount = 0; let processedCount = 0; let failures: { file: string; error: any }[] = []; -// Function to save summary log - can be called from signal handlers -async function saveSummaryLog() { - try { - const ai = new OpenAI({ - apiKey: process.env.API_KEY, - baseURL: process.env.AI_BASEURL - }); - - // list and delete all assistants - const assistants = await ai.beta.assistants.list({ limit: 100 }); - const failedDel: string[] = []; - await Promise.all( - assistants.data.map(async assistant => { - try { - await ai.beta.assistants.del(assistant.id); - } catch (error) { - failedDel.push(assistant.id); - } - }) - ).then(() => console.log("successfully removed all assistants")); - - // list and delete all uploaded files - const files = await ai.files.list(); - await Promise.all( - files.data.map(async file => { - try { - await ai.files.del(file.id); - } catch (error) { - failedDel.push(file.id); - } - }) - ).then(() => console.log("successfully deleted all files")); - - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - let summaryLog = ` -Translation Summary (${timestamp}) -================================ -Total files scanned: ${xmlFiles.length} -Files needing translation: ${filesToTranslate.length} -Successfully translated: ${successCount} -Failed translations: ${failureCount} -Success rate: ${filesToTranslate.length > 0 ? ((successCount / filesToTranslate.length) * 100).toFixed(2) : 0}% -`; - - if (failedDel.length > 0) { - summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; - failedDel.forEach( - (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) - ); - } - - // Add failed translations to the log - if (failures.length > 0) { - summaryLog += `\nFailed Translations (High-level errors):\n`; - failures.forEach((failure, index) => { - summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; - }); - } - - // Add detailed errors captured during translation process - const fileErrors = getFileErrors(); - if (Object.keys(fileErrors).length > 0) { - failureCount = Object.keys(fileErrors).length; - summaryLog += `\nDetailed Translation Errors:\n`; - summaryLog += `============================\n`; - - for (const [filePath, errors] of Object.entries(fileErrors)) { - summaryLog += `\nFile: ${filePath}\n`; - errors.forEach((error, index) => { - summaryLog += ` ${index + 1}. ${error.error}\n`; - if (error.error) { - // Format the error object/message for better readability - const errorStr = - typeof error.error === "object" - ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors - : String(error.error); - summaryLog += ` Details: ${errorStr}\n`; - } - }); - } - } - - const logDir = path.resolve(__dirname, "../logs"); - if (!fs.existsSync(logDir)) { - fs.mkdirSync(logDir, { recursive: true }); - } - - const logPath = path.join(logDir, `${translationSummaryPrefix}-${timestamp}.log`); - fs.writeFileSync(logPath, summaryLog); - console.log( - `Summary log saved to logs/translation-summary-${timestamp}.log` - ); - } catch (logError) { - console.error("Failed to save log file:", logError); - } -} - -// Register handlers for various termination signals -async function setupCleanupHandlers() { - // Handle normal exit - process.on("exit", () => { - console.log("Process is exiting, saving summary..."); - // Only synchronous operations work in 'exit' handlers - // We can't await here, as exit handlers must be synchronous - try { - // Use synchronous operations for final cleanup if needed - // Note: This won't actually call the async parts of saveSummaryLog properly - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const summaryContent = `Translation interrupted during exit at ${timestamp}`; - const logDir = path.resolve(__dirname, "../logs"); - if (!fs.existsSync(logDir)) { - fs.mkdirSync(logDir, { recursive: true }); - } - fs.writeFileSync( - path.join(logDir, `emergency-log-${timestamp}.log`), - summaryContent - ); - } catch (error) { - console.error("Failed to save emergency log during exit:", error); - } - }); - - // Handle Ctrl+C - process.on("SIGINT", async () => { - console.log("\nReceived SIGINT (Ctrl+C). Saving summary before exit..."); - await saveSummaryLog(); - process.exit(1); - }); - - // Handle SIGTERM (kill command) - process.on("SIGTERM", async () => { - console.log("\nReceived SIGTERM. Saving summary before exit..."); - await saveSummaryLog(); - process.exit(1); - }); - - // Handle uncaught exceptions - process.on("uncaughtException", async error => { - console.error("\nUncaught exception:", error); - console.log("Saving summary before exit..."); - await saveSummaryLog(); - process.exit(1); - }); -} - -async function needsTranslation( - enFilePath: string, - lang: string -): Promise { - const cnFilePath = enFilePath.replace( - path.sep + "en" + path.sep, - path.sep + lang + path.sep - ); - try { - const cnStats = await fs.promises.stat(cnFilePath); - if (!cnStats.isFile()) { - return true; - } - - const enStats = await fs.promises.stat(enFilePath); - return enStats.mtime > cnStats.mtime; - } catch (error) { - throw error; - } -} - -// Function to recursively find all XML files in a directory -async function findAllXmlFiles(directory: string): Promise { - const files = await fs.promises.readdir(directory); - const xmlFiles: string[] = []; - - for (const file of files) { - const fullPath = path.join(directory, file); - const stat = await fs.promises.stat(fullPath); - - if (stat.isDirectory()) { - // Recursively search subdirectories - const subDirFiles = await findAllXmlFiles(fullPath); - xmlFiles.push(...subDirFiles); - } else if (path.extname(file).toLowerCase() === ".xml") { - // Add XML files to the list - xmlFiles.push(fullPath); - } - } - - return xmlFiles; -} -export default async function fancyName(path: string, language: string) { +export default async function translateSingle(path: string, language: string) { const fullPath = PathGenerator(path); await translate(language, fullPath); } (async () => { - await setupCleanupHandlers(); + await setupCleanupHandlers(xmlFiles, failures, filesToTranslate.length, failureCount, successCount); try { const languages: string[] = await getDirectories( @@ -354,7 +169,7 @@ export default async function fancyName(path: string, language: string) { } // Save a detailed summary to a log file - await saveSummaryLog(); + await saveSummaryLog(xmlFiles, failures, filesToTranslate.length, failureCount, successCount); } } catch (e) { console.error("Error during translation process:", e); diff --git a/i18n/initializers/processHandler.ts b/i18n/initializers/processHandler.ts new file mode 100644 index 000000000..582862b44 --- /dev/null +++ b/i18n/initializers/processHandler.ts @@ -0,0 +1,55 @@ +import path from "path"; +import { saveSummaryLog } from "../controllers/loggers"; +import fs from "fs"; + +export async function setupCleanupHandlers( + xmlFiles: string[], + failures: { file: string; error: any }[], + translateNum: number, + failureCount: number, + successCount: number) { + // Handle normal exit + process.on("exit", () => { + console.log("Process is exiting, saving summary..."); + // Only synchronous operations work in 'exit' handlers + // We can't await here, as exit handlers must be synchronous + try { + // Use synchronous operations for final cleanup if needed + // Note: This won't actually call the async parts of saveSummaryLog properly + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const summaryContent = `Translation interrupted during exit at ${timestamp}`; + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + fs.writeFileSync( + path.join(logDir, `emergency-log-${timestamp}.log`), + summaryContent + ); + } catch (error) { + console.error("Failed to save emergency log during exit:", error); + } + }); + + // Handle Ctrl+C + process.on("SIGINT", async () => { + console.log("\nReceived SIGINT (Ctrl+C). Saving summary before exit..."); + await saveSummaryLog(xmlFiles, failures, translateNum, failureCount, successCount); + process.exit(1); + }); + + // Handle SIGTERM (kill command) + process.on("SIGTERM", async () => { + console.log("\nReceived SIGTERM. Saving summary before exit..."); + await saveSummaryLog(xmlFiles, failures, translateNum, failureCount, successCount); + process.exit(1); + }); + + // Handle uncaught exceptions + process.on("uncaughtException", async error => { + console.error("\nUncaught exception:", error); + console.log("Saving summary before exit..."); + await saveSummaryLog(xmlFiles, failures, translateNum, failureCount, successCount); + process.exit(1); + }); +} \ No newline at end of file From cdf91c6a2904416b991d346c94963ee0ca9b4eb0 Mon Sep 17 00:00:00 2001 From: yihao Date: Thu, 1 May 2025 22:51:55 +0800 Subject: [PATCH 55/55] renamed files --- i18n/controllers/loggers.ts | 170 +++++++++--------- .../{recurTranslate.ts => translator.ts} | 0 i18n/index.ts | 2 +- 3 files changed, 86 insertions(+), 86 deletions(-) rename i18n/controllers/{recurTranslate.ts => translator.ts} (100%) diff --git a/i18n/controllers/loggers.ts b/i18n/controllers/loggers.ts index 72c6fa6a8..1b9b316b5 100644 --- a/i18n/controllers/loggers.ts +++ b/i18n/controllers/loggers.ts @@ -1,50 +1,50 @@ import OpenAI from "openai"; import path from "path"; import fs from "fs"; -import { getFileErrors } from "./recurTranslate"; +import { getFileErrors } from "./translator"; import { translationSummaryPrefix } from "../config"; // Function to save summary log - can be called from signal handlers export async function saveSummaryLog( - xmlFiles: string[], - failures: { file: string; error: any }[], - translateNum: number, - failureCount: number, - successCount: number + xmlFiles: string[], + failures: { file: string; error: any }[], + translateNum: number, + failureCount: number, + successCount: number ) { - try { - const ai = new OpenAI({ - apiKey: process.env.API_KEY, - baseURL: process.env.AI_BASEURL - }); + try { + const ai = new OpenAI({ + apiKey: process.env.API_KEY, + baseURL: process.env.AI_BASEURL + }); - // list and delete all assistants - const assistants = await ai.beta.assistants.list({ limit: 100 }); - const failedDel: string[] = []; - await Promise.all( - assistants.data.map(async assistant => { - try { - await ai.beta.assistants.del(assistant.id); - } catch (error) { - failedDel.push(assistant.id); - } - }) - ).then(() => console.log("successfully removed all assistants")); + // list and delete all assistants + const assistants = await ai.beta.assistants.list({ limit: 100 }); + const failedDel: string[] = []; + await Promise.all( + assistants.data.map(async assistant => { + try { + await ai.beta.assistants.del(assistant.id); + } catch (error) { + failedDel.push(assistant.id); + } + }) + ).then(() => console.log("successfully removed all assistants")); - // list and delete all uploaded files - const files = await ai.files.list(); - await Promise.all( - files.data.map(async file => { - try { - await ai.files.del(file.id); - } catch (error) { - failedDel.push(file.id); - } - }) - ).then(() => console.log("successfully deleted all files")); + // list and delete all uploaded files + const files = await ai.files.list(); + await Promise.all( + files.data.map(async file => { + try { + await ai.files.del(file.id); + } catch (error) { + failedDel.push(file.id); + } + }) + ).then(() => console.log("successfully deleted all files")); - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - let summaryLog = ` + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + let summaryLog = ` Translation Summary (${timestamp}) ================================ Total files scanned: ${xmlFiles.length} @@ -54,58 +54,58 @@ Failed translations: ${failureCount} Success rate: ${translateNum > 0 ? ((successCount / translateNum) * 100).toFixed(2) : 0}% `; - if (failedDel.length > 0) { - summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; - failedDel.forEach( - (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) - ); - } + if (failedDel.length > 0) { + summaryLog += `\nFailed to remove ${failedDel.length} assistants\n`; + failedDel.forEach( + (assistant, index) => (summaryLog += `${index + 1}. ${assistant}\n`) + ); + } - // Add failed translations to the log - if (failures.length > 0) { - summaryLog += `\nFailed Translations (High-level errors):\n`; - failures.forEach((failure, index) => { - summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; - }); - } + // Add failed translations to the log + if (failures.length > 0) { + summaryLog += `\nFailed Translations (High-level errors):\n`; + failures.forEach((failure, index) => { + summaryLog += `${index + 1}. ${failure.file}\n Error: ${failure.error}\n\n`; + }); + } - // Add detailed errors captured during translation process - const fileErrors = getFileErrors(); - if (Object.keys(fileErrors).length > 0) { - failureCount = Object.keys(fileErrors).length; - summaryLog += `\nDetailed Translation Errors:\n`; - summaryLog += `============================\n`; + // Add detailed errors captured during translation process + const fileErrors = getFileErrors(); + if (Object.keys(fileErrors).length > 0) { + failureCount = Object.keys(fileErrors).length; + summaryLog += `\nDetailed Translation Errors:\n`; + summaryLog += `============================\n`; - for (const [filePath, errors] of Object.entries(fileErrors)) { - summaryLog += `\nFile: ${filePath}\n`; - errors.forEach((error, index) => { - summaryLog += ` ${index + 1}. ${error.error}\n`; - if (error.error) { - // Format the error object/message for better readability - const errorStr = - typeof error.error === "object" - ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors - : String(error.error); - summaryLog += ` Details: ${errorStr}\n`; - } - }); - } - } + for (const [filePath, errors] of Object.entries(fileErrors)) { + summaryLog += `\nFile: ${filePath}\n`; + errors.forEach((error, index) => { + summaryLog += ` ${index + 1}. ${error.error}\n`; + if (error.error) { + // Format the error object/message for better readability + const errorStr = + typeof error.error === "object" + ? JSON.stringify(error.error, null, 2).substring(0, 500) // Limit very long errors + : String(error.error); + summaryLog += ` Details: ${errorStr}\n`; + } + }); + } + } - const logDir = path.resolve(__dirname, "../logs"); - if (!fs.existsSync(logDir)) { - fs.mkdirSync(logDir, { recursive: true }); - } + const logDir = path.resolve(__dirname, "../logs"); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } - const logPath = path.join( - logDir, - `${translationSummaryPrefix}-${timestamp}.log` - ); - fs.writeFileSync(logPath, summaryLog); - console.log( - `Summary log saved to logs/translation-summary-${timestamp}.log` - ); - } catch (logError) { - console.error("Failed to save log file:", logError); - } + const logPath = path.join( + logDir, + `${translationSummaryPrefix}-${timestamp}.log` + ); + fs.writeFileSync(logPath, summaryLog); + console.log( + `Summary log saved to logs/translation-summary-${timestamp}.log` + ); + } catch (logError) { + console.error("Failed to save log file:", logError); + } } diff --git a/i18n/controllers/recurTranslate.ts b/i18n/controllers/translator.ts similarity index 100% rename from i18n/controllers/recurTranslate.ts rename to i18n/controllers/translator.ts diff --git a/i18n/index.ts b/i18n/index.ts index 331cce955..8c3e3aedf 100644 --- a/i18n/index.ts +++ b/i18n/index.ts @@ -1,5 +1,5 @@ import PathGenerator from "./controllers/path.ts"; -import translate, { getFileErrors } from "./controllers/recurTranslate.ts"; +import translate, { getFileErrors } from "./controllers/translator.ts"; import fs from "fs"; import util from "util"; import path from "path";