diff --git a/00_intro.md b/00_intro.md index 35055bc4..1d14eef5 100644 --- a/00_intro.md +++ b/00_intro.md @@ -2,7 +2,7 @@ # Introduction -{{quote {author: "Ellen Ullman", title: "Close to the Machine: Technophilia and its Discontents", chapter: true} +{{quote {author: "Ellen Ullman", title: "Close to the Machine: Technophilia and Its Discontents", chapter: true} We think we are creating the system for our own purposes. We believe we are making it in our own image... But the computer is not really like us. It is a projection of a very slim part of ourselves: that portion devoted to logic, order, rule, and clarity. @@ -52,13 +52,13 @@ quote}} {{index [program, "nature of"], data}} -A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer's memory, and at the same time it controls the actions performed on this memory. Analogies that try to compare programs to familiar objects tend to fall short. A superficially fitting one is to compare a program to a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole. +A program is many things. It is a piece of text typed by a programmer, it is the directing force that makes the computer do what it does, it is data in the computer's memory, and, at the same time, it controls the actions performed on this memory. Analogies that try to compare programs to familiar objects tend to fall short. A superficially fitting one is to compare a program to a machine—lots of separate parts tend to be involved, and to make the whole thing tick, we have to consider the ways in which these parts interconnect and contribute to the operation of the whole. A ((computer)) is a physical machine that acts as a host for these immaterial machines. Computers themselves can do only stupidly straightforward things. The reason they are so useful is that they do these things at an incredibly high ((speed)). A program can ingeniously combine an enormous number of these simple actions to do very complicated things. {{index [programming, "joy of"]}} -A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands. But as a program grows, so does its ((complexity)). The skill of programming is the skill of building programs that don't confuse yourself. The best programs are those that manage to do something interesting while still being easy to understand. +A program is a building of thought. It is costless to build, it is weightless, and it grows easily under our typing hands. But as a program grows, so does its ((complexity)). The skill of programming is the skill of building programs that don't confuse the programmer. The best programs are those that manage to do something interesting while still being easy to understand. {{index "programming style", "best practices"}} @@ -66,7 +66,7 @@ Some programmers believe that this complexity is best managed by using only a sm {{index experiment}} -This is not only boring, it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them at least once so that you understand them. A sense of what a good program looks like is developed with practice, not learned from a list of rules. +This is not only boring—it is ineffective. New problems often require new solutions. The field of programming is young and still developing rapidly, and it is varied enough to have room for wildly different approaches. There are many terrible mistakes to make in program design, and you should go ahead and make them at least once so that you understand them. A sense of what a good program looks like is developed with practice, not learned from a list of rules. ## Why language matters @@ -88,7 +88,7 @@ In the beginning, at the birth of computing, there were no programming languages {{index [programming, "history of"], "punch card", complexity}} -This is a program to add the numbers from 1 to 10 together and print the result: `1 + 2 + ... + 10 = 55`. It could run on a simple hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can imagine how tedious and error-prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable. +This is a program to add the numbers from 1 to 10 together and print the result: `1 + 2 + ... + 10 = 55`. It could run on a simple hypothetical machine. To program early computers, it was necessary to set large arrays of switches in the right position or punch holes in strips of cardboard and feed them to the computer. You can imagine how tedious and error prone this procedure was. Even writing simple programs required much cleverness and discipline. Complex ones were nearly inconceivable. {{index bit, "wizard (mighty)"}} @@ -110,25 +110,25 @@ Each line of the previous program contains a single instruction. It could be wri {{index readability, naming, binding}} -Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps: +Although that is already more readable than the soup of bits, it is still rather obscure. Using names instead of numbers for the instructions and memory locations helps. ```{lang: "null"} - Set “total” to 0. - Set “count” to 1. + Set “total” to 0. + Set “count” to 1. [loop] - Set “compare” to “count”. - Subtract 11 from “compare”. - If “compare” is zero, continue at [end]. - Add “count” to “total”. - Add 1 to “count”. - Continue at [loop]. + Set “compare” to “count”. + Subtract 11 from “compare”. + If “compare” is 0, continue at [end]. + Add “count” to “total”. + Add 1 to “count”. + Continue at [loop]. [end] - Output “total”. + Output “total”. ``` {{index loop, jump, "summing example"}} -Can you see how the program works at this point? The first two lines give two memory locations their starting values: `total` will be used to build up the result of the computation, and `count` will keep track of the number that we are currently looking at. The lines using `compare` are probably the most confusing ones. The program wants to see whether `count` is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can only test whether a number is zero and make a decision based on that. It therefore uses the memory location labeled `compare` to compute the value of `count - 11` and makes a decision based on that value. The next two lines add the value of `count` to the result and increment `count` by 1 every time the program decides that `count` is not 11 yet. +Can you see how the program works at this point? The first two lines give two memory locations their starting values: `total` will be used to build up the result of the computation, and `count` will keep track of the number that we are currently looking at. The lines using `compare` are probably the most confusing ones. The program wants to see whether `count` is equal to 11 to decide whether it can stop running. Because our hypothetical machine is rather primitive, it can test only whether a number is zero and make a decision based on that. It therefore uses the memory location labeled `compare` to compute the value of `count - 11` and makes a decision based on that value. The next two lines add the value of `count` to the result and increment `count` by 1 every time the program decides that `count` is not 11 yet. Here is the same program in JavaScript: @@ -195,9 +195,9 @@ This flexibility also has its advantages, though. It leaves room for techniques {{index future, [JavaScript, "versions of"], ECMAScript, "ECMAScript 6"}} -There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version during JavaScript's ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on the version 4 was abandoned in 2008. A much less ambitious version 5, which made only some uncontroversial improvements, came out in 2009. In 2015, version 6 came out, a major update that included some of the ideas planned for version 4. Since then we've had new, small updates every year. +There have been several versions of JavaScript. ECMAScript version 3 was the widely supported version during JavaScript's ascent to dominance, roughly between 2000 and 2010. During this time, work was underway on an ambitious version 4, which planned a number of radical improvements and extensions to the language. Changing a living, widely used language in such a radical way turned out to be politically difficult, and work on version 4 was abandoned in 2008. A much less ambitious version 5, which made only some uncontroversial improvements, came out in 2009. In 2015, version 6 came out, a major update that included some of the ideas planned for version 4. Since then we've had new, small updates every year. -The fact that JavaScript is evolving means that browsers have to constantly keep up. If you're using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I'm using the 2023 version of JavaScript. +The fact that JavaScript is evolving means that browsers have to constantly keep up. If you're using an older browser, it may not support every feature. The language designers are careful to not make any changes that could break existing programs, so new browsers can still run old programs. In this book, I'm using the 2024 version of JavaScript. {{index [JavaScript, "uses of"]}} @@ -239,7 +239,7 @@ The language part of the book starts with four chapters that introduce the basic After a [first project chapter](robot) that builds a crude delivery robot, the language part of the book continues with chapters on [error handling and bug fixing](error), [regular expressions](regexp) (an important tool for working with text), [modularity](modules) (another defense against complexity), and [asynchronous programming](async) (dealing with events that take time). The [second project chapter](language), where we implement a programming language, concludes the first part of the book. -The second part of the book, Chapters [?](browser) to [?](paint), describes the tools that browser JavaScript has access to. You'll learn to display things on the screen (Chapters [?](dom) and [?](canvas)), respond to user input ([Chapter ?](event)), and communicate over the network ([Chapter ?](http)). There are again two project chapters in this part, building a [platform game](game) and a [pixel paint program](paint). +The second part of the book, Chapters [?](browser) to [?](paint), describes the tools that browser JavaScript has access to. You'll learn to display things on the screen (Chapters [?](dom) and [?](canvas)), respond to user input ([Chapter ?](event)), and communicate over the network ([Chapter ?](http)). There are again two project chapters in this part: building a [platform game](game) and a [pixel paint program](paint). [Chapter ?](node) describes Node.js, and [Chapter ?](skillsharing) builds a small website using that tool. diff --git a/01_values.md b/01_values.md index 843a47e0..bbf9de95 100644 --- a/01_values.md +++ b/01_values.md @@ -20,7 +20,7 @@ In the computer's world, there is only data. You can read data, modify data, cre _Bits_ are any kind of two-valued things, usually described as zeros and ones. Inside the computer, they take forms such as a high or low electrical charge, a strong or weak signal, or a shiny or dull spot on the surface of a CD. Any piece of discrete information can be reduced to a sequence of zeros and ones and thus represented in bits. -{{index "binary number", radix, "decimal number"}} +{{index "binary number", "decimal number"}} For example, we can express the number 13 in bits. This works the same way as a decimal number, but instead of 10 different ((digit))s, we have only 2, and the weight of each increases by a factor of 2 from right to left. Here are the bits that make up the number 13, with the weights of the digits shown below them: @@ -29,7 +29,7 @@ For example, we can express the number 13 in bits. This works the same way as a 128 64 32 16 8 4 2 1 ``` -That's the binary number 00001101. Its non-zero digits stand for 8, 4, and 1, and add up to 13. +That's the binary number 00001101. Its nonzero digits stand for 8, 4, and 1, and add up to 13. ## Values @@ -79,7 +79,7 @@ Fractional numbers are written using a dot: {{index exponent, "scientific notation", [number, notation]}} -For very big or very small numbers, you may also use scientific notation by adding an _e_ (for _exponent_), followed by the exponent of the number: +For very big or very small numbers, you may also use scientific notation by adding an _e_ (for _exponent_), followed by the exponent of the number. ``` 2.998e8 @@ -107,7 +107,7 @@ The `+` and `*` symbols are called _operators_. The first stands for addition an {{index grouping, parentheses, precedence}} -Does this example mean "Add 4 and 100, and multiply the result by 11", or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. As in mathematics, you can change this by wrapping the addition in parentheses: +Does this example mean "Add 4 and 100, and multiply the result by 11", or is the multiplication done before the adding? As you might have guessed, the multiplication happens first. As in mathematics, you can change this by wrapping the addition in parentheses. ```{meta: "expr"} (100 + 4) * 11 @@ -163,7 +163,7 @@ To make it possible to include such characters in a string, the following notati "This is the first line\nAnd this is the second" ``` -This is the actual text is that string: +This is the actual text in that string: ```{lang: null} This is the first line @@ -204,7 +204,7 @@ Strings written with single or double quotes behave very much the same—the onl `half of 100 is ${100 / 2}` ``` -When you write something inside `${}` in a template literal, its result will be computed, converted to a string, and included at that position. This example produces "_half of 100 is 50_". +When you write something inside `${}` in a template literal, its result will be computed, converted to a string, and included at that position. This example produces the string `"half of 100 is 50"`. ## Unary operators @@ -223,11 +223,11 @@ console.log(typeof "x") {{id "console.log"}} -We will use `console.log` in example code to indicate that we want to see the result of evaluating something. More about that in the [next chapter](program_structure). +We will use `console.log` in example code to indicate that we want to see the result of evaluating something. (More about that in the [next chapter](program_structure).) {{index negation, "- operator", "binary operator", "unary operator"}} -The other operators shown so far in this chapter all operated on two values, but `typeof` takes only one. Operators that use two values are called _binary_ operators, while those that take one are called _unary_ operators. The minus operator can be used both as a binary operator and as a unary operator. +The other operators shown so far in this chapter all operated on two values, but `typeof` takes only one. Operators that use two values are called _binary_ operators, while those that take one are called _unary_ operators. The minus operator (`-`) can be used both as a binary operator and as a unary operator. ``` console.log(- (10 - 2)) @@ -257,7 +257,7 @@ console.log(3 < 2) The `>` and `<` signs are the traditional symbols for "is greater than" and "is less than", respectively. They are binary operators. Applying them results in a Boolean value that indicates whether they hold true in this case. -Strings can be compared in the same way: +Strings can be compared in the same way. ``` console.log("Aardvark" < "Zoroaster") @@ -357,7 +357,7 @@ The difference in meaning between `undefined` and `null` is an accident of JavaS {{index NaN, "type coercion"}} -In the Introduction, I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: +In the [introduction](intro), I mentioned that JavaScript goes out of its way to accept almost any program you give it, even programs that do odd things. This is nicely demonstrated by the following expressions: ``` console.log(8 * null) @@ -395,7 +395,7 @@ That behavior is often useful. When you want to test whether a value has a real {{index "type coercion", [Boolean, "conversion to"], "=== operator", "!== operator", comparison}} -What if you want to test whether something refers to the precise value `false`? Expressions like `0 == false` and `"" == false` are also true because of automatic type conversion. When you do _not_ want any type conversions to happen, there are two additional operators: `===` and `!==`. The first tests whether a value is _precisely_ equal to the other, and the second tests whether it is not precisely equal. Thus `"" === false` is false as expected. +What if you want to test whether something refers to the precise value `false`? Expressions like `0 == false` and `"" == false` are also true because of automatic type conversion. When you do _not_ want any type conversions to happen, there are two additional operators: `===` and `!==`. The first tests whether a value is _precisely_ equal to the other, and the second tests whether it is not precisely equal. Thus `"" === false` is false, as expected. I recommend using the three-character comparison operators defensively to prevent unexpected type conversions from tripping you up. But when you're certain the types on both sides will be the same, there is no problem with using the shorter operators. @@ -418,11 +418,11 @@ console.log("Agnes" || "user") {{index "default value"}} -We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put `||` after it with a replacement value. If the initial value can be converted to false, you'll get the replacement instead. The rules for converting strings and numbers to Boolean values state that `0`, `NaN`, and the empty string (`""`) count as `false`, while all the other values count as `true`. That means `0 || -1` produces `-1`, and `"" || "!?"` yields `"!?"`. +We can use this functionality as a way to fall back on a default value. If you have a value that might be empty, you can put `||` after it with a replacement value. If the initial value can be converted to false, you'll get the replacement instead. The rules for converting strings and numbers to Boolean values state that `0`, `NaN`, and the empty string (`""`) count as false, while all the other values count as true. That means `0 || -1` produces `-1`, and `"" || "!?"` yields `"!?"`. {{index "?? operator", null, undefined}} -The `??` operator resembles `||`, but returns the value on the right only if the one on the left is null or undefined, not if it is some other value that can be converted to `false`. Often, this is preferable to the behavior of `||`. +The `??` operator resembles `||` but returns the value on the right only if the one on the left is `null` or `undefined`, not if it is some other value that can be converted to `false`. Often, this is preferable to the behavior of `||`. ``` console.log(0 || 100); diff --git a/02_program_structure.md b/02_program_structure.md index 9e729694..7f9db00e 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -43,14 +43,14 @@ It is a useless program, though. An ((expression)) can be content to just produc {{index "programming style", "automatic semicolon insertion", semicolon}} -In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next ((line)) will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error-prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you've learned more about the subtleties of missing semicolons. +In some cases, JavaScript allows you to omit the semicolon at the end of a statement. In other cases, it has to be there, or the next ((line)) will be treated as part of the same statement. The rules for when it can be safely omitted are somewhat complex and error prone. So in this book, every statement that needs a semicolon will always get one. I recommend you do the same, at least until you've learned more about the subtleties of missing semicolons. ## Bindings {{indexsee variable, binding}} {{index [syntax, statement], [binding, definition], "side effect", [memory, organization], [state, in binding]}} -How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value must be used immediately or it will dissipate again. To catch and hold values, JavaScript provides a thing called a _binding_, or _variable_: +How does a program keep an internal state? How does it remember things? We have seen how to produce new values from old values, but this does not change the old values, and the new value must be used immediately or it will dissipate again. To catch and hold values, JavaScript provides a thing called a _binding_, or _variable_. ``` let caught = 5 * 5; @@ -87,7 +87,7 @@ console.log(mood); You should imagine bindings as tentacles rather than boxes. They do not _contain_ values; they _grasp_ them—two bindings can refer to the same value. A program can access only the values to which it still has a reference. When you need to remember something, you either grow a tentacle to hold on to it or reattach one of your existing tentacles to it. -Let's look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. When he pays back $35, you give this binding a new value: +Let's look at another example. To remember the number of dollars that Luigi still owes you, you create a binding. When he pays back $35, you give this binding a new value. ``` let luigisDebt = 140; @@ -110,7 +110,7 @@ console.log(one + two); // → 3 ``` -The words `var` and `const` can also be used to create bindings, in a similar fashion to `let`: +The words `var` and `const` can also be used to create bindings, in a similar fashion to `let`. ``` var name = "Ayda"; @@ -162,7 +162,7 @@ The collection of bindings and their values that exist at a given time is called {{indexsee "calling (of functions)", [function, application]}} {{index output, function, [function, application], [browser, environment]}} -A lot of the values provided in the default environment have the type _((function))_. A function is a piece of program wrapped in a value. Such values can be _applied_ in order to run the wrapped program. For example, in a browser environment, the binding `prompt` holds a function that shows a little ((dialog box)) asking for user input. It is used like this: +A lot of the values provided in the default environment have the type _((function))_. A function is a piece of program wrapped in a value. Such values can be _applied_ in order to run the wrapped program. For example, in a browser environment, the binding `prompt` holds a function that shows a little ((dialog)) asking for user input. It is used like this: ``` prompt("Enter passcode"); @@ -203,7 +203,7 @@ Though binding names cannot contain ((period character))s, `console.log` does ha {{index [comparison, "of numbers"], "return value", "Math.max function", maximum}} -Showing a dialog box or writing text to the screen is a _((side effect))_. Many functions are useful because of the side effects they produce. Functions may also produce values, in which case they don't need to have a side effect to be useful. For example, the function `Math.max` takes any amount of number arguments and gives back the greatest: +Showing a dialog box or writing text to the screen is a _((side effect))_. Many functions are useful because of the side effects they produce. Functions may also produce values, in which case they don't need to have a side effect to be useful. For example, the function `Math.max` takes any amount of number arguments and gives back the greatest. ``` console.log(Math.max(2, 4)); @@ -578,7 +578,7 @@ The first style can be hard to read. I rather like the look of the underscores, {{index "Number function", constructor}} -In a few cases, such as the `Number` function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. It will become clear what a constructor is in [Chapter ?](object#constructors). For now, the important thing is not to be bothered by this apparent lack of ((consistency)). +In a few cases, such as the `Number` function, the first letter of a binding is also capitalized. This was done to mark this function as a constructor. It will become clear what a constructor is in [Chapter ?](object#constructors). For now, the important thing is to not be bothered by this apparent lack of ((consistency)). ## Comments @@ -629,7 +629,7 @@ Functions are special values that encapsulate a piece of program. You can invoke {{index exercises}} -If you are unsure how to test your solutions to the exercises, refer to the [Introduction](intro). +If you are unsure how to test your solutions to the exercises, refer to the [introduction](intro). Each exercise starts with a problem description. Read this description and try to solve the exercise. If you run into problems, consider reading the hints [after the exercise]{if interactive}[at the [end of the book](hints)]{if book}. You can find full solutions to the exercises online at [_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code#2). If you want to learn something from the exercises, I recommend looking at the solutions only after you've solved the exercise, or at least after you've attacked it long and hard enough to have a slight headache. diff --git a/03_functions.md b/03_functions.md index ab8622ae..9f5ce287 100644 --- a/03_functions.md +++ b/03_functions.md @@ -165,7 +165,7 @@ if (safeMode) { {{index [function, "higher-order"]}} -In [Chapter ?](higher_order), we'll discuss the interesting things that we can do by passing around function values to other functions. +In [Chapter ?](higher_order), we'll discuss the interesting things that we can do by passing function values to other functions. ## Declaration notation @@ -214,7 +214,7 @@ The arrow comes _after_ the list of parameters and is followed by the function's {{index [braces, "function body"], "square example", [parentheses, arguments]}} -When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression, rather than a ((block)) in braces, that expression will be returned from the function. So, these two definitions of `square` do the same thing: +When there is only one parameter name, you can omit the parentheses around the parameter list. If the body is a single expression rather than a ((block)) in braces, that expression will be returned from the function. So, these two definitions of `square` do the same thing: ``` const square1 = (x) => { return x * x; }; @@ -260,11 +260,11 @@ We could show the flow of control schematically like this: ```{lang: null} not in function - in greet - in console.log - in greet + in greet + in console.log + in greet not in function - in console.log + in console.log not in function ``` @@ -351,9 +351,9 @@ console.log("C", "O", 2); {{index "call stack", "local binding", [function, "as value"], scope}} -The ability to treat functions as values, combined with the fact that local bindings are recreated every time a function is called, brings up an interesting question: what happens to local bindings when the function call that created them is no longer active? +The ability to treat functions as values, combined with the fact that local bindings are re-created every time a function is called, brings up an interesting question: What happens to local bindings when the function call that created them is no longer active? -The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding: +The following code shows an example of this. It defines a function, `wrapValue`, that creates a local binding. It then returns a function that accesses and returns this local binding. ``` function wrapValue(n) { @@ -375,7 +375,7 @@ This feature—being able to reference a specific instance of a local binding in {{index "multiplier function"}} -With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount: +With a slight change, we can turn the previous example into a way to create functions that multiply by an arbitrary amount. ``` function multiplier(factor) { @@ -389,7 +389,7 @@ console.log(twice(5)); {{index [binding, "from parameter"]}} -The explicit `local` binding from the `wrapValue` example isn't really needed, since a parameter is itself a local binding. +The explicit `local` binding from the `wrapValue` example isn't really needed since a parameter is itself a local binding. {{index [function, "model of"]}} @@ -422,7 +422,7 @@ This is rather close to the way mathematicians define exponentiation and arguabl {{index [function, application], efficiency}} -This implementation has one problem, however: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. +However, this implementation has one problem: in typical JavaScript implementations, it's about three times slower than a version using a `for` loop. Running through a simple loop is generally cheaper than calling a function multiple times. {{index optimization}} @@ -436,7 +436,7 @@ Worrying about efficiency can be a distraction. It's yet another factor that com {{index "premature optimization"}} -Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't, since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. +Therefore, you should generally start by writing something that's correct and easy to understand. If you're worried that it's too slow—which it usually isn't since most code simply isn't executed often enough to take any significant amount of time—you can measure afterward and improve it if necessary. {{index "branching recursion"}} @@ -470,7 +470,7 @@ console.log(findSolution(24)); Note that this program doesn't necessarily find the _shortest_ sequence of operations. It is satisfied when it finds any sequence at all. -It's okay if you don't see how this code works right away. Let's work through it, since it makes for a great exercise in recursive thinking. +It's okay if you don't see how this code works right away. Let's work through it since it makes for a great exercise in recursive thinking. The inner function `find` does the actual recursing. It takes two ((argument))s: the current number and a string that records how we reached this number. If it finds a solution, it returns a string that shows how to get to the target. If it can find no solution starting from this number, it returns `null`. @@ -510,7 +510,7 @@ There are two more or less natural ways for functions to be introduced into prog The first occurs when you find yourself writing similar code multiple times. You'd prefer not to do that, as having more code means more space for mistakes to hide and more material to read for people trying to understand the program. So you take the repeated functionality, find a good name for it, and put it into a function. -The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You start by naming the function, then write its body. You might even start writing code that uses the function before you actually define the function itself. +The second way is that you find you need some functionality that you haven't written yet and that sounds like it deserves its own function. You start by naming the function, and then write its body. You might even start writing code that uses the function before you actually define the function itself. {{index [function, naming], [binding, naming]}} @@ -732,7 +732,7 @@ hint}} You can get the *N*th character, or letter, from a string by writing `[N]` after the string (for example, `string[2]`). The resulting value will be a string containing only one character (for example, `"b"`). The first character has position 0, which causes the last one to be found at position `string.length - 1`. In other words, a two-character string has length 2, and its characters have positions 0 and 1. -Write a function `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase B characters there are in the string. +Write a function called `countBs` that takes a string as its only argument and returns a number that indicates how many uppercase B characters there are in the string. Next, write a function called `countChar` that behaves like `countBs`, except it takes a second argument that indicates the character that is to be counted (rather than counting only uppercase B characters). Rewrite `countBs` to make use of this new function. diff --git a/04_data.md b/04_data.md index dc21c98e..51541b8d 100644 --- a/04_data.md +++ b/04_data.md @@ -42,7 +42,7 @@ Switching to a more scientific approach, Jacques has started keeping a daily log The first thing he needs is a data structure to store this information. -## Data sets +## Datasets {{index ["data structure", collection], [memory, organization]}} @@ -54,7 +54,7 @@ We could get creative with strings—after all, strings can have any length, so {{index [array, creation], "[] (array)"}} -Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an _array_ and is written as a list of values between ((square brackets)), separated by commas: +Fortunately, JavaScript provides a data type specifically for storing sequences of values. It is called an _array_ and is written as a list of values between ((square brackets)), separated by commas. ``` let listOfNumbers = [2, 3, 5, 7, 11]; @@ -133,7 +133,7 @@ Properties that contain functions are generally called _methods_ of the value th {{id array_methods}} -This example demonstrates two methods you can use to manipulate arrays: +This example demonstrates two methods you can use to manipulate arrays. ``` let sequence = [1, 2, 3]; @@ -163,7 +163,7 @@ Back to the weresquirrel. A set of daily log entries can be represented as an ar {{index [syntax, object], [property, definition], [braces, object], "{} (object)"}} -Values of the type _((object))_ are arbitrary collections of properties. One way to create an object is by using braces as an expression: +Values of the type _((object))_ are arbitrary collections of properties. One way to create an object is by using braces as an expression. ``` let day1 = { @@ -192,7 +192,7 @@ let descriptions = { {{index [braces, object]}} -This means that braces have _two_ meanings in JavaScript. At the start of a ((statement)), they begin a ((block)) of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem. The one case where this does come up is when you want to return an object from a short-hand arrow function—you can't write `n => {prop: n}`, since the braces will be interpreted as a function body. Instead, you have to put a set of parentheses around the object to make it clear that it is an expression. +This means that braces have _two_ meanings in JavaScript. At the start of a ((statement)), they begin a ((block)) of statements. In any other position, they describe an object. Fortunately, it is rarely useful to start a statement with an object in braces, so the ambiguity between these two is not much of a problem. The one case where this does come up is when you want to return an object from a shorthand arrow function—you can't write `n => {prop: n}` since the braces will be interpreted as a function body. Instead, you have to put a set of parentheses around the object to make it clear that it is an expression. {{index undefined}} @@ -225,7 +225,7 @@ console.log("right" in anObject); {{index "in operator", [property, "testing for"], object}} -The binary `in` operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to `undefined` and actually deleting it is that in the first case, the object still _has_ the property (it just doesn't have a very interesting value), whereas in the second case the property is no longer present and `in` will return `false`. +The binary `in` operator, when applied to a string and an object, tells you whether that object has a property with that name. The difference between setting a property to `undefined` and actually deleting it is that in the first case, the object still _has_ the property (it just doesn't have a very interesting value), whereas in the second case, the property is no longer present and `in` will return `false`. {{index "Object.keys function"}} @@ -264,7 +264,7 @@ let journal = [ {events: ["weekend", "cycling", "break", "peanuts", "beer"], squirrel: true}, - /* and so on... */ + /* And so on... */ ]; ``` @@ -415,9 +415,9 @@ console.log(phi([76, 9, 4, 1])); This is a direct translation of the _ϕ_ formula into JavaScript. `Math.sqrt` is the square root function, as provided by the `Math` object in a standard JavaScript environment. We have to add two fields from the table to get fields like [n~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} because the sums of rows or columns are not stored directly in our data structure. -{{index "JOURNAL data set"}} +{{index "JOURNAL dataset"}} -Jacques keeps his journal for three months. The resulting ((data set)) is available in the [coding sandbox](https://eloquentjavascript.net/code#4) for this chapter[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book}, where it is stored in the `JOURNAL` binding, and in a downloadable [file](https://eloquentjavascript.net/code/journal.js). +Jacques keeps his journal for three months. The resulting ((dataset)) is available in the [coding sandbox](https://eloquentjavascript.net/code#4) for this chapter[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book}, where it is stored in the `JOURNAL` binding, and in a downloadable [file](https://eloquentjavascript.net/code/journal.js). {{index "tableFor function"}} @@ -484,7 +484,7 @@ When a `for` loop uses the word `of` after its variable definition, it will loop {{index journal, "weresquirrel example", "journalEvents function"}} -We need to compute a correlation for every type of event that occurs in the data set. To do that, we first need to _find_ every type of event. +We need to compute a correlation for every type of event that occurs in the dataset. To do that, we first need to _find_ every type of event. {{index "includes method", "push method"}} @@ -518,7 +518,7 @@ for (let event of journalEvents(JOURNAL)) { // → weekend: 0.1371988681 // → bread: -0.0757554019 // → pudding: -0.0648203724 -// and so on... +// And so on... ``` Most correlations seem to lie close to zero. Eating carrots, bread, or pudding apparently does not trigger squirrel-lycanthropy. The transformations _do_ seem to occur somewhat more often on weekends. Let's filter the results to show only correlations greater than 0.1 or less than -0.1: @@ -541,7 +541,7 @@ for (let event of journalEvents(JOURNAL)) { Aha! There are two factors with a ((correlation)) clearly stronger than the others. Eating ((peanuts)) has a strong positive effect on the chance of turning into a squirrel, whereas brushing teeth has a significant negative effect. -Interesting. Let's try something: +Interesting. Let's try something. ``` for (let entry of JOURNAL) { @@ -560,13 +560,13 @@ Knowing this, Jacques stops eating peanuts altogether and finds that his transfo {{index "weresquirrel example"}} -But it only takes a few months for him to notice that something is missing from this entirely human way of living. Without his feral adventures, Jacques hardly feels alive at all. He decides he'd rather be a full-time wild animal. After building a beautiful little tree house in the forest and equipping it with a peanut butter dispenser and a ten-year supply of peanut butter, he changes form one last time, and lives the short and energetic life of a squirrel. +But it takes only a few months for him to notice that something is missing from this entirely human way of living. Without his feral adventures, Jacques hardly feels alive at all. He decides he'd rather be a full-time wild animal. After building a beautiful little tree house in the forest and equipping it with a peanut butter dispenser and a ten-year supply of peanut butter, he changes form one last time, and lives the short and energetic life of a squirrel. ## Further arrayology {{index [array, methods], [method, array]}} -Before finishing the chapter, I want to introduce you to a few more object-related concepts. I'll start by introducing some generally useful array methods. +Before finishing the chapter, I want to introduce you to a few more object-related concepts. I'll start with some generally useful array methods. {{index "push method", "pop method", "shift method", "unshift method"}} @@ -604,7 +604,7 @@ Both `indexOf` and `lastIndexOf` take an optional second argument that indicates {{index "slice method", [array, indexing]}} -Another fundamental array method is `slice`, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive, the end index exclusive. +Another fundamental array method is `slice`, which takes start and end indices and returns an array that has only the elements between them. The start index is inclusive and the end index is exclusive. ``` console.log([0, 1, 2, 3, 4].slice(2, 4)); @@ -745,7 +745,7 @@ When such a function is called, the _((rest parameter))_ is bound to an array co {{index [function, application]}} -You can use a similar three-dot notation to _call_ a function with an array of arguments: +You can use a similar three-dot notation to _call_ a function with an array of arguments. ``` let numbers = [5, 1, 7]; @@ -797,7 +797,7 @@ Many languages will stop you, or at least warn you, when you are defining a bind {{index "Math.cos function", "Math.sin function", "Math.tan function", "Math.acos function", "Math.asin function", "Math.atan function", "Math.PI constant", cosine, sine, tangent, "PI constant", pi}} -Back to the `Math` object. If you need to do ((trigonometry)), `Math` can help. It contains `cos` (cosine), `sin` (sine), and `tan` (tangent), as well as their inverse functions, `acos`, `asin`, and `atan`, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as `Math.PI`. There is an old programming tradition of writing the names of ((constant)) values in all caps: +Back to the `Math` object. If you need to do ((trigonometry)), `Math` can help. It contains `cos` (cosine), `sin` (sine), and `tan` (tangent), as well as their inverse functions, `acos`, `asin`, and `atan`, respectively. The number π (pi)—or at least the closest approximation that fits in a JavaScript number—is available as `Math.PI`. There is an old programming tradition of writing the names of ((constant)) values in all caps. ```{test: no} function randomPointOnCircle(radius) { @@ -809,11 +809,11 @@ console.log(randomPointOnCircle(2)); // → {x: 0.3667, y: 1.966} ``` -If you're not familiar with sines and cosines, don't worry. I'll explain them when they are used in this book, in [Chapter ?](dom#sin_cos). +If you're not familiar with sines and cosines, don't worry. I'll explain them when they are used in [Chapter ?](dom#sin_cos). {{index "Math.random function", "random number"}} -The previous example used `Math.random`. This is a function that returns a new pseudorandom number between zero (inclusive) and one (exclusive) every time you call it: +The previous example used `Math.random`. This is a function that returns a new pseudorandom number between 0 (inclusive) and 1 (exclusive) every time you call it: ```{test: no} console.log(Math.random()); @@ -877,7 +877,7 @@ This also works for bindings created with `let`, `var`, or `const`. If you know {{index [object, property], [braces, object]}} -A similar trick works for objects, using braces instead of square brackets: +A similar trick works for objects, using braces instead of square brackets. ``` let {name} = {name: "Faraji", age: 23}; @@ -893,7 +893,7 @@ Note that if you try to destructure `null` or `undefined`, you get an error, muc {{index "optional chaining", "period character"}} -When you aren't sure whether a given value produces an object but still want to read a property from it when it does, you can use a variant of the dot notation: `object?.property`. +When you aren't sure whether a given value produces an object, but still want to read a property from it when it does, you can use a variant of the dot notation: `object?.property`. ``` function city(object) { @@ -905,7 +905,7 @@ console.log(city({name: "Vera"})); // → undefined ``` -The expression `a?.b` means the same `a.b` when `a` isn't null or undefined. When it is, it evaluates to undefined. This can be convenient when, as in the example, you aren't sure that a given property exists or when a variable might hold an undefined value. +The expression `a?.b` means the same as `a.b` when `a` isn't null or undefined. When it is, it evaluates to `undefined`. This can be convenient when, as in the example, you aren't sure that a given property exists or when a variable might hold an undefined value. A similar notation can be used with square bracket access, and even with function calls, by putting `?.` in front of the parentheses or brackets: @@ -928,7 +928,7 @@ If you want to save data in a file for later or send it to another computer over {{index serialization, "World Wide Web"}} -What we can do is _serialize_ the data. That means it is converted into a flat description. A popular serialization format is called _((JSON))_ (pronounced "Jason"), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the Web, even in languages other than JavaScript. +What we can do is _serialize_ the data. That means it is converted into a flat description. A popular serialization format is called _((JSON))_ (pronounced "Jason"), which stands for JavaScript Object Notation. It is widely used as a data storage and communication format on the web, even with languages other than JavaScript. {{index [array, notation], [object, creation], [quoting, "in JSON"], comment}} @@ -1013,15 +1013,13 @@ Building up an array is most easily done by first initializing a binding to `[]` Since the end boundary is inclusive, you'll need to use the `<=` operator rather than `<` to check for the end of your loop. -{{index "arguments object"}} - The step parameter can be an optional parameter that defaults (using the `=` operator) to 1. {{index "range function", "for loop"}} Having `range` understand negative step values is probably best done by writing two separate loops—one for counting up and one for counting down—because the comparison that checks whether the loop is finished needs to be `>=` rather than `<=` when counting downward. -It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, `range(5, 2)` returns something meaningful, rather than getting stuck in an ((infinite loop)). It is possible to refer to previous parameters in the default value of a parameter. +It might also be worthwhile to use a different default step, namely, -1, when the end of the range is smaller than the start. That way, `range(5, 2)` returns something meaningful rather than getting stuck in an ((infinite loop)). It is possible to refer to previous parameters in the default value of a parameter. hint}} @@ -1029,7 +1027,7 @@ hint}} {{index "reversing (exercise)", "reverse method", [array, methods]}} -Arrays have a `reverse` method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, `reverseArray` and `reverseArrayInPlace`. The first, `reverseArray`, should take an array as argument and produce a _new_ array that has the same elements in the inverse order. The second, `reverseArrayInPlace`, should do what the `reverse` method does: _modify_ the array given as argument by reversing its elements. Neither may use the standard `reverse` method. +Arrays have a `reverse` method that changes the array by inverting the order in which its elements appear. For this exercise, write two functions, `reverseArray` and `reverseArrayInPlace`. The first, `reverseArray`, should take an array as its argument and produce a _new_ array that has the same elements in the inverse order. The second, `reverseArrayInPlace`, should do what the `reverse` method does: _modify_ the array given as its argument by reversing its elements. Neither may use the standard `reverse` method. {{index efficiency, "pure function", "side effect"}} @@ -1057,13 +1055,13 @@ if}} {{index "reversing (exercise)"}} -There are two obvious ways to implement `reverseArray`. The first is to simply go over the input array from front to back and use the `unshift` method on the new array to insert each element at its start. The second is to loop over the input array backwards and use the `push` method. Iterating over an array backwards requires a (somewhat awkward) `for` specification, like `(let i = array.length - 1; i >= 0; i--)`. +There are two obvious ways to implement `reverseArray`. The first is to simply go over the input array from front to back and use the `unshift` method on the new array to insert each element at its start. The second is to loop over the input array backward and use the `push` method. Iterating over an array backward requires a (somewhat awkward) `for` specification, like `(let i = array.length - 1; i >= 0; i--)`. {{index "slice method"}} Reversing the array in place is harder. You have to be careful not to overwrite elements that you will later need. Using `reverseArray` or otherwise copying the whole array (`array.slice()` is a good way to copy an array) works but is cheating. -The trick is to _swap_ the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use `Math.floor` to round down—you don't need to touch the middle element in an array with an odd number of elements) and swapping the element at position `i` with the one at position `array.length - 1 - i`. You can use a local binding to briefly hold on to one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be. +The trick is to _swap_ the first and last elements, then the second and second-to-last, and so on. You can do this by looping over half the length of the array (use `Math.floor` to round down—you don't need to touch the middle element in an array with an odd number of elements) and swapping the element at position `i` with the one at position `array.length - 1 - i`. You can use a local binding to briefly hold onto one of the elements, overwrite that one with its mirror image, and then put the value from the local binding in the place where the mirror image used to be. hint}} @@ -1123,7 +1121,7 @@ if}} {{index "list (exercise)", "linked list"}} -Building up a list is easier when done back to front. So `arrayToList` could iterate over the array backwards (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like `list = {value: X, rest: list}` to add an element. +Building up a list is easier when done back to front. So `arrayToList` could iterate over the array backward (see the previous exercise) and, for each element, add an object to the list. You can use a local binding to hold the part of the list that was built so far and use an assignment like `list = {value: X, rest: list}` to add an element. {{index "for loop"}} @@ -1133,7 +1131,7 @@ To run over a list (in `listToArray` and `nth`), a `for` loop specification like for (let node = list; node; node = node.rest) {} ``` -Can you see how that works? Every iteration of the loop, `node` points to the current sublist, and the body can read its `value` property to get the current element. At the end of an iteration, `node` moves to the next sublist. When that is null, we have reached the end of the list, and the loop is finished. +Can you see how that works? Every iteration of the loop, `node` points to the current sublist, and the body can read its `value` property to get the current element. At the end of an iteration, `node` moves to the next sublist. When that is `null`, we have reached the end of the list, and the loop is finished. {{index recursion}} @@ -1149,7 +1147,7 @@ hint}} The `==` operator compares objects by identity, but sometimes you'd prefer to compare the values of their actual properties. -Write a function `deepEqual` that takes two values and returns true only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to `deepEqual`. +Write a function `deepEqual` that takes two values and returns `true` only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to `deepEqual`. {{index null, "=== operator", "typeof operator"}} @@ -1187,6 +1185,6 @@ Use `Object.keys` to go over the properties. You need to test whether both objec {{index "return value"}} -Returning the correct value from the function is best done by immediately returning false when a mismatch is found and returning true at the end of the function. +Returning the correct value from the function is best done by immediately returning `false` when a mismatch is found and returning `true` at the end of the function. hint}} diff --git a/05_higher_order.md b/05_higher_order.md index 1dc9d912..8a37a8dd 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -18,7 +18,7 @@ A large program is a costly program, and not just because of the time it takes t {{index "summing example"}} -Let's briefly go back to the final two example programs in the introduction. The first is self-contained and six lines long. +Let's briefly go back to the final two example programs in the introduction. The first is self contained and six lines long. ``` let total = 0, count = 1; @@ -97,7 +97,7 @@ for (let i = 0; i < 10; i++) { } ``` -Can we abstract "doing something _N_ times" as a function? Well, it's easy to write a function that calls `console.log` _N_ times: +Can we abstract "doing something _N_ times" as a function? Well, it's easy to write a function that calls `console.log` _N_ times. ``` function repeatLog(n) { @@ -111,7 +111,7 @@ function repeatLog(n) { {{indexsee "higher-order function", "function, higher-order"}} -But what if we want to do something other than logging the numbers? Since "doing something" can be represented as a function and functions are just values, we can pass our action as a function value: +But what if we want to do something other than logging the numbers? Since "doing something" can be represented as a function and functions are just values, we can pass our action as a function value. ```{includeCode: "top_lines: 5"} function repeat(n, action) { @@ -126,7 +126,7 @@ repeat(3, console.log); // → 2 ``` -We don't have to pass a predefined function to `repeat`. Often, it is easier to create a function value on the spot instead: +We don't have to pass a predefined function to `repeat`. Often, it is easier to create a function value on the spot instead. ``` let labels = []; @@ -139,7 +139,7 @@ console.log(labels); {{index "loop body", [braces, body], [parentheses, arguments]}} -This is structured a little like a `for` loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to `repeat`. This is why it has to be closed with the closing brace _and_ closing parenthesis. In cases like this example where the body is a single small expression, you could also omit the braces and write the loop on a single line. +This is structured a little like a `for` loop—it first describes the kind of loop and then provides a body. However, the body is now written as a function value, which is wrapped in the parentheses of the call to `repeat`. This is why it has to be closed with the closing brace _and_ closing parenthesis. In cases like this example, where the body is a single small expression, you could also omit the braces and write the loop on a single line. ## Higher-order functions @@ -149,7 +149,7 @@ Functions that operate on other functions, either by taking them as arguments or {{index abstraction}} -Higher-order functions allow us to abstract over _actions_, not just values. They come in several forms. For example, we can have functions that create new functions: +Higher-order functions allow us to abstract over _actions_, not just values. They come in several forms. For example, we can have functions that create new functions. ``` function greaterThan(n) { @@ -160,7 +160,7 @@ console.log(greaterThan10(11)); // → true ``` -We can also have functions that change other functions: +We can also have functions that change other functions. ``` function noisy(f) { @@ -176,7 +176,7 @@ noisy(Math.min)(3, 2, 1); // → called with [3, 2, 1] , returned 1 ``` -We can even write functions that provide new types of ((control flow)): +We can even write functions that provide new types of ((control flow)). ``` function unless(test, then) { @@ -194,7 +194,7 @@ repeat(3, n => { {{index [array, methods], [array, iteration], "forEach method"}} -There is a built-in array method, `forEach`, that provides something like a `for`/`of` loop as a higher-order function: +There is a built-in array method, `forEach`, that provides something like a `for`/`of` loop as a higher-order function. ``` ["A", "B"].forEach(l => console.log(l)); @@ -204,19 +204,19 @@ There is a built-in array method, `forEach`, that provides something like a `for {{id scripts}} -## Script data set +## Script dataset -One area where higher-order functions shine is data processing. To process data, we'll need some actual example data. This chapter will use a ((data set)) about scripts—((writing system))s such as Latin, Cyrillic, or Arabic. +One area where higher-order functions shine is data processing. To process data, we'll need some actual example data. This chapter will use a ((dataset)) about scripts—((writing system))s such as Latin, Cyrillic, or Arabic. -Remember ((Unicode)) from [Chapter ?](values#unicode), the system that assigns a number to each character in written language? Most of these characters are associated with a specific script. The standard contains 140 different scripts, of which 81 are still in use today and 59 are historic. +Remember ((Unicode)), the system that assigns a number to each character in written language, from [Chapter ?](values#unicode)? Most of these characters are associated with a specific script. The standard contains 140 different scripts, of which 81 are still in use today and 59 are historic. Though I can fluently read only Latin characters, I appreciate the fact that people are writing texts in at least 80 other writing systems, many of which I wouldn't even recognize. For example, here's a sample of ((Tamil)) handwriting: {{figure {url: "img/tamil.png", alt: "A line of verse in Tamil handwriting. The characters are relatively simple, and neatly separated, yet completely different from Latin."}}} -{{index "SCRIPTS data set"}} +{{index "SCRIPTS dataset"}} -The example ((data set)) contains some pieces of information about the 140 scripts defined in Unicode. It is available in the [coding sandbox](https://eloquentjavascript.net/code#5) for this chapter[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} as the `SCRIPTS` binding. The binding contains an array of objects, each of which describes a script: +The example ((dataset)) contains some pieces of information about the 140 scripts defined in Unicode. It is available in the [coding sandbox](https://eloquentjavascript.net/code#5) for this chapter[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} as the `SCRIPTS` binding. The binding contains an array of objects, each of which describes a script. ```{lang: "json"} @@ -234,13 +234,13 @@ Such an object tells us the name of the script, the Unicode ranges assigned to i {{index "slice method"}} -The `ranges` property contains an array of Unicode character ((range))s, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower ((bound)) is inclusive (code 994 is a Coptic character) and the upper bound is non-inclusive (code 1008 isn't). +The `ranges` property contains an array of Unicode character ((range))s, each of which is a two-element array containing a lower bound and an upper bound. Any character codes within these ranges are assigned to the script. The lower ((bound)) is inclusive (code 994 is a Coptic character) and the upper bound is noninclusive (code 1008 isn't). ## Filtering arrays {{index [array, methods], [array, filtering], "filter method", [function, "higher-order"], "predicate function"}} -If we want to find the scripts in the data set that are still in use, the following function might be helpful. It filters out elements in an array that don't pass a test. +If we want to find the scripts in the dataset that are still in use, the following function might be helpful. It filters out elements in an array that don't pass a test. ``` function filter(array, test) { @@ -282,7 +282,7 @@ Say we have an array of objects representing scripts, produced by filtering the {{index [function, "higher-order"]}} -The `map` method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been _mapped_ to a new form by the function: +The `map` method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been _mapped_ to a new form by the function. ``` function map(array, transform) { @@ -327,7 +327,7 @@ console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); // → 10 ``` -{{index "reduce method", "SCRIPTS data set"}} +{{index "reduce method", "SCRIPTS dataset"}} The standard array method `reduce`, which of course corresponds to this function, has an added convenience. If your array contains at least one element, you are allowed to leave off the `start` argument. The method will take the first element of the array as its start value and start reducing at the second element. @@ -355,13 +355,13 @@ console.log(SCRIPTS.reduce((a, b) => { The `characterCount` function reduces the ranges assigned to a script by summing their sizes. Note the use of destructuring in the parameter list of the reducer function. The second call to `reduce` then uses this to find the largest script by repeatedly comparing two scripts and returning the larger one. -The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the data set. Han is a script sometimes used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (US-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called _Han unification_ and still makes some people very angry. +The Han script has more than 89,000 characters assigned to it in the Unicode standard, making it by far the biggest writing system in the dataset. Han is a script sometimes used for Chinese, Japanese, and Korean text. Those languages share a lot of characters, though they tend to write them differently. The (US-based) Unicode Consortium decided to treat them as a single writing system to save character codes. This is called _Han unification_ and still makes some people very angry. ## Composability {{index loop, maximum}} -Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse: +Consider how we would have written the previous example (finding the biggest script) without higher-order functions. The code is not that much worse. ```{test: no} let biggest = null; @@ -381,7 +381,7 @@ There are a few more bindings, and the program is four lines longer, but it is s {{id average_function}} -The abstractions provided by these functions really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the data set: +The abstractions these functions provide really shine when you need to _compose_ operations. As an example, let's write code that finds the average year of origin for living and dead scripts in the dataset. ``` function average(array) { @@ -398,7 +398,7 @@ console.log(Math.round(average( As you can see, the dead scripts in Unicode are, on average, older than the living ones. This is not a terribly meaningful or surprising statistic. But I hope you'll agree that the code used to compute it isn't hard to read. You can see it as a pipeline: we start with all scripts, filter out the living (or dead) ones, take the years from those, average them, and round the result. -You could definitely also write this computation as one big ((loop)): +You could definitely also write this computation as one big ((loop)). ``` let total = 0, count = 0; @@ -420,9 +420,9 @@ In terms of what the computer is actually doing, these two approaches are also q ## Strings and character codes -{{index "SCRIPTS data set"}} +{{index "SCRIPTS dataset"}} -One interesting use of this data set would be figuring out what script a piece of text is using. Let's go through a program that does this. +One interesting use of this dataset would be figuring out what script a piece of text is using. Let's go through a program that does this. Remember that each script has an array of character code ranges associated with it. Given a character code, we could use a function like this to find the corresponding script (if any): @@ -479,7 +479,7 @@ JavaScript's `charCodeAt` method gives you a code unit, not a full character cod {{index "for/of loop", character}} -In the [previous chapter](data#for_of_loop), I mentioned that a `for`/`of` loop can also be used on strings. Like `codePointAt`, this type of loop was introduced at a time when people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units: +In the [previous chapter](data#for_of_loop), I mentioned that a `for`/`of` loop can also be used on strings. Like `codePointAt`, this type of loop was introduced at a time when people were acutely aware of the problems with UTF-16. When you use it to loop over a string, it gives you real characters, not code units. ``` let roseDragon = "🌹🐉"; @@ -494,7 +494,7 @@ If you have a character (which will be a string of one or two code units), you c ## Recognizing text -{{index "SCRIPTS data set", "countBy function", [array, counting]}} +{{index "SCRIPTS dataset", "countBy function", [array, counting]}} We have a `characterScript` function and a way to correctly loop over characters. The next step is to count the characters that belong to each script. The following counting abstraction will be useful there: @@ -521,11 +521,11 @@ The `countBy` function expects a collection (anything that we can loop over with {{index "find method"}} -It uses another array method, `find`, which goes over the elements in the array and returns the first one for which a function returns true. It returns `undefined` when no such element is found. +It uses another array method, `find`, which goes over the elements in the array and returns the first one for which a function returns true. It returns `undefined` when it finds no such element. {{index "textScripts function", "Chinese characters"}} -Using `countBy`, we can write the function that tells us which scripts are used in a piece of text: +Using `countBy`, we can write the function that tells us which scripts are used in a piece of text. ```{includeCode: strip_log, startCode: true} function textScripts(text) { @@ -548,17 +548,17 @@ console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"')); {{index "characterScript function", "filter method"}} -The function first counts the characters by name, using `characterScript` to assign them a name and falling back to the string `"none"` for characters that aren't part of any script. The `filter` call drops the entry for `"none"` from the resulting array since we aren't interested in those characters. +The function first counts the characters by name, using `characterScript` to assign them a name and falling back to the string `"none"` for characters that aren't part of any script. The `filter` call drops the entry for `"none"` from the resulting array, since we aren't interested in those characters. {{index "reduce method", "map method", "join method", [array, methods]}} -To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If no such characters are found, the function returns a specific string. Otherwise it transforms the counting entries into readable strings with `map` and then combines them with `join`. +To be able to compute ((percentage))s, we first need the total number of characters that belong to a script, which we can compute with `reduce`. If we find no such characters, the function returns a specific string. Otherwise, it transforms the counting entries into readable strings with `map` and then combines them with `join`. ## Summary Being able to pass function values to other functions is a deeply useful aspect of JavaScript. It allows us to write functions that model computations with "gaps" in them. The code that calls these functions can fill in the gaps by providing function values. -Arrays provide a number of useful higher-order methods. You can use `forEach` to loop over the elements in an array. The `filter` method returns a new array containing only the elements that pass the ((predicate function)). Transforming an array by putting each element through a function is done with `map`. You can use `reduce` to combine all the elements in an array into a single value. The `some` method tests whether any element matches a given predicate function, while `find` finds the first element that matches a predicate. +Arrays provide a number of useful higher-order methods. You can use `forEach` to loop over the elements in an array. The `filter` method returns a new array containing only the elements that pass the ((predicate function)). You can transform an array by putting each element through a function using `map`. You can use `reduce` to combine all the elements in an array into a single value. The `some` method tests whether any element matches a given predicate function, while `find` finds the first element that matches a predicate. ## Exercises @@ -581,7 +581,7 @@ if}} {{index "your own loop (example)", "for loop"}} -Write a higher-order function `loop` that provides something like a `for` loop statement. It should take a value, a test function, an update function, and a body function. Each iteration, it should first run the test function on the current loop value and stop if that returns false. It should then call the body function, giving it the current value, then finally call the update function to create a new value and start over from the beginning. +Write a higher-order function `loop` that provides something like a `for` loop statement. It should take a value, a test function, an update function, and a body function. Each iteration, it should first run the test function on the current loop value and stop if that returns `false`. It should then call the body function, giving it the current value, and finally call the update function to create a new value and start over from the beginning. When defining the function, you can use a regular loop to do the actual looping. @@ -602,7 +602,7 @@ if}} {{index "predicate function", "everything (exercise)", "every method", "some method", [array, methods], "&& operator", "|| operator"}} -Arrays also have an `every` method analogous to the `some` method. This method returns true when the given function returns true for _every_ element in the array. In a way, `some` is a version of the `||` operator that acts on arrays, and `every` is like the `&&` operator. +Arrays also have an `every` method analogous to the `some` method. This method returns `true` when the given function returns `true` for _every_ element in the array. In a way, `some` is a version of the `||` operator that acts on arrays, and `every` is like the `&&` operator. Implement `every` as a function that takes an array and a predicate function as parameters. Write two versions, one using a loop and one using the `some` method. @@ -627,7 +627,7 @@ if}} {{index "everything (exercise)", "short-circuit evaluation", "return keyword"}} -Like the `&&` operator, the `every` method can stop evaluating further elements as soon as it has found one that doesn't match. So the loop-based version can jump out of the loop—with `break` or `return`—as soon as it runs into an element for which the predicate function returns false. If the loop runs to its end without finding such an element, we know that all elements matched and we should return true. +Like the `&&` operator, the `every` method can stop evaluating further elements as soon as it has found one that doesn't match. So the loop-based version can jump out of the loop—with `break` or `return`—as soon as it runs into an element for which the predicate function returns `false`. If the loop runs to its end without finding such an element, we know that all elements matched and we should return `true`. To build `every` on top of `some`, we can apply _((De Morgan's laws))_, which state that `a && b` equals `!(!a || !b)`. This can be generalized to arrays, where all elements in the array match if there is no element in the array that does not match. @@ -635,7 +635,7 @@ hint}} ### Dominant writing direction -{{index "SCRIPTS data set", "direction (writing)", "groupBy function", "dominant direction (exercise)"}} +{{index "SCRIPTS dataset", "direction (writing)", "groupBy function", "dominant direction (exercise)"}} Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a `direction` property that can be `"ltr"` (left to right), `"rtl"` (right to left), or `"ttb"` (top to bottom). diff --git a/06_object.md b/06_object.md index 662666e0..86b92bd5 100644 --- a/06_object.md +++ b/06_object.md @@ -12,9 +12,7 @@ quote}} {{figure {url: "img/chapter_picture_6.jpg", alt: "Illustration of a rabbit next to its prototype, a schematic representation of a rabbit", chapter: framed}}} -[Chapter ?](data) introduced JavaScript's objects, as containers that hold other data. - -In programming culture, we have a thing called _((object-oriented programming))_, a set of techniques that use objects as the central principle of program organization. Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter describes the way these ideas can be applied in JavaScript. +[Chapter ?](data) introduced JavaScript's objects as containers that hold other data. In programming culture, _((object-oriented programming))_ is a set of techniques that use objects as the central principle of program organization. Though no one really agrees on its precise definition, object-oriented programming has shaped the design of many programming languages, including JavaScript. This chapter describes the way these ideas can be applied in JavaScript. ## Abstract Data Types @@ -22,22 +20,22 @@ In programming culture, we have a thing called _((object-oriented programming))_ The main idea in object-oriented programming is to use objects, or rather _types_ of objects, as the unit of program organization. Setting up a program as a number of strictly separated object types provides a way to think about its structure and thus to enforce some kind of discipline, preventing everything from becoming entangled. -The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). There's people who designed and assembled a mixer, and they have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell, so that the people who only want to mix pancake batter don't have to worry about all that—they only have to understand the few knobs that the mixer can be operated with. +The way to do this is to think of objects somewhat like you'd think of an electric mixer or other consumer ((appliance)). The people who design and assemble a mixer have to do specialized work requiring material science and understanding of electricity. They cover all that up in a smooth plastic shell so that the people who only want to mix pancake batter don't have to worry about all that—they have to understand only the few knobs that the mixer can be operated with. {{index "class"}} -Similarly, an abstract data type, or object class, is a subprogram that may contain arbitrarily complicated code, but exposes a limited set of methods and properties that people who are working with it are supposed to use. This allows large programs to be built up out of a number of appliance types, limiting the degree to which these different parts are entangled by requiring them to only interact with each other in specific ways. +Similarly, an _abstract data type_, or _object class_, is a subprogram that may contain arbitrarily complicated code but exposes a limited set of methods and properties that people working with it are supposed to use. This allows large programs to be built up out of a number of appliance types, limiting the degree to which these different parts are entangled by requiring them to only interact with each other in specific ways. {{index encapsulation, isolation, modularity}} -If a problem is found in one such object class, it can often be repaired, or even completely rewritten, without impacting the rest of the program. - -Even better, it may be possible to use object classes in multiple different programs, avoiding the need to recreate their functionality from scratch. You can think of JavaScript's built-in data structures, such as arrays and strings, as such reusable abstract data types. +If a problem is found in one such object class, it can often be repaired or even completely rewritten without impacting the rest of the program. Even better, it may be possible to use object classes in multiple different programs, avoiding the need to recreate their functionality from scratch. You can think of JavaScript's built-in data structures, such as arrays and strings, as such reusable abstract data types. {{id interface}} {{index [interface, object]}} -Each abstract data type has an _interface_, which is the collection of operations that external code can perform on it. Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate, since often useful pieces of functionality involve a group of different object classes working closely together. +Each abstract data type has an _interface_, the collection of operations that external code can perform on it. Any details beyond that interface are _encapsulated_, treated as internal to the type and of no concern to the rest of the program. + +Even basic things like numbers can be thought of as an abstract data type whose interface allows us to add them, multiply them, compare them, and so on. In fact, the fixation on single _objects_ as the main unit of organization in classical object-oriented programming is somewhat unfortunate since useful pieces of functionality often involve a group of different object classes working closely together. {{id obj_methods}} @@ -45,7 +43,7 @@ Each abstract data type has an _interface_, which is the collection of operation {{index "rabbit example", method, [property, access]}} -In JavaScript methods are nothing more than properties that hold function values. This is a simple method: +In JavaScript, methods are nothing more than properties that hold function values. This is a simple method: ```{includeCode: "top_lines:6"} function speak(line) { @@ -62,7 +60,7 @@ hungryRabbit.speak("Got any carrots?"); {{index "this binding", "method call"}} -Typically a method needs to do something with the object it was called on. When a function is called as a method—looked up as a property and immediately called, as in `object.method()`—the binding called `this` in its body automatically points at the object that it was called on. +Typically a method needs to do something with the object on which it was called. When a function is called as a method—looked up as a property and immediately called, as in `object.method()`—the binding called `this` in its body automatically points at the object on which it was called. {{id call_method}} @@ -75,7 +73,7 @@ speak.call(whiteRabbit, "Hurry"); // → The white rabbit says 'Hurry' ``` -Since each function has its own `this` binding, whose value depends on the way it is called, you cannot refer to the `this` of the wrapping scope in a regular function defined with the `function` keyword. +Since each function has its own `this` binding whose value depends on the way it is called, you cannot refer to the `this` of the wrapping scope in a regular function defined with the `function` keyword. {{index "this binding", "arrow function"}} @@ -100,9 +98,9 @@ If I had written the argument to `some` using the `function` keyword, this code ## Prototypes -So one way to create an abstract rabbit type with a `speak` method would be to create a helper function that has a rabbit type as parameter, and returns an object holding that as its `type` property and our speak function in its `speak` property. +One way to create a rabbit object type with a `speak` method would be to create a helper function that has a rabbit type as its parameter and returns an object holding that as its `type` property and our speak function in its `speak` property. -All rabbits share that same method. Especially for types with many methods, it would be nice if there was a way to keep a type's methods in a single place, rather than adding them to each object individually. +All rabbits share that same method. Especially for types with many methods, it would be nice if there were a way to keep a type's methods in a single place, rather than adding them to each object individually. {{index [property, inheritance], [object, property], "Object prototype"}} @@ -118,9 +116,9 @@ console.log(empty.toString()); // → [object Object] ``` -It looks like we just pulled a property out of an empty object. But in fact `toString` is a method stored in `Object.prototype`, meaning it is available in most objects. +It looks like we just pulled a property out of an empty object. But in fact, `toString` is a method stored in `Object.prototype`, meaning it is available in most objects. -When an object gets a request for a property that it does not have, its prototype will be searched for the property. If that doesn't have it, _its_ prototype is searched, and so on until an object is reached has no prototype (`Object.prototype` is such an object). +When an object gets a request for a property that it doesn't have, its prototype will be searched for the property. If that doesn't have it, the _prototype's_ prototype is searched, and so on until an object without prototype is reached (`Object.prototype` is such an object). ``` console.log(Object.getPrototypeOf({}) == Object.prototype); @@ -135,7 +133,7 @@ As you'd guess, `Object.getPrototypeOf` returns the prototype of an object. {{index inheritance, "Function prototype", "Array prototype", "Object prototype"}} -Many objects don't directly have `Object.prototype` as their ((prototype)) but instead have another object that provides a different set of default properties. Functions derive from `Function.prototype`, and arrays derive from `Array.prototype`. +Many objects don't directly have `Object.prototype` as their ((prototype)) but instead have another object that provides a different set of default properties. Functions derive from `Function.prototype` and arrays derive from `Array.prototype`. ``` console.log(Object.getPrototypeOf(Math.max) == @@ -167,7 +165,7 @@ blackRabbit.speak("I am fear and darkness"); {{index "shared property"}} -The "proto" rabbit acts as a container for the properties that are shared by all rabbits. An individual rabbit object, like the black rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype. +The "proto" rabbit acts as a container for the properties shared by all rabbits. An individual rabbit object, like the black rabbit, contains properties that apply only to itself—in this case its type—and derives shared properties from its prototype. {{id classes}} @@ -175,7 +173,7 @@ The "proto" rabbit acts as a container for the properties that are shared by all {{index "object-oriented programming", "abstract data type"}} -JavaScript's ((prototype)) system can be interpreted as a somewhat free-form take on abstract data types or ((class))es. A class defines the shape of a type of object—what methods and properties it has. Such an object is called an _((instance))_ of the class. +JavaScript's ((prototype)) system can be interpreted as a somewhat free-form take on abstract data types or ((class))es. A _class_ defines the shape of a type of object—what methods and properties it has. Such an object is called an _((instance))_ of the class. {{index [property, inheritance]}} @@ -183,7 +181,7 @@ Prototypes are useful for defining properties for which all instances of a class {{id constructors}} -So to create an instance of a given class, you have to make an object that derives from the proper prototype, but you _also_ have to make sure it, itself, has the properties that instances of this class are supposed to have. This is what a _((constructor))_ function does. +To create an instance of a given class, you have to make an object that derives from the proper prototype, but you _also_ have to make sure it itself has the properties that instances of this class are supposed to have. This is what a _((constructor))_ function does. ``` function makeRabbit(type) { @@ -210,17 +208,17 @@ class Rabbit { {{index "prototype property", [braces, class]}} -The `class` keyword starts a ((class declaration)), which allows us to define a constructor and a set of methods together. Any number of methods may be written inside the declaration's braces. This code has the effect of defining a binding called `Rabbit`, which holds a function that runs the code in `constructor`, and has a `prototype` property which holds the `speak` method. +The `class` keyword starts a ((class declaration)), which allows us to define a constructor and a set of methods together. Any number of methods may be written inside the declaration's braces. This code has the effect of defining a binding called `Rabbit`, which holds a function that runs the code in `constructor` and has a `prototype` property that holds the `speak` method. {{index "new operator", "this binding", [object, creation]}} -This function cannot be called normally. Constructors, in JavaScript, are called by putting the keyword `new` in front of them. Doing so creates a fresh object with the object held in the function's `prototype` property as prototype, then runs the function with `this` bound to the new object, and finally returns the object. +This function cannot be called like a normal function. Constructors, in JavaScript, are called by putting the keyword `new` in front of them. Doing so creates a fresh instance object whose prototype is the object from the function's `prototype` property, then runs the function with `this` bound to the new object, and finally returns the object. ```{includeCode: true} let killerRabbit = new Rabbit("killer"); ``` -In fact, `class` was only introduced in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015 the way to define a class was to write a regular function and then manipulate its `prototype` property. +In fact, `class` was only introduced in the 2015 edition of JavaScript. Any function can be used as a constructor, and before 2015, the way to define a class was to write a regular function and then manipulate its `prototype` property. ``` function ArchaicRabbit(type) { @@ -240,7 +238,7 @@ By convention, the names of constructors are capitalized so that they can easily {{index "prototype property", "getPrototypeOf function"}} -It is important to understand the distinction between the way a prototype is associated with a constructor (through its `prototype` _property_) and the way objects _have_ a prototype (which can be found with `Object.getPrototypeOf`). The actual prototype of a constructor is `Function.prototype` since constructors are functions. Its `prototype` _property_ holds the prototype used for instances created through it. +It is important to understand the distinction between the way a prototype is associated with a constructor (through its `prototype` property) and the way objects _have_ a prototype (which can be found with `Object.getPrototypeOf`). The actual prototype of a constructor is `Function.prototype` since constructors are functions. The constructor function's `prototype` _property_ holds the prototype used for instances created through it. ``` console.log(Object.getPrototypeOf(Rabbit) == @@ -253,7 +251,7 @@ console.log(Object.getPrototypeOf(killerRabbit) == {{index constructor}} -Constructors will typically add some per-instance properties to `this`. It is also possible to declare properties directly in the ((class declaration)). Unlike methods, such properties are added to ((instance)) objects, not the prototype. +Constructors will typically add some per-instance properties to `this`. It is also possible to declare properties directly in the ((class declaration)). Unlike methods, such properties are added to ((instance)) objects and not the prototype. ``` class Particle { @@ -277,11 +275,11 @@ console.log(object.getWord()); {{index [property, private], [property, public], "class declaration"}} -It is common for classes to define some properties and ((method))s for internal use, which are not part of their ((interface)). These are called _private_ properties, as opposed to _public_ ones, which are part of the object's external interface. +It is common for classes to define some properties and ((method))s for internal use that are not part of their ((interface)). These are called _private_ properties, as opposed to _public_ ones, which are part of the object's external interface. {{index [method, private]}} -To declare a private method, put a `#` sign in front of its name. Such methods can only be called from inside the `class` declaration that defines them. +To declare a private method, put a `#` sign in front of its name. Such methods can be called only from inside the `class` declaration that defines them. ``` class SecretiveObject { @@ -295,11 +293,13 @@ class SecretiveObject { } ``` +When a class does not declare a constructor, it will automatically get an empty one. + If you try to call `#getSecret` from outside the class, you get an error. Its existence is entirely hidden inside the class declaration. To use private instance properties, you must declare them. Regular properties can be created by just assigning to them, but private properties _must_ be declared in the class declaration to be available at all. -This class implements an appliance for getting random whole number below a given maximum number. It only has one ((public)) property: `getNumber`. +This class implements an appliance for getting a random whole number below a given maximum number. It has only one ((public)) property: `getNumber`. ``` class RandomSource { @@ -340,7 +340,7 @@ The following diagram sketches the situation after this code has run. The `Rabbi {{index "shared property"}} -Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects, while letting the nonexceptional objects take a standard value from their prototype. +Overriding properties that exist in a prototype can be a useful thing to do. As the rabbit teeth example shows, overriding can be used to express exceptional properties in instances of a more generic class of objects while letting the nonexceptional objects take a standard value from their prototype. {{index "toString method", "Array prototype", "Function prototype"}} @@ -367,7 +367,7 @@ console.log(Object.prototype.toString.call([1, 2])); {{index "map method"}} -We saw the word _map_ used in the [previous chapter](higher_order#map) for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is also used for a related but rather different thing. +We saw the word _map_ used in the [previous chapter](higher_order#map) for an operation that transforms a data structure by applying a function to its elements. Confusing as it is, in programming the same word is used for a related but rather different thing. {{index "map (data structure)", "ages example", ["data structure", map]}} @@ -390,11 +390,11 @@ console.log("Is toString's age known?", "toString" in ages); {{index "Object.prototype", "toString method"}} -Here, the object's property names are the people's names, and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet, because plain objects derive from `Object.prototype`, it looks like the property is there. +Here, the object's property names are the people's names and the property values are their ages. But we certainly didn't list anybody named toString in our map. Yet because plain objects derive from `Object.prototype`, it looks like the property is there. {{index "Object.create function", prototype}} -As such, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, it is possible to create objects with _no_ prototype. If you pass `null` to `Object.create`, the resulting object will not derive from `Object.prototype` and can safely be used as a map. +For this reason, using plain objects as maps is dangerous. There are several possible ways to avoid this problem. First, you can create objects with _no_ prototype. If you pass `null` to `Object.create`, the resulting object will not derive from `Object.prototype` and can be safely used as a map. ``` console.log("toString" in Object.create(null)); @@ -455,13 +455,13 @@ console.log(String(killerRabbit)); {{index "object-oriented programming", [interface, object]}} -This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a `toString` method—any kind of object that happens to support this interface can be plugged into the code, and it will be able to work with it. +This is a simple instance of a powerful idea. When a piece of code is written to work with objects that have a certain interface—in this case, a `toString` method—any kind of object that happens to support this interface can be plugged into the code and will be able to work with it. This technique is called _polymorphism_. Polymorphic code can work with values of different shapes, as long as they support the interface it expects. {{index "forEach method"}} -An example of a widely used interface is that of ((array-like object))s which have a `length` property holding a number, and numbered properties for each of their elements. Both arrays and strings support this interface, as do various other objects, some of which we'll see later in the chapters about the browser. Our implementation of `forEach` from [Chapter ?](higher_order) works on anything that provides this interface. In fact, so does `Array.prototype.forEach`. +An example of a widely used interface is that of ((array-like object))s that have a `length` property holding a number and numbered properties for each of their elements. Both arrays and strings support this interface, as do various other objects, some of which we'll see later in the chapters about the browser. Our implementation of `forEach` from [Chapter ?](higher_order) works on anything that provides this interface. In fact, so does `Array.prototype.forEach`. ``` Array.prototype.forEach.call({ @@ -479,7 +479,7 @@ Array.prototype.forEach.call({ Interfaces often contain plain properties, not just methods. For example, `Map` objects have a `size` property that tells you how many keys are stored in them. -It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_, and they are defined by writing `get` in front of the method name in an object expression or class declaration. +It is not necessary for such an object to compute and store such a property directly in the instance. Even properties that are accessed directly may hide a method call. Such methods are called _((getter))s_ and are defined by writing `get` in front of the method name in an object expression or class declaration. ```{test: no} let varyingSize = { @@ -498,7 +498,7 @@ console.log(varyingSize.size); Whenever someone reads from this object's `size` property, the associated method is called. You can do a similar thing when a property is written to, using a _((setter))_. -```{test: no, startCode: true} +```{startCode: true, includeCode: "top_lines: 16"} class Temperature { constructor(celsius) { this.celsius = celsius; @@ -527,9 +527,15 @@ The `Temperature` class allows you to read and write the temperature in either d {{index "static method", "static property"}} -Sometimes you want to attach some properties directly to your constructor function, rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. +Sometimes you want to attach some properties directly to your constructor function rather than to the prototype. Such methods won't have access to a class instance but can, for example, be used to provide additional ways to create instances. -Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. So the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit. +Inside a class declaration, methods or properties that have `static` written before their name are stored on the constructor. For example, the `Temperature` class allows you to write `Temperature.fromFahrenheit(100)` to create a temperature using degrees Fahrenheit. + +``` +let boil = Temperature.fromFahrenheit(212); +console.log(boil.celsius); +// → 100 +``` ## Symbols @@ -537,13 +543,13 @@ Inside a class declaration, methods or properties that have `static` written bef I mentioned in [Chapter ?](data#for_of_loop) that a `for`/`of` loop can loop over several kinds of data structures. This is another case of polymorphism—such loops expect the data structure to expose a specific interface, which arrays and strings do. And we can also add this interface to our own objects! But before we can do that, we need to briefly take a look at the symbol type. -It is possible for multiple interfaces to use the same property name for different things. For example, on array-like objects, `length` refers to the amount of elements in the collection. But an object interface describing a hiking route could use `length` to provide the length of the route in meters. It would not be possible for an object to conform to both these interfaces. +It is possible for multiple interfaces to use the same property name for different things. For example, on array-like objects, `length` refers to the number of elements in the collection. But an object interface describing a hiking route could use `length` to provide the length of the route in meters. It would not be possible for an object to conform to both these interfaces. -An object trying to be a route and array-like (maybe to enumerate its waypoints) is somewhat far-fetched, and this kind of problem isn't that common in practice. But for things like the iteration protocol, the language designers needed a type of property that _really_ doesn't conflict with any others. So in 2015, _((symbol))s_ were added to the language. +An object trying to be a route and array-like (maybe to enumerate its waypoints) is somewhat far-fetched, and this kind of problem isn't that common in practice. For things like the iteration protocol, though, the language designers needed a type of property that _really_ doesn't conflict with any others. So in 2015, _((symbol))s_ were added to the language. {{index "Symbol function", [property, naming]}} -Most properties, including all the properties we have seen so far, are named with strings. But it is also possible to use symbols as property names. Symbols are values created with the `Symbol` function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice. +Most properties, including all those we have seen so far, are named with strings. But it is also possible to use symbols as property names. Symbols are values created with the `Symbol` function. Unlike strings, newly created symbols are unique—you cannot create the same symbol twice. ``` let sym = Symbol("name"); @@ -632,7 +638,7 @@ class List { } ``` -Note that `this`, in a static method, points at the constructor of the class, not an instance—there is no instance around, when a static method is called. +Note that `this`, in a static method, points at the constructor of the class, not an instance—there is no instance around when a static method is called. Iterating over a list should return all the list's elements from start to end. We'll write a separate class for the iterator. @@ -655,9 +661,9 @@ class ListIterator { } ``` -The class tracks the progress of iterating through the list by updating its `list` property to move to the next list object whenever a value is returned, and reports that it is done when that list is empty (null). +The class tracks the progress of iterating through the list by updating its `list` property to move to the next list object whenever a value is returned and reports that it is done when that list is empty (null). -Let's set up the `List` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self-contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead. +Let's set up the `List` class to be iterable. Throughout this book, I'll occasionally use after-the-fact prototype manipulation to add methods to classes so that the individual pieces of code remain small and self contained. In a regular program, where there is no need to split the code into small pieces, you'd declare these methods directly in the class instead. ```{includeCode: true} List.prototype[Symbol.iterator] = function() { @@ -681,7 +687,7 @@ for (let element of list) { {{index spread}} -The `...` syntax in array notation and function calls similarly works with any iterable object. So for example, you can use `[...value]` to create an array containing the elements in an arbitrary iterable object. +The `...` syntax in array notation and function calls similarly works with any iterable object. For example, you can use `[...value]` to create an array containing the elements in an arbitrary iterable object. ``` console.log([..."PCI"]); @@ -692,7 +698,7 @@ console.log([..."PCI"]); {{index inheritance, "linked list", "object-oriented programming", "LengthList class"}} -Imagine we needed a list type, much like the `List` class we saw before, but because we will be asking for its length all the time, we don't want it to have to scan through its `rest` every time, and instead want to store the length in every instance for efficient access. +Imagine we need a list type much like the `List` class we saw before, but because we will be asking for its length all the time, we don't want it to have to scan through its `rest` every time. Instead, we want to store the length in every instance for efficient access. {{index overriding, prototype}} @@ -700,7 +706,7 @@ JavaScript's prototype system makes it possible to create a _new_ class, much li In object-oriented programming terms, this is called _((inheritance))_. The new class inherits properties and behavior from the old class. -```{includeCode: "top_lines: 17"} +```{includeCode: "top_lines: 12"} class LengthList extends List { #length; @@ -722,13 +728,13 @@ The use of the word `extends` indicates that this class shouldn't be directly ba To initialize a `LengthList` instance, the constructor calls the constructor of its superclass through the `super` keyword. This is necessary because if this new object is to behave (roughly) like a `List`, it is going to need the instance properties that lists have. -The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet, since `#length` hasn't been filled in yet. We can using `super.something` to call methods and getters on the superclass's prototype, which is often useful. +The constructor then stores the list's length in a private property. If we had written `this.length` there, the class's own getter would have been called, which doesn't work yet since `#length` hasn't been filled in yet. We can use `super.something` to call methods and getters on the superclass's prototype, which is often useful. Inheritance allows us to build slightly different data types from existing data types with relatively little work. It is a fundamental part of the object-oriented tradition, alongside encapsulation and polymorphism. But while the latter two are now generally regarded as wonderful ideas, inheritance is more controversial. {{index complexity, reuse, "class hierarchy"}} -Whereas ((encapsulation)) and polymorphism can be used to _separate_ pieces of code from each other, reducing the tangledness of the overall program, ((inheritance)) fundamentally ties classes together, creating _more_ tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool to make some types of programs more succinct, but it shouldn't be the first tool you reach for, and you probably shouldn't actively go looking for opportunities to construct class hierarchies (family trees of classes). +Whereas ((encapsulation)) and polymorphism can be used to _separate_ pieces of code from one another, reducing the tangledness of the overall program, ((inheritance)) fundamentally ties classes together, creating _more_ tangle. When inheriting from a class, you usually have to know more about how it works than when simply using it. Inheritance can be a useful tool to make some types of programs more succinct, but it shouldn't be the first tool you reach for, and you probably shouldn't actively go looking for opportunities to construct class hierarchies (family trees of classes). ## The instanceof operator @@ -758,7 +764,7 @@ Objects do more than just hold their own properties. They have prototypes, which Constructors, which are functions whose names usually start with a capital letter, can be used with the `new` operator to create new objects. The new object's prototype will be the object found in the `prototype` property of the constructor. You can make good use of this by putting the properties that all values of a given type share into their prototype. There's a `class` notation that provides a clear way to define a constructor and its prototype. -You can define getters and setters to secretly call methods every time an object's property is accessed. Static methods are methods stored in a class's constructor, rather than its prototype. +You can define getters and setters to secretly call methods every time an object's property is accessed. Static methods are methods stored in a class's constructor rather than its prototype. The `instanceof` operator can, given an object and a constructor, tell you whether that object is an instance of that constructor. @@ -776,7 +782,7 @@ When implementing multiple classes that differ in only some details, it can be h {{index dimensions, "Vec class", coordinates, "vector (exercise)"}} -Write a ((class)) `Vec` that represents a vector in two-dimensional space. It takes `x` and `y` parameters (numbers), which it should save to properties of the same name. +Write a ((class)) `Vec` that represents a vector in two-dimensional space. It takes `x` and `y` parameters (numbers), that it saves to properties of the same name. {{index addition, subtraction}} @@ -828,7 +834,7 @@ Use the `===` operator, or something equivalent such as `indexOf`, to determine {{index "static method"}} -Give the class a static `from` method that takes an iterable object as argument and creates a group that contains all the values produced by iterating over it. +Give the class a static `from` method that takes an iterable object as its argument and creates a group that contains all the values produced by iterating over it. {{if interactive @@ -858,7 +864,7 @@ The easiest way to do this is to store an array of group members in an instance {{index "push method"}} -Your class's ((constructor)) can set the member collection to an empty array. When `add` is called, it must check whether the given value is in the array or add it, for example with `push`, otherwise. +Your class's ((constructor)) can set the member collection to an empty array. When `add` is called, it must check whether the given value is in the array or add it otherwise, possibly using `push`. {{index "filter method"}} diff --git a/07_robot.md b/07_robot.md index 9499341f..3c942cfb 100644 --- a/07_robot.md +++ b/07_robot.md @@ -4,7 +4,7 @@ {{quote {author: "Edsger Dijkstra", title: "The Threats to Computing Science", chapter: true} -[...] the question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim. +The question of whether Machines Can Think [...] is about as relevant as the question of whether Submarines Can Swim. quote}} @@ -64,15 +64,13 @@ function buildGraph(edges) { const roadGraph = buildGraph(roads); ``` -Given an array of edges, `buildGraph` creates a map object that, for each node, stores an array of connected nodes. - {{index "split method"}} -It uses the `split` method to go from the road strings, which have the form `"Start-End"`, to two-element arrays containing the start and end as separate strings. +Given an array of edges, `buildGraph` creates a map object that, for each node, stores an array of connected nodes. It uses the `split` method to go from the road strings—which have the form `"Start-End"`)—to two-element arrays containing the start and end as separate strings. ## The task -Our ((robot)) will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes to them and delivers them when it arrives at their destinations. +Our ((robot)) will be moving around the village. There are parcels in various places, each addressed to some other place. The robot picks up parcels when it comes across them and delivers them when it arrives at their destinations. The automaton must decide, at each point, where to go next. It has finished its task when all parcels have been delivered. @@ -92,7 +90,7 @@ Instead, let's condense the village's state down to the minimal set of values th {{index "VillageState class", "persistent data structure"}} -And while we're at it, let's make it so that we don't _change_ this state when the robot moves but rather compute a _new_ state for the situation after the move. +While we're at it, let's make it so that we don't _change_ this state when the robot moves but rather compute a _new_ state for the situation after the move. ```{includeCode: true} class VillageState { @@ -115,11 +113,11 @@ class VillageState { } ``` -The `move` method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state since this is not a valid move. +The `move` method is where the action happens. It first checks whether there is a road going from the current place to the destination, and if not, it returns the old state, since this is not a valid move. {{index "map method", "filter method"}} -Then it creates a new state with the destination as the robot's new place. But it also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot's current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to `map` takes care of the moving, and the call to `filter` does the delivering. +Next, the method creates a new state with the destination as the robot's new place. It also needs to create a new set of parcels—parcels that the robot is carrying (that are at the robot's current place) need to be moved along to the new place. And parcels that are addressed to the new place need to be delivered—that is, they need to be removed from the set of undelivered parcels. The call to `map` takes care of the moving, and the call to `filter` does the delivering. Parcel objects aren't changed when they are moved but re-created. The `move` method gives us a new village state but leaves the old one entirely intact. @@ -138,7 +136,7 @@ console.log(first.place); // → Post Office ``` -The move causes the parcel to be delivered, and this is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered. +The move causes the parcel to be delivered, which is reflected in the next state. But the initial state still describes the situation where the robot is at the post office and the parcel is undelivered. ## Persistent data @@ -146,7 +144,7 @@ The move causes the parcel to be delivered, and this is reflected in the next st Data structures that don't change are called _((immutable))_ or _persistent_. They behave a lot like strings and numbers in that they are who they are and stay that way, rather than containing different things at different times. -In JavaScript, just about everything _can_ be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called `Object.freeze` that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren't changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. So I usually prefer to just tell people that a given object shouldn't be messed with and hope they remember it. +In JavaScript, just about everything _can_ be changed, so working with values that are supposed to be persistent requires some restraint. There is a function called `Object.freeze` that changes an object so that writing to its properties is ignored. You could use that to make sure your objects aren't changed, if you want to be careful. Freezing does require the computer to do some extra work, and having updates ignored is just about as likely to confuse someone as having them do the wrong thing. I usually prefer to just tell people that a given object shouldn't be messed with and hope they remember it. ``` let object = Object.freeze({value: 5}); @@ -155,9 +153,7 @@ console.log(object.value); // → 5 ``` -Why am I going out of my way to not change objects when the language is obviously expecting me to? - -Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice's house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning. +Why am I going out of my way to not change objects when the language is obviously expecting me to? Because it helps me understand my programs. This is about complexity management again. When the objects in my system are fixed, stable things, I can consider operations on them in isolation—moving to Alice's house from a given start state always produces the same new state. When objects change over time, that adds a whole new dimension of complexity to this kind of reasoning. For a small system like the one we are building in this chapter, we could handle that bit of extra complexity. But the most important limit on what kind of systems we can build is how much we can understand. Anything that makes your code easier to understand makes it possible to build a more ambitious system. @@ -167,11 +163,11 @@ Unfortunately, although understanding a system built on persistent data structur {{index simulation, "virtual world"}} -A delivery ((robot)) looks at the world and decides in which direction it wants to move. As such, we could say that a robot is a function that takes a `VillageState` object and returns the name of a nearby place. +A delivery ((robot)) looks at the world and decides in which direction it wants to move. So we could say that a robot is a function that takes a `VillageState` object and returns the name of a nearby place. {{index "runRobot function"}} -Because we want robots to be able to remember things, so that they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called. +Because we want robots to be able to remember things so they can make and execute plans, we also pass them their memory and allow them to return a new memory. Thus, the thing a robot returns is an object containing both the direction it wants to move in and a memory value that will be given back to it the next time it is called. ```{includeCode: true} function runRobot(state, robot, memory) { @@ -188,7 +184,7 @@ function runRobot(state, robot, memory) { } ``` -Consider what a robot has to do to "solve" a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location that a parcel is addressed to, but only after picking up the parcel. +Consider what a robot has to do to "solve" a given state. It must pick up all parcels by visiting every location that has a parcel and deliver them by visiting every location to which a parcel is addressed, but only after picking up the parcel. What is the dumbest strategy that could possibly work? The robot could just walk in a random direction every turn. That means, with great likelihood, it will eventually run into all parcels and then also at some point reach the place where they should be delivered. @@ -209,7 +205,7 @@ function randomRobot(state) { {{index "Math.random function", "Math.floor function", [array, "random element"]}} -Remember that `Math.random()` returns a number between zero and one—but always below one. Multiplying such a number by the length of an array and then applying `Math.floor` to it gives us a random index for the array. +Remember that `Math.random()` returns a number between 0 and 1—but always below 1. Multiplying such a number by the length of an array and then applying `Math.floor` to it gives us a random index for the array. Since this robot does not need to remember anything, it ignores its second argument (remember that JavaScript functions can be called with extra arguments without ill effects) and omits the `memory` property in its returned object. @@ -232,7 +228,7 @@ VillageState.random = function(parcelCount = 5) { {{index "do loop"}} -We don't want any parcels that are sent from the same place that they are addressed to. For this reason, the `do` loop keeps picking new places when it gets one that's equal to the address. +We don't want any parcels to be sent from the same place that they are addressed to. For this reason, the `do` loop keeps picking new places when it gets one that's equal to the address. Let's start up a virtual world. @@ -304,11 +300,11 @@ Still, I wouldn't really call blindly following a fixed route intelligent behavi To do that, it has to be able to deliberately move toward a given parcel or toward the location where a parcel has to be delivered. Doing that, even when the goal is more than one move away, will require some kind of route-finding function. -The problem of finding a route through a ((graph)) is a typical _((search problem))_. We can tell whether a given solution (a route) is a valid solution, but we can't directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works. +The problem of finding a route through a ((graph)) is a typical _((search problem))_. We can tell whether a given solution (a route) is valid, but we can't directly compute the solution the way we could for 2 + 2. Instead, we have to keep creating potential solutions until we find one that works. The number of possible routes through a graph is infinite. But when searching for a route from _A_ to _B_, we are interested only in the ones that start at _A_. We also don't care about routes that visit the same place twice—those are definitely not the most efficient route anywhere. So that cuts down on the number of routes that the route finder has to consider. -In fact, we are mostly interested in the _shortest_ route. So we want to make sure we look at short routes before we look at longer ones. A good approach would be to "grow" routes from the starting point, exploring every reachable place that hasn't been visited yet, until a route reaches the goal. That way, we'll only explore routes that are potentially interesting, and we know that the first route we find is the shortest route (or one of the shortest routes, if there are more than one). +In fact, since we are mostly interested in the _shortest_ route, we want to make sure we look at short routes before we look at longer ones. A good approach would be to "grow" routes from the starting point, exploring every reachable place that hasn't been visited yet until a route reaches the goal. That way, we'll explore only routes that are potentially interesting, and we know that the first route we find is the shortest route (or one of the shortest routes, if there are more than one). {{index "findRoute function"}} @@ -335,9 +331,9 @@ The exploring has to be done in the right order—the places that were reached f Therefore, the function keeps a _((work list))_. This is an array of places that should be explored next, along with the route that got us there. It starts with just the start position and an empty route. -The search then operates by taking the next item in the list and exploring that, which means all roads going from that place are looked at. If one of them is the goal, a finished route can be returned. Otherwise, if we haven't looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we've found either a longer route to that place or one precisely as long as the existing one, and we don't need to explore it. +The search then operates by taking the next item in the list and exploring that, which means it looks at all roads going from that place. If one of them is the goal, a finished route can be returned. Otherwise, if we haven't looked at this place before, a new item is added to the list. If we have looked at it before, since we are looking at short routes first, we've found either a longer route to that place or one precisely as long as the existing one, and we don't need to explore it. -You can visually imagine this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route. +You can visualize this as a web of known routes crawling out from the start location, growing evenly on all sides (but never tangling back into itself). As soon as the first thread reaches the goal location, that thread is traced back to the start, giving us our route. {{index "connected graph"}} @@ -372,7 +368,7 @@ runRobotAnimation(VillageState.random(), if}} -This robot usually finishes the task of delivering 5 parcels in about 16 turns. That's slightly better than `routeRobot` but still definitely not optimal. +This robot usually finishes the task of delivering 5 parcels in about 16 turns. That's slightly better than `routeRobot` but still definitely not optimal. We'll continue refining it in the exercises. ## Exercises @@ -382,7 +378,7 @@ This robot usually finishes the task of delivering 5 parcels in about 16 turns. It's hard to objectively compare ((robot))s by just letting them solve a few scenarios. Maybe one robot just happened to get easier tasks or the kind of tasks that it is good at, whereas the other didn't. -Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task. +Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let both of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task. For the sake of fairness, make sure you give each task to both robots, rather than generating different tasks per robot. @@ -441,11 +437,9 @@ hint}} Most data structures provided in a standard JavaScript environment aren't very well suited for persistent use. Arrays have `slice` and `concat` methods, which allow us to easily create new arrays without damaging the old one. But `Set`, for example, has no methods for creating a new set with an item added or removed. -Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. - -Its `add` method, however, should return a _new_ `PGroup` instance with the given member added and leave the old one unchanged. Similarly, `delete` creates a new instance without a given member. +Write a new class `PGroup`, similar to the `Group` class from [Chapter ?](object#groups), which stores a set of values. Like `Group`, it has `add`, `delete`, and `has` methods. Its `add` method, however, should return a _new_ `PGroup` instance with the given member added and leave the old one unchanged. Similarly, `delete` should create a new instance without a given member. -The class should work for values of any type, not just strings. It does _not_ have to be efficient when used with large amounts of values. +The class should work for values of any type, not just strings. It does _not_ have to be efficient when used with large numbers of values. {{index [interface, object]}} @@ -453,7 +447,7 @@ The ((constructor)) shouldn't be part of the class's interface (though you'll de {{index singleton}} -Why do you need only one `PGroup.empty` value, rather than having a function that creates a new, empty map every time? +Why do you need only one `PGroup.empty` value rather than having a function that creates a new, empty map every time? {{if interactive @@ -480,13 +474,13 @@ if}} {{index "persistent map (exercise)", "Set class", [array, creation], "PGroup class"}} -The most convenient way to represent the set of member values is still as an array since arrays are easy to copy. +The most convenient way to represent the set of member values is still as an array, since arrays are easy to copy. {{index "concat method", "filter method"}} When a value is added to the group, you can create a new group with a copy of the original array that has the value added (for example, using `concat`). When a value is deleted, you filter it from the array. -The class's ((constructor)) can take such an array as argument and store it as the instance's (only) property. This array is never updated. +The class's ((constructor)) can take such an array as its argument and store it as the instance's (only) property. This array is never updated. {{index "static property"}} diff --git a/08_error.md b/08_error.md index 35a3bc3e..f191c9ae 100644 --- a/08_error.md +++ b/08_error.md @@ -14,13 +14,13 @@ quote}} Flaws in computer programs are usually called _((bug))s_. It makes programmers feel good to imagine them as little things that just happen to crawl into our work. In reality, of course, we put them there ourselves. -If a program is crystallized thought, you can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter. +If a program is crystallized thought, we can roughly categorize bugs into those caused by the thoughts being confused and those caused by mistakes introduced while converting a thought to code. The former type is generally harder to diagnose and fix than the latter. ## Language {{index parsing, analysis}} -Many mistakes could be pointed out to us automatically by the computer, if it knew enough about what we're trying to do. But here JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. And even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. +Many mistakes could be pointed out to us automatically by the computer if it knew enough about what we're trying to do. But here, JavaScript's looseness is a hindrance. Its concept of bindings and properties is vague enough that it will rarely catch ((typo))s before actually running the program. Even then, it allows you to do some clearly nonsensical things without complaint, such as computing `true * "monkey"`. {{index [syntax, error], [property, access]}} @@ -28,7 +28,7 @@ There are some things that JavaScript does complain about. Writing a program tha {{index NaN, error}} -But often, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. +Often, however, your nonsense computation will merely produce `NaN` (not a number) or an undefined value, while the program happily continues, convinced that it's doing something meaningful. The mistake will manifest itself only later, after the bogus value has traveled through several functions. It might not trigger an error at all, but silently cause the program's output to be wrong. Finding the source of such problems can be difficult. The process of finding mistakes—bugs—in programs is called _((debugging))_. @@ -38,7 +38,7 @@ The process of finding mistakes—bugs—in programs is called _((debugging))_. {{indexsee "use strict", "strict mode"}} -JavaScript can be made a _little_ stricter by enabling _strict mode_. This is done by putting the string `"use strict"` at the top of a file or a function body. Here's an example: +JavaScript can be made a _little_ stricter by enabling _strict mode_. This can done by putting the string `"use strict"` at the top of a file or a function body. Here's an example: ```{test: "error \"ReferenceError: counter is not defined\""} function canYouSpotTheProblem() { @@ -52,6 +52,10 @@ canYouSpotTheProblem(); // → ReferenceError: counter is not defined ``` +{{index ECMAScript, compatibility}} + +Code inside classes and modules (which we will discuss in [Chapter ?](modules)) is automatically strict. The old nonstrict behavior still exists only because some old code might depend on it, and the language designers work hard to avoid breaking any existing programs. + {{index "let keyword", [binding, global]}} Normally, when you forget to put `let` in front of your binding, as with `counter` in the example, JavaScript quietly creates a global binding and uses that. In strict mode, an ((error)) is reported instead. This is very helpful. It should be noted, though, that this doesn't work when the binding in question already exists somewhere in scope. In that case, the loop will still quietly overwrite the value of the binding. @@ -71,7 +75,7 @@ console.log(name); {{index error}} -So the bogus call to `Person` succeeded but returned an undefined value and created the global binding `name`. In strict mode, the result is different. +The bogus call to `Person` succeeded, but returned an undefined value and created the global binding `name`. In strict mode, the result is different. ```{test: "error \"TypeError: Cannot set properties of undefined (setting 'name')\""} "use strict"; @@ -82,7 +86,7 @@ let ferdinand = Person("Ferdinand"); // forgot new We are immediately told that something is wrong. This is helpful. -Fortunately, constructors created with the `class` notation will always complain if they are called without `new`, making this less of a problem even in non-strict mode. +Fortunately, constructors created with the `class` notation will always complain if they are called without `new`, making this less of a problem even in nonstrict mode. {{index parameter, [binding, naming], "with statement"}} @@ -117,7 +121,7 @@ One thing about types is that they need to introduce their own complexity to be When the types of a program are known, it is possible for the computer to _check_ them for you, pointing out mistakes before the program is run. There are several JavaScript dialects that add types to the language and check them. The most popular one is called [TypeScript](https://www.typescriptlang.org/). If you are interested in adding more rigor to your programs, I recommend you give it a try. -In this book, we'll continue using raw, dangerous, untyped JavaScript code. +In this book, we will continue using raw, dangerous, untyped JavaScript code. ## Testing @@ -125,9 +129,9 @@ In this book, we'll continue using raw, dangerous, untyped JavaScript code. If the language is not going to do much to help us find mistakes, we'll have to find them the hard way: by running the program and seeing whether it does the right thing. -Doing this by hand, again and again, is a really bad idea. Not only is it annoying, it also tends to be ineffective since it takes too much time to exhaustively test everything every time you make a change. +Doing this by hand, again and again, is a really bad idea. Not only is it annoying but it also tends to be ineffective, since it takes too much time to exhaustively test everything every time you make a change. -Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice, rather than randomly running into it at some later time. +Computers are good at repetitive tasks, and testing is the ideal repetitive task. Automated testing is the process of writing a program that tests another program. Writing tests is a bit more work than testing manually, but once you've done it, you gain a kind of superpower: it takes you only a few seconds to verify that your program still behaves properly in all the situations you wrote tests for. When you break something, you'll immediately notice rather than randomly running into it at some later time. {{index "toUpperCase method"}} @@ -196,7 +200,7 @@ Even if you see the problem already, pretend for a moment that you don't. We kno {{index "trial and error"}} -This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, _think_. Analyze what is happening and come up with a ((theory)) of why it might be happening. Then, make additional observations to test this theory—or, if you don't yet have a theory, make additional observations to help you come up with one. +This is where you must resist the urge to start making random changes to the code to see whether that makes it better. Instead, _think_. Analyze what is happening and come up with a ((theory)) of why it might be happening. Then make additional observations to test this theory—or, if you don't yet have a theory, make additional observations to help you come up with one. {{index "console.log", output, debugging, logging}} @@ -217,9 +221,9 @@ _Right_. Dividing 13 by 10 does not produce a whole number. Instead of `n /= bas {{index "JavaScript console", "debugger statement"}} -An alternative to using `console.log` to peek into the program's behavior is to use the _debugger_ capabilities of your browser. Browsers come with the ability to set a _((breakpoint))_ on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won't go into details, as debuggers differ from browser to browser, but look in your browser's ((developer tools)) or search the Web for instructions. +An alternative to using `console.log` to peek into the program's behavior is to use the _debugger_ capabilities of your browser. Browsers come with the ability to set a _((breakpoint))_ on a specific line of your code. When the execution of the program reaches a line with a breakpoint, it is paused, and you can inspect the values of bindings at that point. I won't go into details, as debuggers differ from browser to browser, but look in your browser's ((developer tools)) or search the web for instructions. -Another way to set a breakpoint is to include a `debugger` statement (consisting of simply that keyword) in your program. If the ((developer tools)) of your browser are active, the program will pause whenever it reaches such a statement. +Another way to set a breakpoint is to include a `debugger` statement (consisting simply of that keyword) in your program. If the ((developer tools)) of your browser are active, the program will pause whenever it reaches such a statement. ## Error propagation @@ -229,7 +233,7 @@ Not all problems can be prevented by the programmer, unfortunately. If your prog {{index "error recovery"}} -If you're programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. But in either situation, the program has to actively do something in response to the problem. +If you're programming only for yourself, you can afford to just ignore such problems until they occur. But if you build something that is going to be used by anybody else, you usually want the program to do better than just crash. Sometimes the right thing to do is take the bad input in stride and continue running. In other cases, it is better to report to the user what went wrong and then give up. In either situation the program has to actively do something in response to the problem. {{index "promptNumber function", validation}} @@ -237,7 +241,7 @@ Say you have a function `promptNumber` that asks the user for a number and retur {{index null, undefined, "return value", "special return value"}} -One option is to make it return a special value. Common choices for such values are `null`, `undefined`, or -1. +One option is to make it return a special value. Common choices for such values are `null`, `undefined`, or `-1`. ```{test: no} function promptNumber(question) { @@ -267,7 +271,7 @@ function lastElement(array) { {{index "special return value", readability}} -The second issue with returning special values is that it can lead to awkward code. If a piece of code calls `promptNumber` 10 times, it has to check 10 times whether `null` was returned. And if its response to finding `null` is to simply return `null` itself, callers of the function will in turn have to check for it, and so on. +The second issue with returning special values is that it can lead to awkward code. If a piece of code calls `promptNumber` 10 times, it has to check 10 times whether `null` was returned. If its response to finding `null` is to simply return `null` itself, callers of the function will in turn have to check for it, and so on. ## Exceptions @@ -277,7 +281,7 @@ When a function cannot proceed normally, what we would often _like_ to do is jus {{index ["control flow", exceptions], "raising (exception)", "throw keyword", "call stack"}} -Exceptions are a mechanism that makes it possible for code that runs into a problem to _raise_ (or _throw_) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called _((unwinding the stack))_. You may remember the stack of function calls that was mentioned in [Chapter ?](functions#stack). An exception zooms down this stack, throwing away all the call contexts it encounters. +Exceptions are a mechanism that makes it possible for code that runs into a problem to _raise_ (or _throw_) an exception. An exception can be any value. Raising one somewhat resembles a super-charged return from a function: it jumps out of not just the current function but also its callers, all the way down to the first call that started the current execution. This is called _((unwinding the stack))_. You may remember the stack of function calls mentioned in [Chapter ?](functions#stack). An exception zooms down this stack, throwing away all the call contexts it encounters. {{index "error handling", [syntax, statement], "catch keyword"}} @@ -333,7 +337,7 @@ This means when code has several side effects, even if its "regular" control flo {{index "banking example"}} -Here is some really bad banking code. +Here is some really bad banking code: ```{includeCode: true} const accounts = { @@ -367,7 +371,7 @@ One way to address this is to use fewer side effects. Again, a programming style {{index block, "try keyword", "finally keyword"}} -But that isn't always practical. So there is another feature that `try` statements have. They may be followed by a `finally` block either instead of or in addition to a `catch` block. A `finally` block says "no matter _what_ happens, run this code after trying to run the code in the `try` block." +Since that isn't always practical, `try` statements have another feature: they may be followed by a `finally` block either instead of or in addition to a `catch` block. A `finally` block says "no matter _what_ happens, run this code after trying to run the code in the `try` block." ```{includeCode: true} function transfer(from, amount) { @@ -438,13 +442,13 @@ for (;;) { {{index "infinite loop", "for loop", "catch keyword", debugging}} -The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. _But_ we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop, it "buries" the useful error message about the misspelled binding. +The `for (;;)` construct is a way to intentionally create a loop that doesn't terminate on its own. We break out of the loop only when a valid direction is given. Unfortunately, we misspelled `promptDirection`, which will result in an "undefined variable" error. Because the `catch` block completely ignores its exception value (`e`), assuming it knows what the problem is, it wrongly treats the binding error as indicating bad input. Not only does this cause an infinite loop but it also "buries" the useful error message about the misspelled binding. As a general rule, don't blanket-catch exceptions unless it is for the purpose of "routing" them somewhere—for example, over the network to tell another system that our program crashed. And even then, think carefully about how you might be hiding information. {{index "exception handling"}} -So we want to catch a _specific_ kind of exception. We can do this by checking in the `catch` block whether the exception we got is the one we are interested in and rethrowing it otherwise. But how do we recognize an exception? +We want to catch a _specific_ kind of exception. We can do this by checking in the `catch` block whether the exception we got is the one we are interested in, and if not, rethrow it. But how do we recognize an exception? We could compare its `message` property against the ((error)) message we happen to expect. But that's a shaky way to write code—we'd be using information that's intended for human consumption (the message) to make a programmatic decision. As soon as someone changes (or translates) the message, the code will stop working. diff --git a/09_regexp.md b/09_regexp.md index 064de87b..02eae5bb 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -22,7 +22,7 @@ if}} {{index evolution, adoption, integration}} -Programming ((tool))s and techniques survive and spread in a chaotic, evolutionary way. It's not always the best or brilliant ones that win but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology. +Programming ((tool))s and techniques survive and spread in a chaotic, evolutionary way. It's not always the best or most brilliant ones that win, but rather the ones that function well enough within the right niche or that happen to be integrated with another successful piece of technology. {{index "domain-specific language"}} @@ -30,7 +30,7 @@ In this chapter, I will discuss one such tool, _((regular expression))s_. Regula {{index [interface, design]}} -Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic, and the programming interface JavaScript provides for them is clumsy. But they are a powerful ((tool)) for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer. +Regular expressions are both terribly awkward and extremely useful. Their syntax is cryptic and the programming interface JavaScript provides for them is clumsy. But they are a powerful ((tool)) for inspecting and processing strings. Properly understanding regular expressions will make you a more effective programmer. ## Creating a regular expression @@ -111,7 +111,7 @@ A number of common character groups have their own built-in shortcuts. Digits ar | `\S` | A nonwhitespace character | `.` | Any character except for newline -So you could match a ((date)) and ((time)) format like 01-30-2003 15:20 with the following expression: +You could match a ((date)) and ((time)) format like 01-30-2003 15:20 with the following expression: ``` let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/; @@ -123,11 +123,11 @@ console.log(dateTime.test("30-jan-2003 15:20")); {{index ["backslash character", "in regular expressions"]}} -That looks completely awful, doesn't it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual ((pattern)) expressed. We'll see a slightly improved version of this expression [later](regexp#date_regexp_counted). +That regular expression looks completely awful, doesn't it? Half of it is backslashes, producing a background noise that makes it hard to spot the actual ((pattern)) expressed. We'll see a slightly improved version of this expression [later](regexp#date_regexp_counted). {{index [escaping, "in regexps"], "regular expression", set}} -These backslash codes can also be used inside ((square brackets)). For example, `[\d.]` means any digit or a period character. But the period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as `+`. +These backslash codes can also be used inside ((square brackets)). For example, `[\d.]` means any digit or a period character. The period itself, between square brackets, loses its special meaning. The same goes for other special characters, such as the plus sign (`+`). {{index "square brackets", inversion, "caret character"}} @@ -153,14 +153,14 @@ By a strange historical accident, `\s` (whitespace) does not have this problem a {{index "character category", [Unicode, property]}} -It is possible to use `\p` in a regular expression to match all characters to which the Unicode standard assigns a given property. This allows us to match things like letters in a more cosmopolitan way. However, again due to compatibility with the original language standards, those are only recognized when you put a `u` character (for ((Unicode))) after the regular expression. +It is possible to use `\p` in a regular expression to match all characters to which the Unicode standard assigns a given property. This allows us to match things like letters in a more cosmopolitan way. However, again due to compatibility with the original language standards, those are recognized only when you put a `u` character (for ((Unicode))) after the regular expression. {{table {cols: [1, 5]}}} | `\p{L}` | Any letter | `\p{N}` | Any numeric character | `\p{P}` | Any punctuation character -| `\P{L}` | Any non-letter (uppercase P inverts) +| `\P{L}` | Any nonletter (uppercase P inverts) | `\p{Script=Hangul}` | Any character from the given script (see [Chapter ?](higher_order#scripts)) Using `\w` for text processing that may need to handle non-English text (or even English text with borrowed words like “cliché”) is a liability, since it won't treat characters like “é” as letters. Though they tend to be a bit more verbose, `\p` property groups are more robust. @@ -207,7 +207,7 @@ The star (`*`) has a similar meaning but also allows the pattern to match zero t {{index "British English", "American English", "question mark"}} -A question mark makes a part of a pattern _((optional))_, meaning it may occur zero times or one time. In the following example, the _u_ character is allowed to occur, but the pattern also matches when it is missing. +A question mark (`?`) makes a part of a pattern _((optional))_, meaning it may occur zero times or one time. In the following example, the _u_ character is allowed to occur, but the pattern also matches when it is missing: ``` let neighbor = /neighbou?r/; @@ -231,13 +231,13 @@ console.log(dateTime.test("1-30-2003 8:45")); // → true ``` -You can also specify open-ended ((range))s when using braces by omitting the number after the comma. So, `{5,}` means five or more times. +You can also specify open-ended ((range))s when using braces by omitting the number after the comma. For example, `{5,}` means five or more times. ## Grouping subexpressions {{index ["regular expression", grouping], grouping, [parentheses, "in regular expressions"]}} -To use an operator like `*` or `+` on more than one element at a time, you have to use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned. +To use an operator like `*` or `+` on more than one element at a time, you must use parentheses. A part of a regular expression that is enclosed in parentheses counts as a single element as far as the operators following it are concerned. ``` let cartoonCrying = /boo+(hoo+)+/i; @@ -247,7 +247,7 @@ console.log(cartoonCrying.test("Boohoooohoohooo")); {{index crying}} -The first and second `+` characters apply only to the second _o_ in _boo_ and _hoo_, respectively. The third `+` applies to the whole group `(hoo+)`, matching one or more sequences like that. +The first and second `+` characters apply only to the second `o` in `boo` and `hoo`, respectively. The third `+` applies to the whole group `(hoo+)`, matching one or more sequences like that. {{index "case sensitivity", capitalization, ["regular expression", flags]}} @@ -292,7 +292,7 @@ console.log(quotedText.exec("she said 'hello'")); {{index "capture group"}} -When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold `undefined`. And when a group is matched multiple times (for example when it is followed by a `+`), only the last match ends up in the array. +When a group does not end up being matched at all (for example, when followed by a question mark), its position in the output array will hold `undefined`. When a group is matched multiple times (for example, when followed by a `+`), only the last match ends up in the array. ``` console.log(/bad(ly)?/.exec("bad")); @@ -312,13 +312,13 @@ console.log(/(?:na)+/.exec("banana")); Groups can be useful for extracting parts of a string. If we don't just want to verify whether a string contains a ((date)) but also extract it and construct an object that represents it, we can wrap parentheses around the digit patterns and directly pick the date out of the result of `exec`. -But first we'll take a brief detour, in which we discuss the built-in way to represent date and ((time)) values in JavaScript. +But first we'll take a brief detour to discuss the built-in way to represent date and ((time)) values in JavaScript. ## The Date class {{index constructor, "Date class"}} -JavaScript has a standard class for representing ((date))s—or, rather, points in ((time)). It is called `Date`. If you simply create a date object using `new`, you get the current date and time. +JavaScript has a standard `Date` class for representing ((date))s, or rather, points in ((time)). If you simply create a date object using `new`, you get the current date and time. ```{test: no} console.log(new Date()); @@ -359,7 +359,7 @@ If you give the `Date` constructor a single argument, that argument is treated a {{index "getFullYear method", "getMonth method", "getDate method", "getHours method", "getMinutes method", "getSeconds method", "getYear method"}} -Date objects provide methods such as `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes`, and `getSeconds` to extract their components. Besides `getFullYear` there's also `getYear`, which gives you the year minus 1900 (`98` or `119`) and is mostly useless. +Date objects provide methods such as `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes`, and `getSeconds` to extract their components. Besides `getFullYear` there's also `getYear`, which gives you the year minus 1900 (such as `98` or `125`) and is mostly useless. {{index "capture group", "getDate method", [parentheses, "in regular expressions"]}} @@ -377,7 +377,7 @@ console.log(getDate("1-30-2003")); {{index destructuring, "underscore character"}} -The `_` (underscore) binding is ignored and used only to skip the full match element in the array returned by `exec`. +The underscore (`_`) binding is ignored and used only to skip the full match element in the array returned by `exec`. ## Boundaries and look-ahead @@ -387,17 +387,17 @@ Unfortunately, `getDate` will also happily extract a date from the string `"100- {{index boundary, "caret character", "dollar sign"}} -If we want to enforce that the match must span the whole string, we can add the markers `^` and `$`. The caret matches the start of the input string, whereas the dollar sign matches the end. So, `/^\d+$/` matches a string consisting entirely of one or more digits, `/^!/` matches any string that starts with an exclamation mark, and `/x^/` does not match any string (there cannot be an _x_ before the start of the string). +If we want to enforce that the match must span the whole string, we can add the markers `^` and `$`. The caret matches the start of the input string, whereas the dollar sign matches the end. Thus `/^\d+$/` matches a string consisting entirely of one or more digits, `/^!/` matches any string that starts with an exclamation mark, and `/x^/` does not match any string (there cannot be an `x` before the start of the string). {{index "word boundary", "word character"}} -There is also a `\b` marker, which matches “word boundaries”, positions that have a word character one side, and a non-word character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w`, and are therefore not very reliable. +There is also a `\b` marker that matches _word boundaries_, positions that have a word character on one side, and a nonword character on the other. Unfortunately, these use the same simplistic concept of word characters as `\w` and are therefore not very reliable. -Note that these markers don't match any actual characters. They just enforces that a given condition holds at the place where they appears in the pattern. +Note that these boundary markers don't match any actual characters. They just enforce that a given condition holds at the place where it appears in the pattern. {{index "look-ahead"}} -_Look-ahead_ tests do something similar. They provide a pattern, and will make the match fail if the input doesn't match that pattern, but don't actually move the match position forward. They are written between `(?=` and `)`. +_Look-ahead_ tests do something similar. They provide a pattern and will make the match fail if the input doesn't match that pattern, but don't actually move the match position forward. They are written between `(?=` and `)`. ``` console.log(/a(?=e)/.exec("braeburn")); @@ -406,7 +406,7 @@ console.log(/a(?! )/.exec("a b")); // → null ``` -Note how the `e` in the first example is necessary to match, but is not part of the matched string. The `(?! )` notation expresses a _negative_ look-ahead. This only matches if the pattern in the parentheses _doesn't_ match, causing the second example to only match “a” characters that don't have a space after them. +The `e` in the first example is necessary to match, but is not part of the matched string. The `(?! )` notation expresses a _negative_ look-ahead. This matches only if the pattern in the parentheses _doesn't_ match, causing the second example to match only `a` characters that don't have a space after them. ## Choice patterns @@ -414,7 +414,7 @@ Note how the `e` in the first example is necessary to match, but is not part of Say we want to know whether a piece of text contains not only a number but a number followed by one of the words _pig_, _cow_, or _chicken_, or any of their plural forms. -We could write three regular expressions and test them in turn, but there is a nicer way. The ((pipe character)) (`|`) denotes a ((choice)) between the pattern to its left and the pattern to its right. So I can say this: +We could write three regular expressions and test them in turn, but there is a nicer way. The ((pipe character)) (`|`) denotes a ((choice)) between the pattern to its left and the pattern to its right. We can use it in expressions like this: ``` let animalCount = /\d+ (pig|cow|chicken)s?/; @@ -426,13 +426,13 @@ console.log(animalCount.test("15 pugs")); {{index [parentheses, "in regular expressions"]}} -Parentheses can be used to limit the part of the pattern that the pipe operator applies to, and you can put multiple such operators next to each other to express a choice between more than two alternatives. +Parentheses can be used to limit the part of the pattern to which the pipe operator applies, and you can put multiple such operators next to each other to express a choice between more than two alternatives. ## The mechanics of matching {{index ["regular expression", matching], [matching, algorithm], "search problem"}} -Conceptually, when you use `exec` or `test`, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on, until it finds a match or reaches the end of the string. It'll either return the first match that can be found or fail to find any match at all. +Conceptually, when you use `exec` or `test`, the regular expression engine looks for a match in your string by trying to match the expression first from the start of the string, then from the second character, and so on until it finds a match or reaches the end of the string. It'll either return the first match that can be found or fail to find any match at all. {{index ["regular expression", matching], [matching, algorithm]}} @@ -442,7 +442,7 @@ To do the actual matching, the engine treats a regular expression something like {{index traversal}} -Our expression matches if we can find a path from the left side of the diagram to the right side. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box. +If we can find a path from the left side of the diagram to the right side, our expression matches. We keep a current position in the string, and every time we move through a box, we verify that the part of the string after our current position matches that box. {{id backtracking}} @@ -450,23 +450,23 @@ Our expression matches if we can find a path from the left side of the diagram t {{index ["regular expression", backtracking], "binary number", "decimal number", "hexadecimal number", "flow diagram", [matching, algorithm], backtracking}} -The regular expression `/^([01]+b|[\da-f]+h|\d+)$/` matches either a binary number followed by a _b_, a hexadecimal number (that is, base 16, with the letters _a_ to _f_ standing for the digits 10 to 15) followed by an _h_, or a regular decimal number with no suffix character. This is the corresponding diagram: +The regular expression `/^([01]+b|[\da-f]+h|\d+)$/` matches either a binary number followed by a `b`, a hexadecimal number (that is, base 16, with the letters `a` to `f` standing for the digits 10 to 15) followed by an `h`, or a regular decimal number with no suffix character. This is the corresponding diagram: {{figure {url: "img/re_number.svg", alt: "Railroad diagram for the regular expression '^([01]+b|\\d+|[\\da-f]+h)$'"}}} {{index branching}} -When matching this expression, it will often happen that the top (binary) branch is entered even though the input does not actually contain a binary number. When matching the string `"103"`, for example, it becomes clear only at the 3 that we are in the wrong branch. The string _does_ match the expression, just not the branch we are currently in. +When matching this expression, the top (binary) branch will often be entered even though the input does not actually contain a binary number. When matching the string `"103"`, for example, it becomes clear only at the `3` that we are in the wrong branch. The string _does_ match the expression, just not the branch we are currently in. {{index backtracking, "search problem"}} -So the matcher _backtracks_. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string `"103"`, after encountering the 3 character, it will start trying the branch for hexadecimal numbers, which fails again because there is no _h_ after the number. So it tries the decimal number branch. This one fits, and a match is reported after all. +So the matcher _backtracks_. When entering a branch, it remembers its current position (in this case, at the start of the string, just past the first boundary box in the diagram) so that it can go back and try another branch if the current one does not work out. For the string `"103"`, after encountering the `3` character, the matcher starts trying the branch for hexadecimal numbers, which fails again because there is no `h` after the number. It then tries the decimal number branch. This one fits, and a match is reported after all. {{index [matching, algorithm]}} The matcher stops as soon as it finds a full match. This means that if multiple branches could potentially match a string, only the first one (ordered by where the branches appear in the regular expression) is used. -Backtracking also happens for ((repetition)) operators like + and `*`. If you match `/^.*x/` against `"abcxe"`, the `.*` part will first try to consume the whole string. The engine will then realize that it needs an _x_ to match the pattern. Since there is no _x_ past the end of the string, the star operator tries to match one character less. But the matcher doesn't find an _x_ after `abcx` either, so it backtracks again, matching the star operator to just `abc`. _Now_ it finds an _x_ where it needs it and reports a successful match from positions 0 to 4. +Backtracking also happens for ((repetition)) operators like + and `*`. If you match `/^.*x/` against `"abcxe"`, the `.*` part will first try to consume the whole string. The engine will then realize that it needs an `x` to match the pattern. Since there is no `x` past the end of the string, the star operator tries to match one character less. But the matcher doesn't find an `x` after `abcx` either, so it backtracks again, matching the star operator to just `abc`. _Now_ it finds an `x` where it needs it and reports a successful match from positions 0 to 4. {{index performance, complexity}} @@ -536,15 +536,15 @@ console.log(stock.replace(/(\d+) (\p{L}+)/gu, minusOne)); // → no lemon, 1 cabbage, and 100 eggs ``` -This takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string that has one less of every such quantity. +This code takes a string, finds all occurrences of a number followed by an alphanumeric word, and returns a string that has one less of every such quantity. -The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works since it matched `\d+`—and makes some adjustments in case there is only one or zero left. +The `(\d+)` group ends up as the `amount` argument to the function, and the `(\p{L}+)` group gets bound to `unit`. The function converts `amount` to a number—which always works, since it matched `\d+` earlier—and makes some adjustments in case there is only one or zero left. ## Greed {{index greed, "regular expression"}} -It is possible to use `replace` to write a function that removes all ((comment))s from a piece of JavaScript ((code)). Here is a first attempt: +We can use `replace` to write a function that removes all ((comment))s from a piece of JavaScript ((code)). Here is a first attempt: ```{test: wrap} function stripComments(code) { @@ -560,7 +560,7 @@ console.log(stripComments("1 /* a */+/* b */ 1")); {{index "period character", "slash character", "newline character", "empty set", "block comment", "line comment"}} -The part before the _or_ operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use `[^]` (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters. +The part before the `|` operator matches two slash characters followed by any number of non-newline characters. The part for multiline comments is more involved. We use `[^]` (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a period here because block comments can continue on a new line, and the period character does not match newline characters. But the output for the last line appears to have gone wrong. Why? @@ -586,7 +586,7 @@ A lot of ((bug))s in ((regular expression)) programs can be traced to unintentio {{index ["regular expression", creation], "underscore character", "RegExp class"}} -There are cases where you might not know the exact ((pattern)) you need to match against when you are writing your code. Say you want to test for the user's name in a piece of text. You can build up a string and use the `RegExp` ((constructor)) on that. Here's an example: +In some cases you may not know the exact ((pattern)) you need to match against when you are writing your code. Say you want to test for the user's name in a piece of text. You can build up a string and use the `RegExp` ((constructor)) on that. ``` let name = "harry"; @@ -619,7 +619,7 @@ console.log(regexp.test(text)); {{index ["regular expression", methods], "indexOf method", "search method"}} -The `indexOf` method on strings cannot be called with a regular expression. But there is another method, `search`, that does expect a regular expression. Like `indexOf`, it returns the first index on which the expression was found, or -1 when it wasn't found. +While the `indexOf` method on strings cannot be called with a regular expression, there is another method, `search`, that does expect a regular expression. Like `indexOf`, it returns the first index on which the expression was found, or -1 when it wasn't found. ``` console.log(" word".search(/\S/)); @@ -656,9 +656,9 @@ console.log(pattern.lastIndex); {{index "side effect", "lastIndex property"}} -If the match was successful, the call to `exec` automatically updates the `lastIndex` property to point after the match. If no match was found, `lastIndex` is set back to zero, which is also the value it has in a newly constructed regular expression object. +If the match was successful, the call to `exec` automatically updates the `lastIndex` property to point after the match. If no match was found, `lastIndex` is set back to 0, which is also the value it has in a newly constructed regular expression object. -The difference between the global and the sticky options is that, when sticky is enabled, the match will succeed only if it starts directly at `lastIndex`, whereas with global, it will search ahead for a position where a match can start. +The difference between the global and the sticky options is that when sticky is enabled, the match will succeed only if it starts directly at `lastIndex`, whereas with global, it will search ahead for a position where a match can start. ``` let global = /abc/g; @@ -671,7 +671,7 @@ console.log(sticky.exec("xyz abc")); {{index bug}} -When using a shared regular expression value for multiple `exec` calls, these automatic updates to the `lastIndex` property can cause problems. Your regular expression might be accidentally starting at an index that was left over from a previous call. +When using a shared regular expression value for multiple `exec` calls, these automatic updates to the `lastIndex` property can cause problems. Your regular expression might be accidentally starting at an index left over from a previous call. ``` let digit = /\d/g; @@ -690,9 +690,7 @@ console.log("Banana".match(/an/g)); // → ["an", "an"] ``` -So be cautious with global regular expressions. The cases where they are necessary—calls to `replace` and places where you want to explicitly use `lastIndex`—are typically the only places where you want to use them. - -### Getting all matches +So be cautious with global regular expressions. The cases where they are necessary—calls to `replace` and places where you want to explicitly use `lastIndex`—are typically the situations where you want to use them. {{index "lastIndex property", "exec method", loop}} @@ -711,14 +709,14 @@ for (let match of matches) { {{index ["regular expression", global]}} -This method returns an array of match arrays. The regular expression given it _must_ have `g` enabled. +This method returns an array of match arrays. The regular expression given to `matchAll` _must_ have `g` enabled. {{id ini}} ## Parsing an INI file {{index comment, "file format", "enemies example", "INI file"}} -To conclude the chapter, we'll look at a problem that calls for ((regular expression))s. Imagine we are writing a program to automatically collect information about our enemies from the ((Internet)). (We will not actually write that program here, just the part that reads the ((configuration)) file. Sorry.) The configuration file looks like this: +To conclude the chapter, we'll look at a problem that calls for ((regular expression))s. Imagine we are writing a program to automatically collect information about our enemies from the ((internet)). (We will not actually write that program here, just the part that reads the ((configuration)) file. Sorry.) The configuration file looks like this: ```{lang: "null"} searchengine=https://duckduckgo.com/?q=$1 @@ -739,7 +737,7 @@ outputdir=/home/marijn/enemies/davaeorn {{index grammar}} -The exact rules for this format (which is a widely used format, usually called an _INI_ file) are as follows: +The exact rules for this format—which is a widely used file format, usually called an _INI_ file—are as follows: - Blank lines and lines starting with semicolons are ignored. @@ -796,11 +794,11 @@ The pattern `if (match = string.match(...))` makes use of the fact that the valu {{index [parentheses, "in regular expressions"]}} -If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression `/^\s*(;|$)/` to match lines that either contain only space, or space followed by a semicolon (making the rest of the line a comment). When a line doesn't match any of the expected forms, the function throws an exception. +If a line is not a section header or a property, the function checks whether it is a comment or an empty line using the expression `/^\s*(;|$)/` to match lines that either contain only whitespace, or whitespace followed by a semicolon (making the rest of the line a comment). When a line doesn't match any of the expected forms, the function throws an exception. ## Code units and characters -Another design mistake that's been standardized, in JavaScript regular expressions, is that by default, operator like `.` or `?` work on code units, as discussed in [Chapter ?](higher_order#code_units), not actual characters. This means characters that are composed of two code units behave strangely. +Another design mistake that's been standardized in JavaScript regular expressions is that by default, operators like `.` or `?` work on code units (as discussed in [Chapter ?](higher_order#code_units)), not actual characters. This means characters that are composed of two code units behave strangely. ``` console.log(/🍎{3}/.test("🍎🍎🍎")); @@ -811,7 +809,7 @@ console.log(/<.>/u.test("<🌹>")); // → true ``` -The problem is that the 🍎 in the first line is treated as two code units, and the `{3}` part is applied only to the second one. Similarly, the dot matches a single code unit, not the two that make up the rose ((emoji)). +The problem is that the 🍎 in the first line is treated as two code units, and `{3}` is applied only to the second unit. Similarly, the dot matches a single code unit, not the two that make up the rose ((emoji)). You must add the `u` (Unicode) option to your regular expression to make it treat such characters properly. @@ -852,21 +850,21 @@ A regular expression has a method `test` to test whether a given string matches Strings have a `match` method to match them against a regular expression and a `search` method to search for one, returning only the starting position of the match. Their `replace` method can replace matches of a pattern with a replacement string or function. -Regular expressions can have options, which are written after the closing slash. The `i` option makes the match case insensitive. The `g` option makes the expression _global_, which, among other things, causes the `replace` method to replace all instances instead of just the first. The `y` option makes it sticky, which means that it will not search ahead and skip part of the string when looking for a match. The `u` option turns on Unicode mode, which enables `\p` syntax and fixes a number of problems around the handling of characters that take up two code units. +Regular expressions can have options, which are written after the closing slash. The `i` option makes the match case insensitive. The `g` option makes the expression _global_, which, among other things, causes the `replace` method to replace all instances instead of just the first. The `y` option makes and expression sticky, which means that it will not search ahead and skip part of the string when looking for a match. The `u` option turns on Unicode mode, which enables `\p` syntax and fixes a number of problems around the handling of characters that take up two code units. -Regular expressions are a sharp ((tool)) with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things that they cannot cleanly express into them. +Regular expressions are a sharp ((tool)) with an awkward handle. They simplify some tasks tremendously but can quickly become unmanageable when applied to complex problems. Part of knowing how to use them is resisting the urge to try to shoehorn things into them that they cannot cleanly express. ## Exercises {{index debugging, bug}} -It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression's inexplicable ((behavior)). Sometimes it helps to enter your expression into an online tool like [_debuggex.com_](https://www.debuggex.com/) to see whether its visualization corresponds to what you intended and to ((experiment)) with the way it responds to various input strings. +It is almost unavoidable that, in the course of working on these exercises, you will get confused and frustrated by some regular expression's inexplicable ((behavior)). Sometimes it helps to enter your expression into an online tool like [_debuggex.com_](https://www.debuggex.com) to see whether its visualization corresponds to what you intended and to ((experiment)) with the way it responds to various input strings. ### Regexp golf {{index "program size", "code golf", "regexp golf (exercise)"}} -_Code golf_ is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, _regexp golf_ is the practice of writing as tiny a regular expression as possible to match a given pattern, and _only_ that pattern. +_Code golf_ is a term used for the game of trying to express a particular program in as few characters as possible. Similarly, _regexp golf_ is the practice of writing as tiny a regular expression as possible to match a given pattern and _only_ that pattern. {{index boundary, matching}} @@ -952,7 +950,7 @@ if}} {{index "quoting style (exercise)", boundary}} -The most obvious solution is to replace only quotes with a nonletter character on at least one side—something like `/\P{L}'|'\P{L}/`. But you also have to take the start and end of the line into account. +The most obvious solution is to replace only quotes with a nonletter character on at least one side—something like `/\P{L}'|'\P{L}/u`. But you also have to take the start and end of the line into account. {{index grouping, "replace method", [parentheses, "in regular expressions"]}} @@ -964,7 +962,7 @@ hint}} {{index sign, "fractional number", [syntax, number], minus, "plus character", exponent, "scientific notation", "period character"}} -Write an expression that matches only JavaScript-style ((number))s. It must support an optional minus _or_ plus sign in front of the number, the decimal dot, and exponent notation—`5e-3` or `1E10`—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, `.5` and `5.` are valid JavaScript numbers, but a lone dot _isn't_. +Write an expression that matches only JavaScript-style ((number))s. It must support an optional minus _or_ plus sign in front of the number, the decimal dot, and exponent notation—`5e-3` or `1E10`—again with an optional sign in front of the exponent. Also note that it is not necessary for there to be digits in front of or after the dot, but the number cannot be a dot alone. That is, `.5` and `5.` are valid JavaScript numbers, but a lone dot isn't. {{if interactive ```{test: no} diff --git a/10_modules.md b/10_modules.md index ae371435..0f253a7e 100644 --- a/10_modules.md +++ b/10_modules.md @@ -2,7 +2,7 @@ # Modules -{{quote {author: "Tef", title: "Programming is Terrible", chapter: true} +{{quote {author: "Tef", title: "programming is terrible", chapter: true} Write code that is easy to delete, not easy to extend. @@ -18,13 +18,13 @@ Ideally, a program has a clear, straightforward structure. The way it works is e {{index "organic growth"}} -In practice, programs grow organically. Pieces of functionality are added as the programmer identifies new needs. Keeping such a program well-structured requires constant attention and work. This is work that will pay off only in the future, the _next_ time someone works on the program. So it is tempting to neglect it and allow the various parts of the program to become deeply entangled. +In practice, programs grow organically. Pieces of functionality are added as the programmer identifies new needs. Keeping such a program well structured requires constant attention and work. This is work that will pay off only in the future, the _next_ time someone works on the program, so it's tempting to neglect it and allow the various parts of the program to become deeply entangled. {{index readability, reuse, isolation}} This causes two practical issues. First, understanding an entangled system is hard. If everything can touch everything else, it is difficult to look at any given piece in isolation. You are forced to build up a holistic understanding of the entire thing. Second, if you want to use any of the functionality from such a program in another situation, rewriting it may be easier than trying to disentangle it from its context. -The phrase "((big ball of mud))" is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and you only succeed in making a mess. +The phrase "((big ball of mud))" is often used for such large, structureless programs. Everything sticks together, and when you try to pick out a piece, the whole thing comes apart, and you succeed only in making a mess. ## Modular programs @@ -38,9 +38,9 @@ Module interfaces have a lot in common with object interfaces, as we saw them in {{index dependency}} -But the interface that a module provides for others to use is only half the story. A good module system also requires modules to specify which code _they_ use from other modules. These relations are called _dependencies_. If module A uses functionality from module B, it is said to _depend_ on it. When these are clearly specified in the module itself, they can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies. +But the interface that a module provides for others to use is only half the story. A good module system also requires modules to specify which code _they_ use from other modules. These relations are called _dependencies_. If module A uses functionality from module B, it is said to _depend_ on that module. When these are clearly specified in the module itself, they can be used to figure out which other modules need to be present to be able to use a given module and to automatically load dependencies. -When the ways in which modules interact with each other are explicit, a system becomes more like ((LEGO)), where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything. +When the ways in which modules interact with each other are explicit, a system becomes more like ((LEGO)), where pieces interact through well-defined connectors, and less like mud, where everything mixes with everything else. {{id es}} @@ -52,13 +52,13 @@ The original JavaScript language did not have any concept of a module. All scrip {{index "ES modules"}} -Since ECMAScript 2015, JavaScript supports two different types of programs. _Scripts_ behave in the old way: their bindings are defined in the global scope, and they have no way to directly reference other scripts. _Modules_ get their own separate scope, and support the `import` and `export` keywords, which aren't available in scripts, to declare their dependencies and interface. This module system is usually called _ES modules_ (where “ES” stands for “ECMAScript”). +Since ECMAScript 2015, JavaScript supports two different types of programs. _Scripts_ behave in the old way: their bindings are defined in the global scope, and they have no way to directly reference other scripts. _Modules_ get their own separate scope and support the `import` and `export` keywords, which aren't available in scripts, to declare their dependencies and interface. This module system is usually called _ES modules_ (where _ES_ stands for ECMAScript). A modular program is composed of a number of such modules, wired together via their imports and exports. {{index "Date class", "weekDay module"}} -This example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant which is not part of its interface, and two functions which are. It has no dependencies. +The following example module converts between day names and numbers (as returned by `Date`'s `getDay` method). It defines a constant that is not part of its interface, and two functions that are. It has no dependencies. ``` const names = ["Sunday", "Monday", "Tuesday", "Wednesday", @@ -87,11 +87,11 @@ The `import` keyword, followed by a list of binding names in braces, makes bindi {{index [module, resolution], resolution}} -How such a module name is resolved to an actual program differs by platform. The browser treats them as Web addresses, whereas Node.js resolves them to files. To run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. +How such a module name is resolved to an actual program differs by platform. The browser treats them as web addresses, whereas Node.js resolves them to files. When you run a module, all the other modules it depends on—and the modules _those_ depend on—are loaded, and the exported bindings are made available to the modules that import them. -Import and export declarations cannot appear inside of functions, loops, or other blocks. They are immediately resolved when the module is loaded, regardless of how the code in the module executes, and to reflect this they must appear only in the outer module body. +Import and export declarations cannot appear inside of functions, loops, or other blocks. They are immediately resolved when the module is loaded, regardless of how the code in the module executes. To reflect this, they must appear only in the outer module body. -So a module's interface consists of a collection of named bindings, which other modules that depend on them have access to. Imported bindings can be renamed to give them a new local name using `as` after their name. +A module's interface thus consists of a collection of named bindings, which other modules that depend on the module can access. Imported bindings can be renamed to give them a new local name using `as` after their name. ``` import {dayName as nomDeJour} from "./dayname.js"; @@ -99,7 +99,7 @@ console.log(nomDeJour(3)); // → Wednesday ``` -It is also possible for a module to have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. +A module may also have a special export named `default`, which is often used for modules that only export a single binding. To define a default export, you write `export default` before an expression, a function declaration, or a class declaration. ``` export default ["Winter", "Spring", "Summer", "Autumn"]; @@ -111,11 +111,19 @@ Such a binding is imported by omitting the braces around the name of the import. import seasonNames from "./seasonname.js"; ``` +To import all bindings from a module at the same time, you can use `import *`. You provide a name, and that name will be bound to an object holding all the module's exports. This can be useful when you are using a lot of different exports. + +``` +import * as dayName from "./dayname.js"; +console.log(dayName.dayName(3)); +// → Wednesday +``` + ## Packages {{index bug, dependency, structure, reuse}} -One of the advantages of building a program out of separate pieces, and being able to run some of those pieces on their own, is that you might be able to apply the same piece in different programs. +One of the advantages of building a program out of separate pieces and being able to run some of those pieces on their own is that you might be able to use the same piece in different programs. {{index "parseINI function"}} @@ -123,9 +131,7 @@ But how do you set this up? Say I want to use the `parseINI` function from [Chap {{index duplication, "copy-paste programming"}} -Once you start duplicating code, you'll quickly find yourself wasting time and energy moving copies around and keeping them up-to-date. - -That's where _((package))s_ come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn't write it might still be able to use it. +Once you start duplicating code, you'll quickly find yourself wasting time and energy moving copies around and keeping them up to date. That's where _((package))s_ come in. A package is a chunk of code that can be distributed (copied and installed). It may contain one or more modules and has information about which other packages it depends on. A package also usually comes with documentation explaining what it does so that people who didn't write it might still be able to use it. When a problem is found in a package or a new feature is added, the package is updated. Now the programs that depend on it (which may also be packages) can copy the new ((version)) to get the improvements that were made to the code. @@ -133,9 +139,9 @@ When a problem is found in a package or a new feature is added, the package is u {{index installation, upgrading, "package manager", download, reuse}} -Working in this way requires ((infrastructure)). We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by ((NPM)) ([_https://npmjs.org_](https://npmjs.org)). +Working in this way requires ((infrastructure)). We need a place to store and find packages and a convenient way to install and upgrade them. In the JavaScript world, this infrastructure is provided by ((NPM)) ([_https://npmjs.com_](https://npmjs.com)). -NPM is two things: an online service where you can download (and upload) packages and a program (bundled with Node.js) that helps you install and manage them. +NPM is two things: an online service where you can download (and upload) packages, and a program (bundled with Node.js) that helps you install and manage them. {{index "ini package"}} @@ -149,11 +155,11 @@ Having quality packages available for download is extremely valuable. It means t {{index maintenance}} -Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. But writing it in the first place _is_ work, and responding to people who have found problems in the code, or who want to propose new features, is even more work. +Software is cheap to copy, so once someone has written it, distributing it to other people is an efficient process. Writing it in the first place _is_ work, though, and responding to people who have found problems in the code or who want to propose new features is even more work. By default, you own the ((copyright)) to the code you write, and other people may use it only with your permission. But because some people are just nice and because publishing good software can help make you a little bit famous among programmers, many packages are published under a ((license)) that explicitly allows other people to use it. -Most code on ((NPM)) is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, just requiring that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people's packages, make sure you are aware of their license. +Most code on ((NPM)) is licensed this way. Some licenses require you to also publish code that you build on top of the package under the same license. Others are less demanding, requiring only that you keep the license with the code as you distribute it. The JavaScript community mostly uses the latter type of license. When using other people's packages, make sure you are aware of their licenses. {{id modules_ini}} @@ -172,14 +178,14 @@ console.log(parse("x = 10\ny = 20")); ## CommonJS modules -Before 2015, when the JavaScript language had no actual built-in module system, people were already building large systems in JavaScript. To make that workable, they _needed_ ((module))s. +Before 2015, when the JavaScript language had no built-in module system, people were already building large systems in JavaScript. To make that workable, they _needed_ ((module))s. {{index [function, scope], [interface, module], [object, as module]}} The community designed its own improvised ((module system))s on top of the language. These use functions to create a local scope for the modules and regular objects to represent module interfaces. Initially, people just manually wrapped their entire module in an “((immediately invoked function -expression))” to create the module's scope, and assigned their interface objects to a single global +expression))” to create the module's scope and assigned their interface objects to a single global variable. ``` @@ -202,7 +208,7 @@ This style of modules provides ((isolation)), to a certain degree, but it does n {{index "CommonJS modules"}} -If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used it from the start (though it now also knows how to load ES modules) it and is the module system used by many packages on ((NPM)). +If we implement our own module loader, we can do better. The most widely used approach to bolted-on JavaScript modules is called _CommonJS modules_. ((Node.js)) used this module system from the start (though it now also knows how to load ES modules), and it is the module system used by many packages on ((NPM)). {{index "require function", [interface, module], "exports object"}} @@ -212,7 +218,7 @@ A CommonJS module looks like a regular script, but it has access to two bindings This CommonJS example module provides a date-formatting function. It uses two ((package))s from NPM—`ordinal` to convert numbers to strings like `"1st"` and `"2nd"`, and `date-names` to get the English names for weekdays and months. It exports a single function, `formatDate`, which takes a `Date` object and a ((template)) string. -The template string may contain codes that direct the format, such as `YYYY` for the full year and `Do` for the ordinal day of the month. You could give it a string like `"MMMM Do YYYY"` to get output like "November 22nd 2017". +The template string may contain codes that direct the format, such as `YYYY` for the full year and `Do` for the ordinal day of the month. You could give it a string like `"MMMM Do YYYY"` to get output like `November 22nd 2017`. ``` const ordinal = require("ordinal"); @@ -244,7 +250,7 @@ console.log(formatDate(new Date(2017, 9, 13), // → Friday the 13th ``` -CommonJS is implemented with a module loader that, when loading a module, wraps its code in a function (giving it its own local scope), and passes the `require` and `exports` bindings to that function as arguments. +CommonJS is implemented with a module loader that, when loading a module, wraps its code in a function (giving it its own local scope) and passes the `require` and `exports` bindings to that function as arguments. {{id require}} @@ -273,29 +279,27 @@ require.cache = Object.create(null); {{index [file, access]}} -Standard JavaScript provides no such function as `readFile`—but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that `readFile` exists. +Standard JavaScript provides no such function as `readFile`, but different JavaScript environments, such as the browser and Node.js, provide their own ways of accessing files. The example just pretends that `readFile` exists. -To avoid loading the same module multiple times, `require` keeps a store (cache) of already loaded modules. When called, it first checks if the requested module has been loaded and, if not, loads it. This involves reading the module's code, wrapping it in a function, and calling it. +To avoid loading the same module multiple times, `require` keeps a store (cache) of already loaded modules. When called, it first checks whether the requested module has been loaded and, if not, loads it. This involves reading the module's code, wrapping it in a function, and calling it. {{index "ordinal package", "exports object", "module object", [interface, module]}} -By defining `require`, `exports` as ((parameter))s for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module's ((scope)). +By defining `require` and `exports` as ((parameter))s for the generated wrapper function (and passing the appropriate values when calling it), the loader makes sure that these bindings are available in the module's ((scope)). -An important difference between this system and ES modules is that ES module imports happen before a module's script starts running, whereas `require` is a normal function, invoked when the module is already running. Unlike `import` declarations, `require` calls _can_ appear inside functions, and the name of the dependency can be any expression that evaluates to a string, whereas `import` only allows plain quoted strings. +An important difference between this system and ES modules is that ES module imports happen before a module's script starts running, whereas `require` is a normal function, invoked when the module is already running. Unlike `import` declarations, `require` calls _can_ appear inside functions, and the name of the dependency can be any expression that evaluates to a string, whereas `import` allows only plain quoted strings. -The transition of the JavaScript community from CommonJS style to ES modules has been a slow and somewhat rough one. But fortunately we are now at a point where most of the popular packages on NPM provide their code as ES modules, and Node.js allows ES modules to import from CommonJS modules. So while CommonJS code is still something you will run across, there is no real reason to write new programs in this style anymore. +The transition of the JavaScript community from CommonJS style to ES modules has been a slow and somewhat rough one. Fortunately we are now at a point where most of the popular packages on NPM provide their code as ES modules, and Node.js allows ES modules to import from CommonJS modules. While CommonJS code is still something you will run across, there is no real reason to write new programs in this style anymore. ## Building and bundling {{index compilation, "type checking"}} -Many JavaScript packages aren't, technically, written in JavaScript. There are extensions, such as TypeScript, the type checking ((dialect)) mentioned in [Chapter ?](error#typing), that are widely used. People also often start using planned extensions to the language long before they have been added to the platforms that actually run JavaScript. - -To make this possible, they _compile_ their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that ((browsers)) can run it. +Many JavaScript packages aren't technically written in JavaScript. Language extensions such as TypeScript, the type checking ((dialect)) mentioned in [Chapter ?](error#typing), are widely used. People also often start using planned new language features long before they have been added to the platforms that actually run JavaScript. To make this possible, they _compile_ their code, translating it from their chosen JavaScript dialect to plain old JavaScript—or even to a past version of JavaScript—so that ((browsers)) can run it. {{index latency, performance, [file, access], [network, speed]}} -Including a modular program that consists of 200 different files in a ((web page)) produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That's a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that combine their programs (which they painstakingly split into modules) into a single big file before they publish it to the Web. Such tools are called _((bundler))s_. +Including a modular program that consists of 200 different files in a ((web page)) produces its own problems. If fetching a single file over the network takes 50 milliseconds, loading the whole program takes 10 seconds, or maybe half that if you can load several files simultaneously. That's a lot of wasted time. Because fetching a single big file tends to be faster than fetching a lot of tiny ones, web programmers have started using tools that combine their programs (which they painstakingly split into modules) into a single big file before they publish it to the web. Such tools are called _((bundler))s_. {{index "file size"}} @@ -303,7 +307,7 @@ And we can go further. Apart from the number of files, the _size_ of the files a {{index pipeline, tool}} -So it is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through _multiple_ stages of transformation—converted from modern JavaScript to historic JavaScript, then combining the modules into a single file, and minifying the code. We won't go into the details of these tools in this book since there are many of them, and which one is popular changes regularly. Just be aware that such things exist, and look them up when you need them. +It is not uncommon for the code that you find in an NPM package or that runs on a web page to have gone through _multiple_ stages of transformation—converting from modern JavaScript to historic JavaScript, combining the modules into a single file, and minifying the code. We won't go into the details of these tools in this book, since there are many of them, and which one is popular changes regularly. Just be aware that such things exist, and look them up when you need them. ## Module design @@ -319,11 +323,11 @@ One aspect of module design is ease of use. If you are designing something that {{index "ini package", JSON}} -That may mean following existing conventions. A good example is the `ini` package. This module imitates the standard `JSON` object by providing `parse` and `stringify` (to write an INI file) functions, and, like `JSON`, converts between strings and plain objects. So the interface is small and familiar, and after you've worked with it once, you're likely to remember how to use it. +That may mean following existing conventions. A good example is the `ini` package. This module imitates the standard `JSON` object by providing `parse` and `stringify` (to write an INI file) functions, and, like `JSON`, converts between strings and plain objects. The interface is small and familiar, and after you've worked with it once, you're likely to remember how to use it. {{index "side effect", "hard disk", composability}} -Even if there's no standard function or widely used package to imitate, you can keep your modules predictable by using simple ((data structure))s and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don't have direct file system access, and adds complexity that would have been better addressed by _composing_ the module with some file-reading function. +Even if there's no standard function or widely used package to imitate, you can keep your modules predictable by using simple ((data structure))s and doing a single, focused thing. Many of the INI-file parsing modules on NPM provide a function that directly reads such a file from the hard disk and parses it, for example. This makes it impossible to use such modules in the browser, where we don't have direct filesystem access, and adds complexity that would have been better addressed by _composing_ the module with some file-reading function. {{index "pure function"}} @@ -333,7 +337,7 @@ This points to another helpful aspect of module design—the ease with which som Relatedly, stateful objects are sometimes useful or even necessary, but if something can be done with a function, use a function. Several of the INI file readers on NPM provide an interface style that requires you to first create an object, then load the file into your object, and finally use specialized methods to get at the results. This type of thing is common in the object-oriented tradition, and it's terrible. Instead of making a single function call and moving on, you have to perform the ritual of moving your object through its various states. And because the data is now wrapped in a specialized object type, all code that interacts with it has to know about that type, creating unnecessary interdependencies. -Often defining new data structures can't be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array. +Often, defining new data structures can't be avoided—only a few basic ones are provided by the language standard, and many types of data have to be more complex than an array or a map. But when an array suffices, use an array. An example of a slightly more complex data structure is the graph from [Chapter ?](robot). There is no single obvious way to represent a ((graph)) in JavaScript. In that chapter, we used an object whose properties hold arrays of strings—the other nodes reachable from that node. @@ -343,7 +347,7 @@ There are several different pathfinding packages on ((NPM)), but none of them us For example, there's the `dijkstrajs` package. A well-known approach to pathfinding, quite similar to our `findRoute` function, is called _Dijkstra's algorithm_, after Edsger Dijkstra, who first wrote it down. The `js` suffix is often added to package names to indicate the fact that they are written in JavaScript. This `dijkstrajs` package uses a graph format similar to ours, but instead of arrays, it uses objects whose property values are numbers—the weights of the edges. -So if we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight since our simplified model treats each road as having the same cost (one turn). +If we wanted to use that package, we'd have to make sure that our graph was stored in the format it expects. All edges get the same weight, since our simplified model treats each road as having the same cost (one turn). ``` const {find_path} = require("dijkstrajs"); @@ -364,11 +368,11 @@ This can be a barrier to composition—when various packages are using different {{index design}} -Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much since keeping everything organized can be a big distraction. Once you have something that feels solid, that's a good time to take a step back and organize it. +Designing a fitting module structure for a program can be difficult. In the phase where you are still exploring the problem, trying different things to see what works, you might want to not worry about it too much, since keeping everything organized can be a big distraction. Once you have something that feels solid, that's a good time to take a step back and organize it. ## Summary -Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that's visible to other modules, and the dependencies are the other modules that it makes use of. +Modules provide structure to bigger programs by separating the code into pieces with clear interfaces and dependencies. The interface is the part of the module that's visible to other modules, and the dependencies are the other modules it makes use of. Because JavaScript historically did not provide a module system, the CommonJS system was built on top of it. Then at some point it _did_ get a built-in system, which now coexists uneasily with the CommonJS system. @@ -410,15 +414,15 @@ Here's what I would have done (but again, there is no single _right_ way to desi {{index "dijkstrajs package"}} -The code used to build the road graph lives in the `graph` module. Because I'd rather use `dijkstrajs` from NPM than our own pathfinding code, we'll make this build the kind of graph data that `dijkstrajs` expects. This module exports a single function, `buildGraph`. I'd have `buildGraph` accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format. +The code used to build the road graph lives in the `graph.js` module. Because I'd rather use `dijkstrajs` from NPM than our own pathfinding code, we'll make this build the kind of graph data that `dijkstrajs` expects. This module exports a single function, `buildGraph`. I'd have `buildGraph` accept an array of two-element arrays, rather than strings containing hyphens, to make the module less dependent on the input format. -The `roads` module contains the raw road data (the `roads` array) and the `roadGraph` binding. This module depends on `./graph.js` and exports the road graph. +The `roads.js` module contains the raw road data (the `roads` array) and the `roadGraph` binding. This module depends on `./graph.js` and exports the road graph. {{index "random-item package"}} -The `VillageState` class lives in the `state` module. It depends on the `./roads` module because it needs to be able to verify that a given road exists. It also needs `randomPick`. Since that is a three-line function, we could just put it into the `state` module as an internal helper function. But `randomRobot` needs it too. So we'd have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the `random-item` package, a reasonable solution is to just make both modules depend on that. We can add the `runRobot` function to this module as well, since it's small and closely related to state management. The module exports both the `VillageState` class and the `runRobot` function. +The `VillageState` class lives in the `state.js` module. It depends on the `./roads.js` module because it needs to be able to verify that a given road exists. It also needs `randomPick`. Since that is a three-line function, we could just put it into the `state.js` module as an internal helper function. But `randomRobot` needs it too. So we'd have to either duplicate it or put it into its own module. Since this function happens to exist on NPM in the `random-item` package, a reasonable solution is to just make both modules depend on that. We can add the `runRobot` function to this module as well, since it's small and closely related to state management. The module exports both the `VillageState` class and the `runRobot` function. -Finally, the robots, along with the values they depend on such as `mailRoute`, could go into an `example-robots` module, which depends on `./roads` and exports the robot functions. To make it possible for `goalOrientedRobot` to do route-finding, this module also depends on `dijkstrajs`. +Finally, the robots, along with the values they depend on, such as `mailRoute`, could go into an `example-robots.js` module, which depends on `./roads.js` and exports the robot functions. To make it possible for `goalOrientedRobot` to do route-finding, this module also depends on `dijkstrajs`. By offloading some work to ((NPM)) modules, the code became a little smaller. Each individual module does something rather simple and can be read on its own. Dividing code into modules also often suggests further improvements to the program's design. In this case, it seems a little odd that the `VillageState` and the robots depend on a specific road graph. It might be a better idea to make the graph an argument to the state's constructor and make the robots read it from the state object—this reduces dependencies (which is always good) and makes it possible to run simulations on different maps (which is even better). @@ -434,7 +438,7 @@ hint}} {{index "roads module (exercise)"}} -Write an ES module, based on the example from [Chapter ?](robot), that contains the array of roads and exports the graph data structure representing them as `roadGraph`. It should depend on a module `./graph.js`, which exports a function `buildGraph` that is used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads). +Write an ES module based on the example from [Chapter ?](robot) that contains the array of roads and exports the graph data structure representing them as `roadGraph`. It depends on a module `./graph.js` that exports a function `buildGraph`, used to build the graph. This function expects an array of two-element arrays (the start and end points of the roads). {{if interactive @@ -478,6 +482,6 @@ The `require` function given [earlier in this chapter](modules#require) supports {{index overriding, "circular dependency", "exports object"}} -The trick is that `require` adds the interface object for a module to its cache _before_ it starts loading the module. That way, if any `require` call made while it is running try to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack). +The trick is that `require` adds the interface object for a module to its cache _before_ it starts loading the module. That way, if any `require` call made while it is running tries to load it, it is already known, and the current interface will be returned, rather than starting to load the module once more (which would eventually overflow the stack). hint}} diff --git a/11_async.md b/11_async.md index b4da70d6..ee30fd7f 100644 --- a/11_async.md +++ b/11_async.md @@ -31,7 +31,7 @@ In a _synchronous_ programming model, things happen one at a time. When you call An _asynchronous_ model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from disk). -We can compare synchronous and asynchronous programming using a small example: a program that makes two requests over the ((network)) and then combines the result. +We can compare synchronous and asynchronous programming using a small example: a program that makes two requests over the ((network)) and then combines the results. {{index "synchronous programming"}} @@ -49,7 +49,7 @@ In the following diagram, the thick lines represent time the program spends runn {{index ["control flow", asynchronous], "asynchronous programming", verbosity, performance}} -Another way to describe the difference is that waiting for actions to finish is _implicit_ in the synchronous model, while it is _explicit_, under our control, in the asynchronous one. +Another way to describe the difference is that waiting for actions to finish is _implicit_ in the synchronous model, while it is _explicit_—under our control—in the asynchronous one. Asynchronicity cuts both ways. It makes expressing programs that do not fit the straight-line model of control easier, but it can also make expressing programs that do follow a straight line more awkward. We'll see some ways to reduce this awkwardness later in the chapter. @@ -59,21 +59,21 @@ Both prominent JavaScript programming platforms—((browser))s and ((Node.js)) {{indexsee [function, callback], "callback function"}} -One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. The asynchronous a function starts some process, sets things up so that the callback function is called when the process finishes, and then returns. +One approach to ((asynchronous programming)) is to make functions that need to wait for something take an extra argument, a _((callback function))_. The asynchronous function starts a process, sets things up so that the callback function is called when the process finishes, and then returns. {{index "setTimeout function", waiting}} -As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function. +As an example, the `setTimeout` function, available both in Node.js and in browsers, waits a given number of milliseconds and then calls a function. ```{test: no} setTimeout(() => console.log("Tick"), 500); ``` -Waiting is not generally a very important type of work, but it can be very useful when you need to arrange for something to happen at a certain time or check whether some other action is taking longer than expected. +Waiting is not generally important work, but it can be very useful when you need to arrange for something to happen at a certain time or check whether some action is taking longer than expected. {{index "readTextFile function"}} -Another example of a common asynchronous operation is reading a file from a device's storage. Imagine you have a function `readTextFile`, which reads a file's content as a string and passes it to a callback function. +Another example of a common asynchronous operation is reading a file from a device's storage. Imagine you have a function `readTextFile` that reads a file's content as a string and passes it to a callback function. ``` readTextFile("shopping_list.txt", content => { @@ -86,7 +86,7 @@ readTextFile("shopping_list.txt", content => { The `readTextFile` function is not part of standard JavaScript. We will see how to read files in the browser and in Node.js in later chapters. -Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the ((continuation)) of the computation after the actions. This is what an asynchronous function that compares two files and produces a boolean indicating whether their content is the same might look like. +Performing multiple asynchronous actions in a row using callbacks means that you have to keep passing new functions to handle the ((continuation)) of the computation after the actions. An asynchronous function that compares two files and produces a boolean indicating whether their content is the same might look like this: ``` function compareFiles(fileA, fileB, callback) { @@ -100,19 +100,19 @@ function compareFiles(fileA, fileB, callback) { This style of programming is workable, but the indentation level increases with each asynchronous action because you end up in another function. Doing more complicated things, such as wrapping asynchronous actions in a loop, can get awkward. -In a way, asynchronicity is _contagious_. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error-prone than simply returning a value, so needing to structure large parts of your program that way is not great. +In a way, asynchronicity is _contagious_. Any function that calls a function that works asynchronously must itself be asynchronous, using a callback or similar mechanism to deliver its result. Calling a callback is somewhat more involved and error prone than simply returning a value, so needing to structure large parts of your program that way is not great. ## Promises -A slightly different way to build an asynchronous program is to have asynchronous functions return an object that represents its (future) result instead of passing around callback functions. This way such functions actually return something meaningful, and the shape of the program more closely resembles that of synchronous programs. +A slightly different way to build an asynchronous program is to have asynchronous functions return an object that represents its (future) result instead of passing around callback functions. This way, such functions actually return something meaningful, and the shape of the program more closely resembles that of synchronous programs. {{index "Promise class", "asynchronous programming", "resolving (a promise)", "then method", "callback function"}} -This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provides a `then` method that allows you to register a function that should be called when the action it is waiting for finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible to call `then` on a promise that has already resolved—your function will still be called. +This is what the standard class `Promise` is for. A _promise_ is a receipt representing a value that may not be available yet. It provides a `then` method that allows you to register a function that should be called when the action for which it is waiting finishes. When the promise is _resolved_, meaning its value becomes available, such functions (there can be multiple) are called with the result value. It is possible to call `then` on a promise that has already resolved—your function will still be called. {{index "Promise.resolve function"}} -The easiest way to create a promise is by calling `Promise.resolve`. This function ensures that the value you give it is wrapped in a promise. If it's already a promise, it is simply returned—otherwise, you get a new promise that immediately resolves with your value as its result. +The easiest way to create a promise is by calling `Promise.resolve`. This function ensures that the value you give it is wrapped in a promise. If it's already a promise, it is simply returned. Otherwise, you get a new promise that immediately resolves with your value as its result. ``` let fifteen = Promise.resolve(15); @@ -122,9 +122,9 @@ fifteen.then(value => console.log(`Got ${value}`)); {{index "Promise class"}} -To create a promise that does not immediately resolve, you can use `Promise` as a constructor. It has a somewhat odd interface—the constructor expects a function as argument, which it immediately calls, passing it a function that it can use to resolve the promise. +To create a promise that does not immediately resolve, you can use `Promise` as a constructor. It has a somewhat odd interface: the constructor expects a function as its argument, which it immediately calls, passing it a function that it can use to resolve the promise. -This is how you could create a promise-based interface for the `readTextFile` function: +For example, this is how you could create a promise-based interface for the `readTextFile` function: {{index "textFile function"}} @@ -138,13 +138,13 @@ function textFile(filename) { textFile("plans.txt").then(console.log); ``` -Note how this asynchronous function returns a meaningful value—a promise to give you the content of the file at some point in the future. +Note how, in contrast to callback-style functions, this asynchronous function returns a meaningful value—a promise to give you the contents of the file at some point in the future. {{index "then method"}} -A useful thing about the `then` method is that it itself returns another promise that resolves to the value returned by the callback function or, if that function returns a promise, to the value that promise resolves to. Thus, you can “chain” multiple calls to `then` together to set up a sequence of asynchronous actions. +A useful thing about the `then` method is that it itself returns another promise. This one resolves to the value returned by the callback function or, if that returned value is a promise, to the value that promise resolves to. Thus, you can “chain” multiple calls to `then` together to set up a sequence of asynchronous actions. -This function, which reads a file full of filenames, and returns the content of a random file in that list, shows this kind of asynchronous promise pipeline. +This function, which reads a file full of filenames and returns the content of a random file in that list, shows this kind of asynchronous promise pipeline: ``` function randomFile(listFile) { @@ -155,11 +155,11 @@ function randomFile(listFile) { } ``` -The function returns the result of this chain of `then` calls. The initial promise fetches the list of files as a string. The first `then` call transforms that string into an array of lines, producing a new promise. The second `then` call picks a random line from that, producing a third promise that yields a single filename. The final `then` call reads this file, so that the result of the function as a whole is a promise that returns the content of a random file. +The function returns the result of this chain of `then` calls. The initial promise fetches the list of files as a string. The first `then` call transforms that string into an array of lines, producing a new promise. The second `then` call picks a random line from that, producing a third promise that yields a single filename. The final `then` call reads this file, so the result of the function as a whole is a promise that returns the content of a random file. -In this code, the functions used in the first two `then` calls return a regular value, which will immediately be passed into the promise returned by `then` when the function returns. The last one returns a promise (`textFile(filename)`), making it an actual asynchronous step. +In this code, the functions used in the first two `then` calls return a regular value that will immediately be passed into the promise returned by `then` when the function returns. The last `then` call returns a promise (`textFile(filename)`), making it an actual asynchronous step. -It would have also been possible to do all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, for example when you want to return a promise that produces a processed version of some asynchronous result. +It would also have been possible to perform all these steps inside a single `then` callback, since only the last step is actually asynchronous. But the kind of `then` wrappers that only do some synchronous data transformation are often useful, such as when you want to return a promise that produces a processed version of some asynchronous result. ``` function jsonFile(filename) { @@ -169,7 +169,7 @@ function jsonFile(filename) { jsonFile("package.json").then(console.log); ``` -Generally, it is useful to think of promises as a device that lets code ignore the question of when a value is going to arrive. A normal value has to actually exist before we can reference it. A promised value is a value that _might_ already be there or might appear at some point in the future. Computations defined in terms of promises, by wiring them together with `then` calls, are executed asynchronously as their inputs become available. +Generally, it is useful to think of a promise as a device that lets code ignore the question of when a value is going to arrive. A normal value has to actually exist before we can reference it. A promised value is a value that _might_ already be there or might appear at some point in the future. Computations defined in terms of promises, by wiring them together with `then` calls, are executed asynchronously as their inputs become available. ## Failure @@ -179,9 +179,9 @@ Regular JavaScript computations can fail by throwing an exception. Asynchronous {{index "callback function", error}} -One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to make sure failures are properly reported to the callbacks. +One of the most pressing problems with the callback style of asynchronous programming is that it makes it extremely difficult to ensure failures are properly reported to the callbacks. -A widely used convention is that the first argument to the callback is used to indicate that the action failed, and the second contains the value produced by the action when it was successful. +A common convention is to use the first argument to the callback to indicate that the action failed, and the second to pass the value produced by the action when it was successful. ``` someAsyncFunction((error, value) => { @@ -194,11 +194,11 @@ Such callback functions must always check whether they received an exception and {{index "rejecting (a promise)", "resolving (a promise)", "then method"}} -Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with `then`) are called only when the action is successful, and rejections are propagated to the new promise that is returned by `then`. When a handler throws an exception, this automatically causes the promise produced by its `then` call to be rejected. So if any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed. +Promises make this easier. They can be either resolved (the action finished successfully) or rejected (it failed). Resolve handlers (as registered with `then`) are called only when the action is successful, and rejections are propagated to the new promise returned by `then`. When a handler throws an exception, this automatically causes the promise produced by its `then` call to be rejected. If any element in a chain of asynchronous actions fails, the outcome of the whole chain is marked as rejected, and no success handlers are called beyond the point where it failed. {{index "Promise.reject function", "Promise class"}} -Much like resolving a promise provides a value, rejecting one also provides one, usually called the _reason_ of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There's a `Promise.reject` function that creates a new, immediately rejected promise. +Much like resolving a promise provides a value, rejecting one also provides a value, usually called the _reason_ of the rejection. When an exception in a handler function causes the rejection, the exception value is used as the reason. Similarly, when a handler returns a promise that is rejected, that rejection flows into the next promise. There's a `Promise.reject` function that creates a new, immediately rejected promise. {{index "catch method"}} @@ -206,13 +206,13 @@ To explicitly handle such rejections, promises have a `catch` method that regist {{index "then method"}} -As a shorthand, `then` also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call. +As a shorthand, `then` also accepts a rejection handler as a second argument, so you can install both types of handlers in a single method call: `.then(acceptHandler, rejectHandler)`. A function passed to the `Promise` constructor receives a second argument, alongside the resolve function, which it can use to reject the new promise. {{index "textFile function"}} -When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually look at that argument, so that a failure causes the promise it returns to be rejected. +When our `readTextFile` function encounters a problem, it passes the error to its callback function as a second argument. Our `textFile` wrapper should actually check that argument so that a failure causes the promise it returns to be rejected. ```{includeCode: true} function textFile(filename) { @@ -225,7 +225,7 @@ function textFile(filename) { } ``` -The chains of promise values created by calls to `then` and `catch` thus form a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don't match the type of outcome (success or failure) are ignored. But those that do match are called, and their outcome determines what kind of value comes next—success when it returns a non-promise value, rejection when it throws an exception, and the outcome of the promise when it returns a promise. +The chains of promise values created by calls to `then` and `catch` thus form a pipeline through which asynchronous values or failures move. Since such chains are created by registering handlers, each link has a success handler or a rejection handler (or both) associated with it. Handlers that don't match the type of outcome (success or failure) are ignored. Handlers that do match are called, and their outcome determines what kind of value comes next—success when they return a non-promise value, rejection when they throw an exception, and the outcome of the promise when they return a promise. ```{test: no} new Promise((_, reject) => reject(new Error("Fail"))) @@ -239,7 +239,7 @@ new Promise((_, reject) => reject(new Error("Fail"))) // → Handler 2: nothing ``` -The first regular handler function isn't called, because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second handler function. +The first `then` handler function isn't called because at that point of the pipeline the promise holds a rejection. The `catch` handler handles that rejection and returns a value, which is given to the second `then` handler function. {{index "uncaught exception", "exception handling"}} @@ -249,29 +249,29 @@ Much like an uncaught exception is handled by the environment, JavaScript enviro {{index "Carla the crow"}} -It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. +It's a sunny day in Berlin. The runway of the old, decommissioned airport is teeming with cyclists and inline skaters. In the grass near a garbage container, a flock of crows noisily mills about, trying to convince a group of tourists to part with their sandwiches. -One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting the people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. +One of the crows stands out—a large scruffy female with a few white feathers in her right wing. She is baiting people with a skill and confidence that suggest she's been doing this for a long time. When an elderly man is distracted by the antics of another crow, she casually swoops in, snatches his half-eaten bun from his hand, and sails away. -Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight towards the roof of the hangar building, disappearing into an air vent. +Contrary to the rest of the group, who look like they are happy to spend the day goofing around here, the large crow looks purposeful. Carrying her loot, she flies straight toward the roof of the hangar building, disappearing into an air vent. -Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snacks, half a dozen smart phones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. +Inside the building, you can hear an odd tapping sound—soft, but persistent. It comes from a narrow space under the roof of an unfinished stairwell. The crow is sitting there, surrounded by her stolen snacks, half a dozen smartphones (several of which are turned on), and a mess of cables. She rapidly taps the screen of one of the phones with her beak. Words are appearing on it. If you didn't know better, you'd think she was typing. This crow is known to her peers as “cāāw-krö”. But since those sounds are poorly suited for human vocal chords, we'll refer to her as Carla. -Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she had a good grasp of what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of JavaScript code. +Carla is a somewhat peculiar crow. In her youth, she was fascinated by human language, eavesdropping on people until she had a good grasp of what they were saying. Later in life, her interest shifted to human technology, and she started stealing phones to study them. Her current project is learning to program. The text she is typing in her hidden lab is, in fact, a piece of asynchronous JavaScript code. ## Breaking In {{index "Carla the crow"}} -Carla loves the Internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. +Carla loves the internet. Annoyingly, the phone she is working on is about to run out of prepaid data. The building has a wireless network, but it requires a code to access. -Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device has to send along the correct 6-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending only a partial code (say, only 3 digits), the response is different based on whether those digits are the correct start of the code or not—when sending incorrect number, you immediately get a failure message. When sending the correct ones, the access point waits for more digits. +Fortunately, the wireless routers in the building are 20 years old and poorly secured. Doing some research, Carla finds out that the network authentication mechanism has a flaw she can use. When joining the network, a device must send along the correct six-digit passcode. The access point will reply with a success or failure message depending on whether the right code is provided. However, when sending a partial code (say, only three digits), the response is different based on whether those digits are the correct start of the code or not. Sending incorrect numbers immediately returns a failure message. When sending the correct ones, the access point waits for more digits. -This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find second digit in the same way, and so on, until she knows the entire passcode. +This makes it possible to greatly speed up the guessing of the number. Carla can find the first digit by trying each number in turn, until she finds one that doesn't immediately return failure. Having one digit, she can find the second digit in the same way, and so on, until she knows the entire passcode. -Assume we have a `joinWifi` function. Given the network name and the passcode (as a string), it tries to join the network, returning a promise that resolves if successful, and rejects if the authentication failed. The first thing we need is to a way to wrap a promise so that it automatically rejects after it takes too much time, so that we can quickly move on if the access point doesn't respond. +Assume Carla has a `joinWifi` function. Given the network name and the passcode (as a string), the function tries to join the network, returning a promise that resolves if successful and rejects if the authentication failed. The first thing she needs is a way to wrap a promise so that it automatically rejects after it takes too much time, to allow the program to quickly move on if the access point doesn't respond. ```{includeCode: true} function withTimeout(promise, time) { @@ -282,11 +282,11 @@ function withTimeout(promise, time) { } ``` -This makes use of the fact that a promise can only be resolved or rejected once—if the promise given as argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored. +This makes use of the fact that a promise can be resolved or rejected only once. If the promise given as its argument resolves or rejects first, that result will be the result of the promise returned by `withTimeout`. If, on the other hand, the `setTimeout` fires first, rejecting the promise, any further resolve or reject calls are ignored. -To find the whole passcode, we need to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong, and must try the next digit. If the request times out, we have found another correct digit, and must continue by adding another digit. +To find the whole passcode, the program needs to repeatedly look for the next digit by trying each digit. If authentication succeeds, we know we have found what we are looking for. If it immediately fails, we know that digit was wrong and must try the next digit. If the request times out, we have found another correct digit and must continue by adding another digit. -Because you cannot wait for a promise inside a `for` loop, Carla uses a recursive function to drive this process. On each call, it gets the code as we know it so far, as well as the next digit to try. Depending on what happens, it may return a finished code, or call through to itself, to either start cracking the next position in the code, or to try again with another digit. +Because you cannot wait for a promise inside a `for` loop, Carla uses a recursive function to drive this process. On each call, this function gets the code as we know it so far, as well as the next digit to try. Depending on what happens, it may return a finished code or call through to itself, to either start cracking the next position in the code or to try again with another digit. ```{includeCode: true} function crackPasscode(networkID) { @@ -321,7 +321,7 @@ Carla tilts her head and sighs. This would have been more satisfying if the code {{index "Promise class", recursion}} -Even with promises, this kind of asynchronous code is annoying to write. Promises often need to be tied together in verbose, arbitrary-looking ways. And we were forced to introduce a recursive function to just to create a loop. +Even with promises, this kind of asynchronous code is annoying to write. Promises often need to be tied together in verbose, arbitrary-looking ways. To create an asynchronous loop, Carla was forced to introduce a recursive function. {{index "synchronous programming", "asynchronous programming"}} @@ -329,7 +329,7 @@ The thing the cracking function actually does is completely linear—it always w {{index "async function", "await keyword"}} -The good news is that JavaScript allows you to write pseudo-synchronous code to describe asynchronous computation. An `async` function is a function that implicitly returns a promise and that can, in its body, `await` other promises in a way that _looks_ synchronous. +The good news is that JavaScript allows you to write pseudosynchronous code to describe asynchronous computation. An `async` function implicitly returns a promise and can, in its body, `await` other promises in a way that _looks_ synchronous. {{index "findInStorage function"}} @@ -356,7 +356,7 @@ async function crackPasscode(networkID) { } ``` -This version more clearly shows the double loop structure of the function (the inner loop tries digit 0 to 9, the outer loop adds digits to the passcode). +This version more clearly shows the double loop structure of the function (the inner loop tries digit 0 to 9 and the outer loop adds digits to the passcode). {{index "async function", "return keyword", "exception handling"}} @@ -366,9 +366,9 @@ An `async` function is marked by the word `async` before the `function` keyword. Inside an `async` function, the word `await` can be put in front of an expression to wait for a promise to resolve and only then continue the execution of the function. If the promise rejects, an exception is raised at the point of the `await`. -Such a function no longer, like a regular JavaScript function, runs from start to completion in one go. Instead, it can be _frozen_ at any point that has an `await`, and can be resumed at a later time. +Such a function no longer runs from start to completion in one go like a regular JavaScript function. Instead, it can be _frozen_ at any point that has an `await` and can be resumed at a later time. -For most asynchronous code, this notation is more convenient than directly using promises. You do still need an understanding of promises, since in many cases you still interact with them directly. But when wiring them together, `async` functions are generally more pleasant to write than chains of `then` calls. +For most asynchronous code, this notation is more convenient than directly using promises. You do still need an understanding of promises, since in many cases you'll still interact with them directly. But when wiring them together, `async` functions are generally more pleasant to write than chains of `then` calls. {{id generator}} @@ -433,17 +433,17 @@ An `async` function is a special type of generator. It produces a promise when c {{index "Carla the crow"}} -This morning, Carla woke up to unfamiliar noise from the tarmac outside of her hangar. Hopping onto the edge of the roof, she sees the humans are setting up for something. There's a lot of electric cabling, a stage, and some kind of big black wall being built up. +One morning, Carla wakes up to unfamiliar noise from the tarmac outside of her hangar. Hopping onto the edge of the roof, she sees the humans are setting up for something. There's a lot of electric cabling, a stage, and some kind of big black wall being built up. Being a curious crow, Carla takes a closer look at the wall. It appears to consist of a number of large glass-fronted devices wired up to cables. On the back, the devices say “LedTec SIG-5030”. -A quick Internet search turns up a user's manual for these devices. They appear to be traffic signs, with a programmable matrix of amber LED lights. The intent is of the humans is probably to display some kind of information on them during their event. Interestingly, the screens can be programmed over a wireless network. Could it be they are connected to the building's local network? +A quick internet search turns up a user manual for these devices. They appear to be traffic signs, with a programmable matrix of amber LED lights. The intent of the humans is probably to display some kind of information on them during their event. Interestingly, the screens can be programmed over a wireless network. Could it be they are connected to the building's local network? Each device on a network gets an _IP address_, which other devices can use to send it messages. We talk more about that in [Chapter ?](browser). Carla notices that her own phones all get addresses like `10.0.0.20` or `10.0.0.33`. It might be worth trying to send messages to all such addresses and see if any one of them responds to the interface described in the manual for the signs. -[Chapter ?](http) shows how to make real requests on real networks. In this chapter, we'll use a simplified dummy function called `request` for network communication. This function takes two arguments—a network address and a message, which may be anything that can be sent as JSON—and returns a promise that either resolves to a response from the machine at the given address, or a rejects if there was a problem. +[Chapter ?](http) shows how to make real requests on real networks. In this chapter, we'll use a simplified dummy function called `request` for network communication. This function takes two arguments—a network address and a message, which may be anything that can be sent as JSON—and returns a promise that either resolves to a response from the machine at the given address, or rejects if there was a problem. -According to the manual, you can change what is displayed on a SIG-5030 sign by sending it a message with content like `{"command": "display", "data": [0, 0, 3, …]}`, where `data` holds one number per LED dot, providing its brightness—0 means off, 3 means maximum brightness. Each sign is 50 lights wide and 30 lights high, so an update command should send 1500 numbers. +According to the manual, you can change what is displayed on a SIG-5030 sign by sending it a message with content like `{"command": "display", "data": [0, 0, 3, …]}`, where `data` holds one number per LED dot, providing its brightness—0 means off, 3 means maximum brightness. Each sign is 50 lights wide and 30 lights high, so an update command should send 1,500 numbers. This code sends a display update message to all addresses on the local network, to see what sticks. Each of the numbers in an IP address can go from 0 to 255. In the data it sends, it activates a number of lights corresponding to the network address's last number. @@ -462,7 +462,7 @@ for (let addr = 1; addr < 256; addr++) { Since most of these addresses won't exist or will not accept such messages, the `catch` call makes sure network errors don't crash the program. The requests are all sent out immediately, without waiting for other requests to finish, in order to not waste time when some of the machines don't answer. -Having fired off her network scan, Carla heads back outside to see the result. To her delight, all of the screens are now showing a stripe of light in their top left corners. They _are_ on the local network, and they _do_ accept commands. She quickly notes the numbers shown on each screen. There are 9 screens, arranged three high and three wide. They have the following network addresses: +Having fired off her network scan, Carla heads back outside to see the result. To her delight, all of the screens are now showing a stripe of light in their upper-left corners. They _are_ on the local network, and they _do_ accept commands. She quickly notes the numbers shown on each screen. There are nine screens, arranged three high and three wide. They have the following network addresses: ```{includeCode: true} const screenAddresses = [ @@ -474,11 +474,11 @@ const screenAddresses = [ Now this opens up possibilities for all kinds of shenanigans. She could show “crows rule, humans drool” on the wall in giant letters. But that feels a bit crude. Instead, she plans to show a video of a flying crow covering all of the screens at night. -Carla finds a fitting video clip, in which a second and a half of footage can be repeated to create a looping video showing a crow's wingbeat. To fit the nine screens (each of which can show 50 by 30 pixels), Carla cuts and resizes the videos to get a series of 150-by-90 images, ten per second. Those are then each cut into nine rectangles, and processed so that the dark spots on the video (where the crow is) show a bright light, and the light spots (no crow) are left dark, which should create the effect of an amber crow flying against a black background. +Carla finds a fitting video clip, in which a second and a half of footage can be repeated to create a looping video showing a crow's wingbeat. To fit the nine screens (each of which can show 50×30 pixels), Carla cuts and resizes the videos to get a series of 150×90 images, 10 per second. Those are then each cut into nine rectangles, and processed so that the dark spots on the video (where the crow is) show a bright light, and the light spots (no crow) are left dark, which should create the effect of an amber crow flying against a black background. She has set up the `clipImages` variable to hold an array of frames, where each frame is represented with an array of nine sets of pixels—one for each screen—in the format that the signs expect. -To display a single frame of the video, Carla needs to send a request to all the screens at once. But she also needs to wait for the result of these requests, both in order to not start sending the next frame before the current one has been properly sent, and in order to notice when requests are failing. +To display a single frame of the video, Carla needs to send a request to all the screens at once. But she also needs to wait for the result of these requests, both in order to not start sending the next frame before the current one has been properly sent and in order to notice when requests are failing. {{index "Promise.all function"}} @@ -498,7 +498,7 @@ function displayFrame(frame) { This maps over the images in `frame` (which is an array of display data arrays) to create an array of request promises. It then returns a promise that combines all of those. -In order to be able to stop a playing video, the process is wrapped in a class. This class has an asynchronous `play` method that returns a promise that only resolves when the playback is stopped again via the `stop` method. +In order to be able to stop a playing video, the process is wrapped in a class. This class has an asynchronous `play` method that returns a promise that resolves only when the playback is stopped again via the `stop` method. ```{includeCode: true} function wait(time) { @@ -527,7 +527,7 @@ class VideoPlayer { } ``` -The `wait` function wraps `setTimeout` in a promise that resolves after the given amount of milliseconds. This is useful for controlling the speed of the playback. +The `wait` function wraps `setTimeout` in a promise that resolves after the given number of milliseconds. This is useful for controlling the speed of the playback. ```{startCode: true} let video = new VideoPlayer(clipImages, 100); @@ -623,7 +623,7 @@ The `async fileName =>` part shows how ((arrow function))s can also be made `asy The code doesn't immediately look suspicious... it maps the `async` arrow function over the array of names, creating an array of promises, and then uses `Promise.all` to wait for all of these before returning the list they build up. -But it is entirely broken. It'll always return only a single line of output, listing the file that took the longest to read. +But this program is entirely broken. It'll always return only a single line of output, listing the file that took the longest to read. {{if interactive @@ -638,15 +638,15 @@ Can you work out why? {{index "+= operator"}} -The problem lies in the `+=` operator, which takes the _current_ value of `list` at the time where the statement starts executing and then, when the `await` finishes, sets the `list` binding to be that value plus the added string. +The problem lies in the `+=` operator, which takes the _current_ value of `list` at the time the statement starts executing and then, when the `await` finishes, sets the `list` binding to be that value plus the added string. {{index "await keyword"}} -But between the time where the statement starts executing and the time where it finishes there's an asynchronous gap. The `map` expression runs before anything has been added to the list, so each of the `+=` operators starts from an empty string and ends up, when its storage retrieval finishes, setting `list` to the result of adding its line to the empty string. +But between the time the statement starts executing and the time it finishes, there's an asynchronous gap. The `map` expression runs before anything has been added to the list, so each of the `+=` operators starts from an empty string and ends up, when its storage retrieval finishes, setting `list` to the result of adding its line to the empty string. {{index "side effect"}} -This could have easily been avoided by returning the lines from the mapped promises and calling `join` on the result of `Promise.all`, instead of building up the list by changing a binding. As usual, computing new values is less error-prone than changing existing values. +This could have easily been avoided by returning the lines from the mapped promises and calling `join` on the result of `Promise.all`, instead of building up the list by changing a binding. As usual, computing new values is less error prone than changing existing values. {{index "fileSizes function"}} @@ -674,11 +674,11 @@ Programming asynchronously is made easier by promises, objects that represent ac {{index "quiet times (exercise)", "security camera", "Carla the crow", "async function"}} -There's a security camera near Carla's lab that's activated by a motion sensor. It is connected to the network and starts sending out a video stream when it is active. Because she'd prefer not to be discovered, Carla has set up a system that notices this kind of wireless network traffic and turns on a light in her lair whenever there is activity outside, so that she knows when to keep quiet. +There's a security camera near Carla's lab that's activated by a motion sensor. It is connected to the network and starts sending out a video stream when it is active. Because she'd rather not be discovered, Carla has set up a system that notices this kind of wireless network traffic and turns on a light in her lair whenever there is activity outside, so she knows when to keep quiet. {{index "Date class", "Date.now function", timestamp}} -She's also been logging the times at which the camera is tripped for a while, and wants to use this information to visualize which times, in an average week, tend to be quiet, and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line. +She's also been logging the times at which the camera is tripped for a while and wants to use this information to visualize which times, in an average week, tend to be quiet and which tend to be busy. The log is stored in files holding one time stamp number (as returned by `Date.now()`) per line. ```{lang: null} 1695709940692 @@ -686,15 +686,15 @@ She's also been logging the times at which the camera is tripped for a while, an 1695701189163 ``` -The `"camera_logs.txt"` file holds a list of log files. Write an asynchronous function `activityTable(day)` that for a given day of the week returns an array of 24 numbers, one for each hour of the day, that hold the amount of camera network traffic observations seen in that hour of the day. Days are identified by number using the system used by `Date.getDay`, where Sunday is 0 and Saturday is 6. +The `"camera_logs.txt"` file holds a list of logfiles. Write an asynchronous function `activityTable(day)` that for a given day of the week returns an array of 24 numbers, one for each hour of the day, that hold the number of camera network traffic observations seen in that hour of the day. Days are identified by number using the system used by `Date.getDay`, where Sunday is 0 and Saturday is 6. The `activityGraph` function, provided by the sandbox, summarizes such a table into a string. {{index "textFile function"}} -Use the `textFile` function defined earlier—given a filename, it returns a promise that resolves to the file's content. Remember that `new Date(timestamp)` creates a `Date` object for that time, which has `getDay` and `getHours` methods returning the day of the week and the hour of the day. +To read the files, use the `textFile` function defined earlier—given a filename, it returns a promise that resolves to the file's content. Remember that `new Date(timestamp)` creates a `Date` object for that time, which has `getDay` and `getHours` methods returning the day of the week and the hour of the day. -Both types of files—the list of log files and the log files themselves—have each piece of data on its own line, separated by newline (`"\n"`) characters. +Both types of files—the list of logfiles and the logfiles themselves—have each piece of data on its own line, separated by newline (`"\n"`) characters. {{if interactive @@ -714,9 +714,9 @@ if}} {{index "quiet times (exercise)", "split method", "textFile function", "Date class"}} -You will need to convert the content of these files to an array. The easiest way to do that is to use the `split` method on the string produced by `textFile`. Note that for the log files, that will still give you an array of strings, which you have to convert to numbers before passing them to `new Date`. +You will need to convert the content of these files to an array. The easiest way to do that is to use the `split` method on the string produced by `textFile`. Note that for the logfiles, that will still give you an array of strings, which you have to convert to numbers before passing them to `new Date`. -Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the log files and the numbers in every log file) and for each one, if it happened on the correct day, take the hour it occurred in, and add one to the corresponding number in the table. +Summarizing all the time points into a table of hours can be done by creating a table (array) that holds a number for each hour in the day. You can then loop over all the timestamps (over the logfiles and the numbers in every logfile) and for each one, if it happened on the correct day, take the hour it occurred in, and add one to the corresponding number in the table. {{index "async function", "await keyword", "Promise class"}} @@ -746,7 +746,7 @@ if}} {{index "async function", "await keyword", performance}} -In this style, using `Promise.all` will be more convenient than trying to model a loop over the log files. In the `async` function, just using `await` in a loop is simpler. If reading a file takes some time, which of these two approaches will take the least time to run? +In this style, using `Promise.all` will be more convenient than trying to model a loop over the logfiles. In the `async` function, just using `await` in a loop is simpler. If reading a file takes some time, which of these two approaches will take the least time to run? {{index "rejecting (a promise)"}} @@ -756,11 +756,11 @@ If one of the files listed in the file list has a typo, and reading it fails, ho {{index "real promises (exercise)", "then method", "textFile function", "Promise.all function"}} -The most straightforward approach to writing this function is to use a chain of `then` calls. The first promise is produced by reading the list of log files. The first callback can split this list and map `textFile` over it to get an array of promises to pass to `Promise.all`. It can return the object returned by `Promise.all`, so that whatever that returns becomes the result of the return value of this first `then`. +The most straightforward approach to writing this function is to use a chain of `then` calls. The first promise is produced by reading the list of logfiles. The first callback can split this list and map `textFile` over it to get an array of promises to pass to `Promise.all`. It can return the object returned by `Promise.all`, so that whatever that returns becomes the result of the return value of this first `then`. {{index "asynchronous programming"}} -We now have a promise that returns an array of log files. We can call `then` again on that, and put the timestamp-counting logic in there. Something like this: +We now have a promise that returns an array of logfiles. We can call `then` again on that, and put the timestamp-counting logic in there. Something like this: ```{test: no} function activityTable(day) { @@ -789,11 +789,11 @@ function activityTable(day) { {{index "await keyword", scheduling}} -Which shows that the way you structure your promises can have a real effect on the way the work is scheduled. A simple loop with `await` in it will make the process completely linear—it waits for each file to load before proceeding. `Promise.all` makes it possible for multiple tasks to conceptually be worked on at the same time, allowing them to make progress while files are still being loaded. This can be faster, but it also makes the order in which things will happen less predictable. In this case, where we're only going to be incrementing numbers in a table, that isn't hard to do in a safe way. For other kinds of problems, it may be a lot more difficult. +This shows that the way you structure your promises can have a real effect on the way the work is scheduled. A simple loop with `await` in it will make the process completely linear—it waits for each file to load before proceeding. `Promise.all` makes it possible for multiple tasks to conceptually be worked on at the same time, allowing them to make progress while files are still being loaded. This can be faster, but it also makes the order in which things will happen less predictable. In this case, we're only going to be incrementing numbers in a table, which isn't hard to do in a safe way. For other kinds of problems, it may be a lot more difficult. {{index "rejecting (a promise)", "then method"}} -When a file in the list doesn't exist, the promise returned by `textFile` will be rejected. Because `Promise.all` rejects if any of the promises given to it fail, the return value of the callback given to the first `then` will also be a rejected promise. That makes the promise returned by `then` fail, so that the callback given to the second `then` isn't even called, and a rejected promise is returned from the function. +When a file in the list doesn't exist, the promise returned by `textFile` will be rejected. Because `Promise.all` rejects if any of the promises given to it fail, the return value of the callback given to the first `then` will also be a rejected promise. That makes the promise returned by `then` fail, so the callback given to the second `then` isn't even called, and a rejected promise is returned from the function. hint}} @@ -801,11 +801,11 @@ hint}} {{index "Promise class", "Promise.all function", "building Promise.all (exercise)"}} -As we saw, given an array of ((promise))s, `Promise.all` returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by `all` fails too, with the failure reason from the failing promise. +As we saw, given an array of ((promise))s, `Promise.all` returns a promise that waits for all of the promises in the array to finish. It then succeeds, yielding an array of result values. If a promise in the array fails, the promise returned by `all` fails too, passing on the failure reason from the failing promise. Implement something like this yourself as a regular function called `Promise_all`. -Remember that after a promise has succeeded or failed, it can't succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle failure of your promise. +Remember that after a promise has succeeded or failed, it can't succeed or fail again, and further calls to the functions that resolve it are ignored. This can simplify the way you handle a failure of your promise. {{if interactive diff --git a/12_language.md b/12_language.md index 7723036f..04a28733 100644 --- a/12_language.md +++ b/12_language.md @@ -14,7 +14,7 @@ quote}} Building your own ((programming language)) is surprisingly easy (as long as you do not aim too high) and very enlightening. -The main thing I want to show in this chapter is that there is no ((magic)) involved in building a programming language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane. +The main thing I want to show in this chapter is that there's no ((magic)) involved in building a programming language. I've often felt that some human inventions were so immensely clever and complicated that I'd never be able to understand them. But with a little reading and experimenting, they often turn out to be quite mundane. {{index "Egg language", [abstraction, "in Egg"]}} @@ -49,7 +49,7 @@ do(define(x, 10), {{index block, [syntax, "of Egg"]}} -The ((uniformity)) of the ((Egg language)) means that things that are ((operator))s in JavaScript (such as `>`) are normal bindings in this language, applied just like other ((function))s. And since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence. +The ((uniformity)) of the ((Egg language)) means that things that are ((operator))s in JavaScript (such as `>`) are normal bindings in this language, applied just like other ((function))s. Since the syntax has no concept of a block, we need a `do` construct to represent doing multiple things in sequence. {{index "type property", parsing, ["data structure", tree]}} @@ -74,7 +74,7 @@ The `>(x, 5)` part of the previous program would be represented like this: {{indexsee "abstract syntax tree", "syntax tree", ["data structure", tree]}} -Such a data structure is called a _((syntax tree))_. If you imagine the objects as dots and the links between them as lines between those dots, it has a ((tree))like shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again. +Such a data structure is called a _((syntax tree))_. If you imagine the objects as dots and the links between them as lines between those dots, as shown in the following diagram, the structure has a ((tree))like shape. The fact that expressions contain other expressions, which in turn might contain more expressions, is similar to the way tree branches split and split again. {{figure {url: "img/syntax_tree.svg", alt: "A diagram showing the structure of the syntax tree for the example program. The root is labeled 'do' and has two children, one labeled 'define' and one labeled 'if'. Those in turn have more children, describing their content.", width: "5cm"}}} @@ -92,7 +92,7 @@ Fortunately, this problem can be solved very well by writing a parser function t {{index "parseExpression function", "syntax tree"}} -We define a function `parseExpression`, which takes a string as input and returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments. +We define a function `parseExpression` that takes a string as input. It returns an object containing the data structure for the expression at the start of the string, along with the part of the string left after parsing this expression. When parsing subexpressions (the argument to an application, for example), this function can be called again, yielding the argument expression as well as the text that remains. This text may in turn contain more arguments or may be the closing parenthesis that ends the list of arguments. This is the first part of the parser: @@ -122,11 +122,11 @@ function skipSpace(string) { {{index "skipSpace function", [whitespace, syntax]}} -Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. That is what the `skipSpace` function helps with. +Because Egg, like JavaScript, allows any amount of whitespace between its elements, we have to repeatedly cut the whitespace off the start of the program string. The `skipSpace` function helps with this. {{index "literal expression", "SyntaxError type"}} -After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which one matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use the `SyntaxError` constructor here. This is an exception class defined by the standard, like `Error`, but more specific. +After skipping any leading space, `parseExpression` uses three ((regular expression))s to spot the three atomic elements that Egg supports: strings, numbers, and words. The parser constructs a different kind of data structure depending on which expression matches. If the input does not match one of these three forms, it is not a valid expression, and the parser throws an error. We use the `SyntaxError` constructor here. This is an exception class defined by the standard, like `Error`, but more specific. {{index "parseApply function"}} @@ -155,13 +155,9 @@ function parseApply(expr, program) { } ``` -{{index parsing}} - -If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` returns the expression it was given. +{{index parsing, recursion}} -{{index recursion}} - -Otherwise, it skips the opening parenthesis and creates the ((syntax tree)) object for this application expression. It then recursively calls `parseExpression` to parse each argument until a closing parenthesis is found. The recursion is indirect, through `parseApply` and `parseExpression` calling each other. +If the next character in the program is not an opening parenthesis, this is not an application, and `parseApply` returns the expression it was given. Otherwise, it skips the opening parenthesis and creates the ((syntax tree)) object for this application expression. It then recursively calls `parseExpression` to parse each argument until a closing parenthesis is found. The recursion is indirect, through `parseApply` and `parseExpression` calling each other. Because an application expression can itself be applied (such as in `multiplier(2)(1)`), `parseApply` must, after it has parsed an application, call itself again to check whether another pair of parentheses follows. @@ -227,21 +223,21 @@ function evaluate(expr, scope) { {{index "literal expression", scope}} -The evaluator has code for each of the ((expression)) types. A literal value expression produces its value. (For example, the expression `100` just evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding's value. +The evaluator has code for each of the ((expression)) types. A literal value expression produces its value. (For example, the expression `100` evaluates to the number 100.) For a binding, we must check whether it is actually defined in the scope and, if it is, fetch the binding's value. {{index [function, application]}} -Applications are more involved. If they are a ((special form)), like `if`, we do not evaluate anything and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments. +Applications are more involved. If they are a ((special form)), like `if`, we do not evaluate anything—we just and pass the argument expressions, along with the scope, to the function that handles this form. If it is a normal call, we evaluate the operator, verify that it is a function, and call it with the evaluated arguments. -We use plain JavaScript function values to represent Egg's function values. We will come back to this [later](language#egg_fun), when the special form called `fun` is defined. +We use plain JavaScript function values to represent Egg's function values. We will come back to this [later](language#egg_fun), when the special form `fun` is defined. {{index readability, "evaluate function", recursion, parsing}} -The recursive structure of `evaluate` resembles the similar structure of the parser, and both mirror the structure of the language itself. It would also be possible to combine the parser and the evaluator into one function, and evaluate during parsing. But splitting them up this way makes the program clearer and more flexible. +The recursive structure of `evaluate` resembles the structure of the parser, and both mirror the structure of the language itself. It would also be possible to combine the parser and the evaluator into one function and evaluate during parsing, but splitting them up this way makes the program clearer and more flexible. {{index "Egg language", interpretation}} -This is really all that is needed to interpret Egg. It is that simple. But without defining a few special forms and adding some useful values to the ((environment)), you can't do much with this language yet. +This is really all that's needed to interpret Egg. It's that simple. But without defining a few special forms and adding some useful values to the ((environment)), you can't do much with this language yet. ## Special forms @@ -263,15 +259,15 @@ specialForms.if = (args, scope) => { {{index "conditional execution", "ternary operator", "?: operator", "conditional operator"}} -Egg's `if` construct expects exactly three arguments. It will evaluate the first, and if the result isn't the value `false`, it will evaluate the second. Otherwise, the third gets evaluated. This `if` form is more similar to JavaScript's ternary `?:` operator than to JavaScript's `if`. It is an expression, not a statement, and it produces a value, namely, the result of the second or third argument. +Egg's `if` construct expects exactly three arguments. It will evaluate the first, and if the result isn't the value `false`, it will evaluate the second. Otherwise, the third gets evaluated. This `if` form is more similar to JavaScript's ternary `?:` operator than to JavaScript's `if`. It is an expression, not a statement, and it produces a value—namely, the result of the second or third argument. {{index Boolean}} -Egg also differs from JavaScript in how it handles the condition value to `if`. It will not treat things like zero or the empty string as false, only the precise value `false`. +Egg also differs from JavaScript in how it handles the condition value to `if`. It will treat only the value `false` as false, not things like zero or the empty string. {{index "short-circuit evaluation"}} -The reason we need to represent `if` as a special form, rather than a regular function, is that all arguments to functions are evaluated before the function is called, whereas `if` should evaluate only _either_ its second or its third argument, depending on the value of the first. +The reason we need to represent `if` as a special form rather than a regular function is that all arguments to functions are evaluated before the function is called, whereas `if` should evaluate only _either_ its second or its third argument, depending on the value of the first. The `while` form is similar. @@ -285,7 +281,7 @@ specialForms.while = (args, scope) => { } // Since undefined does not exist in Egg, we return false, - // for lack of a meaningful result. + // for lack of a meaningful result return false; }; ``` @@ -342,7 +338,7 @@ console.log(evaluate(prog, topScope)); {{index arithmetic, "Function constructor"}} -To supply basic ((arithmetic)) and ((comparison)) ((operator))s, we will also add some function values to the ((scope)). In the interest of keeping the code short, we'll use `Function` to synthesize a bunch of operator functions in a loop, instead of defining them individually. +To supply basic ((arithmetic)) and ((comparison)) ((operator))s, we will also add some function values to the ((scope)). In the interest of keeping the code short, we'll use `Function` to synthesize a bunch of operator functions in a loop instead of defining them individually. ```{includeCode: true} for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { @@ -350,7 +346,7 @@ for (let op of ["+", "-", "*", "/", "==", "<", ">"]) { } ``` -A way to ((output)) values is also useful, so we'll wrap `console.log` in a function and call it `print`. +It is also useful to have a way to ((output)) values, so we'll wrap `console.log` in a function and call it `print`. ```{includeCode: true} topScope.print = value => { @@ -387,7 +383,7 @@ do(define(total, 0), {{index "summing example", "Egg language"}} -This is the program we've seen several times before, which computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in less than 150 ((lines of code)). +This is the program we've seen several times before that computes the sum of the numbers 1 to 10, expressed in Egg. It is clearly uglier than the equivalent JavaScript program—but not bad for a language implemented in fewer than 150 ((lines of code)). {{id egg_fun}} @@ -395,9 +391,7 @@ This is the program we've seen several times before, which computes the sum of t {{index function, "Egg language"}} -A programming language without functions is a poor programming language indeed. - -Fortunately, it isn't hard to add a `fun` construct, which treats its last argument as the function's body and uses all arguments before that as the names of the function's parameters. +A programming language without functions is a poor programming language indeed. Fortunately, it isn't hard to add a `fun` construct, which treats its last argument as the function's body and uses all arguments before that as the names of the function's parameters. ```{includeCode: true} specialForms.fun = (args, scope) => { @@ -460,7 +454,7 @@ Traditionally, ((compilation)) involves converting the program to ((machine code {{index simplicity, "Function constructor", transpilation}} -It would be possible to write an alternative ((evaluation)) strategy for Egg, one that first converts the program to a JavaScript program, uses `Function` to invoke the JavaScript compiler on it, and then runs the result. When done right, this would make Egg run very fast while still being quite simple to implement. +It would be possible to write an alternative ((evaluation)) strategy for Egg, one that first converts the program to a JavaScript program, uses `Function` to invoke the JavaScript compiler on it, and runs the result. When done right, this would make Egg run very fast while still being quite simple to implement. If you are interested in this topic and willing to spend some time on it, I encourage you to try to implement such a compiler as an exercise. @@ -468,7 +462,7 @@ If you are interested in this topic and willing to spend some time on it, I enco {{index "Egg language"}} -When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. Bridging the gap to a more primitive system, such as the machine code the processor understands, is more effort—but the way it works resembles what we are doing here. +When we defined `if` and `while`, you probably noticed that they were more or less trivial wrappers around JavaScript's own `if` and `while`. Similarly, the values in Egg are just regular old JavaScript values. Bridging the gap to a more primitive system, such as the machine code the processor understands, takes more effort—but the way it works resembles what we are doing here. Though the toy language in this chapter doesn't do anything that couldn't be done better in JavaScript, there _are_ situations where writing small languages helps get real work done. @@ -492,7 +486,7 @@ application = expr '(' (expr (',' expr)*)? ')' {{index expressivity}} -This is what is usually called a _((domain-specific language))_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain, and nothing else. +This is what is usually called a _((domain-specific language))_, a language tailored to express a narrow domain of knowledge. Such a language can be more expressive than a general-purpose language because it is designed to describe exactly the things that need to be described in its domain and nothing else. ## Exercises @@ -500,7 +494,7 @@ This is what is usually called a _((domain-specific language))_, a language tail {{index "Egg language", "arrays in egg (exercise)", [array, "in Egg"]}} -Add support for arrays to Egg by adding the following three functions to the top scope: `array(...values)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the n^th^ element from an array. +Add support for arrays to Egg by adding the following three functions to the top scope: `array(...values)` to construct an array containing the argument values, `length(array)` to get an array's length, and `element(array, n)` to fetch the *n*th element from an array. {{if interactive @@ -604,7 +598,7 @@ if}} {{index "comments in egg (exercise)", [whitespace, syntax]}} -Make sure your solution handles multiple comments in a row, with potentially whitespace between or after them. +Make sure your solution handles multiple comments in a row, with whitespace potentially between or after them. A ((regular expression)) is probably the easiest way to solve this. Write something that matches "whitespace or a comment, zero or more times". Use the `exec` or `match` method and look at the length of the first element in the returned array (the whole match) to find out how many characters to slice off. @@ -655,6 +649,6 @@ You will have to loop through one ((scope)) at a time, using `Object.getPrototyp {{index "global scope", "run-time error"}} -If the outermost scope is reached (`Object.getPrototypeOf` returns null) and we haven't found the binding yet, it doesn't exist, and an error should be thrown. +If the outermost scope is reached (`Object.getPrototypeOf` returns `null`) and we haven't found the binding yet, it doesn't exist, and an error should be thrown. hint}} diff --git a/13_browser.md b/13_browser.md index 16b402d5..272c1709 100644 --- a/13_browser.md +++ b/13_browser.md @@ -1,8 +1,8 @@ # JavaScript and the Browser -{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A very short personal history", chapter: true} +{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A Very Short Personal History", chapter: true} -The dream behind the Web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished. +The dream behind the web is of a common information space in which we communicate by sharing information. Its universality is essential: the fact that a hypertext link can point to anything, be it personal, local or global, be it draft or highly polished. quote}} @@ -10,19 +10,19 @@ quote}} {{figure {url: "img/chapter_picture_13.jpg", alt: "Illustration showing a telephone switchboard", chapter: "framed"}}} -The next chapters of this book will talk about web browsers. Without web ((browser))s, there would be no JavaScript. Or even if there were, no one would ever have paid any attention to it. +The next chapters of this book will discuss web browsers. Without ((browser))s, there would be no JavaScript—or if there were, no one would ever have paid any attention to it. {{index decentralization, compatibility}} -Web technology has been decentralized from the start, not just technically but also in the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which then, sometimes, ended up being adopted by others—and finally set down as in ((standards)). +Web technology has been decentralized from the start, not just technically but also in terms of the way it evolved. Various browser vendors have added new functionality in ad hoc and sometimes poorly thought-out ways, which were then—sometimes—adopted by others, and finally set down in ((standards)). -This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally open hostility). On the other hand, the haphazard way in which the Web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. +This is both a blessing and a curse. On the one hand, it is empowering to not have a central party control a system but have it be improved by various parties working in loose ((collaboration)) (or occasionally, open hostility). On the other hand, the haphazard way in which the web was developed means that the resulting system is not exactly a shining example of internal ((consistency)). Some parts of it are downright confusing and badly designed. ## Networks and the Internet Computer ((network))s have been around since the 1950s. If you put cables between two or more computers and allow them to send data back and forth through these cables, you can do all kinds of wonderful things. -And if connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((Internet))_. It has lived up to its promise. +If connecting two machines in the same building allows us to do wonderful things, connecting machines all over the planet should be even better. The technology to start implementing this vision was developed in the 1980s, and the resulting network is called the _((internet))_. It has lived up to its promise. A computer can use this network to shoot bits at another computer. For any effective ((communication)) to arise out of this bit-shooting, the computers on both ends must know what the bits are supposed to represent. The meaning of any given sequence of bits depends entirely on the kind of thing that it is trying to express and on the ((encoding)) mechanism used. @@ -30,25 +30,25 @@ A computer can use this network to shoot bits at another computer. For any effec A _network ((protocol))_ describes a style of communication over a ((network)). There are protocols for sending email, for fetching email, for sharing files, and even for controlling computers that happen to be infected by malicious software. -{{indexsee "Hypertext Transfer Protocol", HTTP}} +{{indexsee "HyperText Transfer Protocol", HTTP}} -The _Hypertext Transfer Protocol_ (((HTTP))) is a protocol for retrieving named ((resource))s (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use: +The _HyperText Transfer Protocol_ (((HTTP))) is a protocol for retrieving named ((resource))s (chunks of information, such as web pages or pictures). It specifies that the side making the request should start with a line like this, naming the resource and the version of the protocol that it is trying to use: ```{lang: http} GET /index.html HTTP/1.1 ``` -There are a lot more rules about the way the requester can include more information in the ((request)) and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter ?](http). +There are many more rules about the way the requester can include more information in the ((request)) and the way the other side, which returns the resource, packages up its content. We'll look at HTTP in a little more detail in [Chapter ?](http). {{index layering, stream, ordering}} -Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Providing those guarantees that on top of the primitive data-sending that the network gives you is already a rather tricky problem. +Most protocols are built on top of other protocols. HTTP treats the network as a streamlike device into which you can put bits and have them arrive at the correct destination in the correct order. Providing those guarantees on top of the primitive data-sending that the network gives you is already a rather tricky problem. {{index TCP}} {{indexsee "Transmission Control Protocol", TCP}} -The _Transmission Control Protocol_ (TCP) is a ((protocol)) that addresses this problem. All Internet-connected devices "speak" it, and most communication on the ((Internet)) is built on top of it. +The _Transmission Control Protocol_ (TCP) is a ((protocol)) that addresses this problem. All internet-connected devices "speak" it, and most communication on the ((internet)) is built on top of it. {{index "listening (TCP)"}} @@ -56,7 +56,7 @@ A TCP ((connection)) works as follows: one computer must be waiting, or _listeni Another computer can then establish a ((connection)) by connecting to the target machine using the correct port number. If the target machine can be reached and is listening on that port, the connection is successfully created. The listening computer is called the _((server))_, and the connecting computer is called the _((client))_. -{{index [abtraction, "of the network"]}} +{{index [abstraction, "of the network"]}} Such a connection acts as a two-way ((pipe)) through which bits can flow—the machines on both ends can put data into it. Once the bits are successfully transmitted, they can be read out again by the machine on the other side. This is a convenient model. You could say that ((TCP)) provides an abstraction of the network. @@ -64,15 +64,15 @@ Such a connection acts as a two-way ((pipe)) through which bits can flow—the m ## The Web -The _((World Wide Web))_ (not to be confused with the ((Internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. The "Web" part in the name refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. +The _((World Wide Web))_ (not to be confused with the ((internet)) as a whole) is a set of ((protocol))s and formats that allow us to visit web pages in a browser. The word _Web_ refers to the fact that such pages can easily link to each other, thus connecting into a huge ((mesh)) that users can move through. -To become part of the Web, all you need to do is connect a machine to the ((Internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. +To become part of the web, all you need to do is connect a machine to the ((internet)) and have it listen on port 80 with the ((HTTP)) protocol so that other computers can ask it for documents. {{index URL}} -{{indexsee "Uniform Resource Locator", URL}} +{{indexsee "uniform resource locator", URL}} -Each ((document)) on the Web is named by a _Uniform Resource Locator_ (URL), which looks something like this: +Each ((document)) on the web is named by a _uniform resource locator_ (URL), which looks something like this: ```{lang: null} http://eloquentjavascript.net/13_browser.html @@ -82,9 +82,9 @@ Each ((document)) on the Web is named by a _Uniform Resource Locator_ (URL), whi {{index HTTPS}} -The first part tells us that this URL uses the HTTP ((protocol)) (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which ((server)) we are requesting the document from. Last is a path string that identifies the specific document (or _((resource))_) we are interested in. +The first part tells us that this URL uses the HTTP ((protocol)) (as opposed to, for example, encrypted HTTP, which would be _https://_). Then comes the part that identifies which ((server)) we are requesting the document from. Last is a path string that identifies the document (or _((resource))_) we are interested in. -Machines connected to the Internet get an _((IP address))_, which is a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. But lists of more or less random numbers are hard to remember and awkward to type, so you can instead register a _((domain)) name_ for a specific address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. +Machines connected to the internet get an _((IP address))_, a number that can be used to send messages to that machine, and looks something like `149.210.142.219` or `2001:4860:4860::8888`. Since lists of more or less random numbers are hard to remember and awkward to type, you can instead register a _((domain)) name_ for an address or set of addresses. I registered _eloquentjavascript.net_ to point at the IP address of a machine I control and can thus use that domain name to serve web pages. {{index browser}} @@ -94,9 +94,9 @@ If you type this URL into your browser's ((address bar)), the browser will try t {{index HTML}} -{{indexsee "Hypertext Markup Language", HTML}} +{{indexsee "HyperText Markup Language", HTML}} -HTML, which stands for _Hypertext Markup Language_, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. +_HTML_, which stands for HyperText Markup Language, is the document format used for web pages. An HTML document contains ((text)), as well as _((tag))s_ that give structure to the text, describing things such as links, paragraphs, and headings. A short HTML document might look like this: @@ -130,7 +130,7 @@ The tags, wrapped in ((angle brackets)) (`<` and `>`, the symbols for _less than {{index doctype, version}} -The document starts with ``, which tells the browser to interpret the page as _modern_ HTML, as opposed to obsolete styles that were used in the past. +The document starts with ``, which tells the browser to interpret the page as _modern_ HTML, as opposed to obsolete styles used in the past. {{index "head (HTML tag)", "body (HTML tag)", "title (HTML tag)", "h1 (HTML tag)", "p (HTML tag)"}} @@ -146,15 +146,15 @@ Some kinds of ((tag))s do not enclose anything and thus do not need to be closed {{index [escaping, "in HTML"]}} -To be able to include ((angle brackets)) in the text of a document, even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` ("less than"), and a closing bracket is written as `>` ("greater than"). In HTML, an ampersand (`&`) character followed by a name or character code and a semicolon (`;`) is called an _((entity))_ and will be replaced by the character it encodes. +To be able to include ((angle brackets)) in the text of a document even though they have a special meaning in HTML, yet another form of special notation has to be introduced. A plain opening angle bracket is written as `<` ("less than"), and a closing bracket is written as `>` ("greater than"). In HTML, an ampersand (`&`) character followed by a name or character code and a semicolon (`;`) is called an _((entity))_ and will be replaced by the character it encodes. {{index ["backslash character", "in strings"], "ampersand character", "double-quote character"}} -This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning too, they need to be escaped as `&`. Inside attribute values, which are wrapped in double quotes, `"` can be used to insert an actual quote character. +This is analogous to the way backslashes are used in JavaScript strings. Since this mechanism gives ampersand characters a special meaning too, they need to be escaped as `&`. Inside attribute values, which are wrapped in double quotes, `"` can be used to insert a literal quote character. {{index "error tolerance", parsing}} -HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser automatically adds them. The way in which this is done has been standardized, and you can rely on all modern browsers to do it in the same way. +HTML is parsed in a remarkably error-tolerant way. When tags that should be there are missing, the browser automatically adds them. The way this is done has been standardized, and you can rely on all modern browsers to do it in the same way. The following document will be treated just like the one shown previously: @@ -172,13 +172,13 @@ The following document will be treated just like the one shown previously: {{index "title (HTML tag)", "head (HTML tag)", "body (HTML tag)", "html (HTML tag)"}} -The ``, `
`, and `` tags are gone completely. The browser knows that `` and `