From 2dd376eb7f8772de88e8e984499ad90fad3a4cf2 Mon Sep 17 00:00:00 2001 From: Michael DiBernardo Date: Sat, 9 Apr 2016 09:39:27 -0400 Subject: [PATCH] Use explicit `—`s. - Addresses https://github.com/aosabook/500lines/issues/203. --- blockcode/blockcode.markdown | 4 +- dagoba/dagoba.markdown | 56 ++++----- flow-shop/flow-shop.markdown | 6 +- functionalDB/functionalDB.markdown | 4 +- image-filters/image-filters.markdown | 10 +- interpreter/interpreter.markdown | 40 +++---- pedometer/pedometer.markdown | 4 +- .../same-origin-policy.markdown | 24 ++-- sampler/sampler.markdown | 12 +- static-analysis/static-analysis.markdown | 14 +-- template-engine/template-engine.markdown | 2 +- tex/500L.tex | 40 +++---- tex/blockcode.tex | 4 +- tex/dagoba.tex | 113 +++++++++--------- tex/flow-shop.tex | 18 +-- tex/functionalDB.tex | 10 +- tex/image-filters.tex | 14 +-- tex/interpreter.tex | 83 ++++++------- tex/pedometer.tex | 14 +-- tex/same-origin-policy.tex | 62 +++++----- tex/spreadsheet.tex | 9 +- tex/static-analysis.tex | 31 +++-- tex/template-engine.tex | 14 +-- tex/web-server.tex | 12 +- web-server/web-server.markdown | 6 +- 25 files changed, 299 insertions(+), 307 deletions(-) diff --git a/blockcode/blockcode.markdown b/blockcode/blockcode.markdown index 55fd176e6..915fb505c 100644 --- a/blockcode/blockcode.markdown +++ b/blockcode/blockcode.markdown @@ -5,7 +5,7 @@ _[Dethe](https://twitter.com/dethe) is a geek dad, aesthetic programmer, mentor, In block-based programming languages, you write programs by dragging and connecting blocks that represent parts of the program. Block-based languages differ from conventional programming languages, in which you type words and symbols. -Learning a programming language can be difficult because they are extremely sensitive to even the slightest of typos. Most programming languages are case-sensitive, have obscure syntax, and will refuse to run if you get so much as a semicolon in the wrong place --- or worse, leave one out. Further, most programming languages in use today are based on English and their syntax cannot be localized. +Learning a programming language can be difficult because they are extremely sensitive to even the slightest of typos. Most programming languages are case-sensitive, have obscure syntax, and will refuse to run if you get so much as a semicolon in the wrong place—or worse, leave one out. Further, most programming languages in use today are based on English and their syntax cannot be localized. In contrast, a well-done block language can eliminate syntax errors completely. You can still create a program which does the wrong thing, but you cannot create one with the wrong syntax: the blocks just won't fit that way. Block languages are more discoverable: you can see all the constructs and libraries of the language right in the list of blocks. Further, blocks can be localized into any human language without changing the meaning of the programming language. @@ -49,7 +49,7 @@ I've tried to follow some conventions and best practices throughout this project The code style is procedural, not object-oriented or functional. We could do the same things in any of these paradigms, but that would require more setup code and wrappers to impose on what exists already for the DOM. Recent work on [Custom Elements](http://webcomponents.org/) make it easier to work with the DOM in an OO way, and there has been a lot of great writing on [Functional JavaScript](https://leanpub.com/javascript-allonge/read), but either would require a bit of shoe-horning, so it felt simpler to keep it procedural. -There are eight source files in this project, but `index.html` and `blocks.css` are basic structure and style for the app and won't be discussed. Two of the JavaScript files won't be discussed in any detail either: `util.js` contains some helpers and serves as a bridge between different browser implementations --- similar to a library like jQuery but in less than 50 lines of code. `file.js` is a similar utility used for loading and saving files and serializing scripts. +There are eight source files in this project, but `index.html` and `blocks.css` are basic structure and style for the app and won't be discussed. Two of the JavaScript files won't be discussed in any detail either: `util.js` contains some helpers and serves as a bridge between different browser implementations—similar to a library like jQuery but in less than 50 lines of code. `file.js` is a similar utility used for loading and saving files and serializing scripts. These are the remaining files: diff --git a/dagoba/dagoba.markdown b/dagoba/dagoba.markdown index 4aa45130b..af7b2a638 100644 --- a/dagoba/dagoba.markdown +++ b/dagoba/dagoba.markdown @@ -6,10 +6,10 @@ _[Dann](https://twitter.com/dann) enjoys building things, like programming langu ## Prologue > "When we try to pick out anything by itself we find that it is bound fast by a thousand invisible cords that cannot be broken, to everything in the universe." -> --- John Muir +> —John Muir > "What went forth to the ends of the world to traverse not itself, God, the sun, Shakespeare, a commercial traveller, having itself traversed in reality itself becomes that self." -> --- James Joyce +> —James Joyce A long time ago, when the world was still young, all data walked happily in single file. If you wanted your data to jump over a fence, you just set the fence down in its path and each datum jumped it in turn. Punch cards in, punch cards out. Life was easy and programming was a breeze. @@ -19,7 +19,7 @@ Later programmers departed from this tradition, imposing a set of rules on how d For much of recorded history this relational model reigned supreme. Its dominance went unchallenged through two major language wars and countless skirmishes. It offered everything you could ask for in a model, for the small price of inefficiency, clumsiness and lack of scalability. For eons that was a price programmers were willing to pay. Then the internet happened. -The distributed revolution changed everything, again. Data broke free of spacial constraints and roamed from machine to machine. CAP-wielding theorists busted the relational monopoly, opening the door to a plethora of new herding techniques --- some of which hark back to the earliest attempts to domesticate random-access data. We're going to look at one of these, a style known as the graph database. +The distributed revolution changed everything, again. Data broke free of spacial constraints and roamed from machine to machine. CAP-wielding theorists busted the relational monopoly, opening the door to a plethora of new herding techniques—some of which hark back to the earliest attempts to domesticate random-access data. We're going to look at one of these, a style known as the graph database. [^items]: One of the very first database designs was the hierarchical model, which grouped items into tree-shaped hierarchies and is still used as the basis of IBM's IMS product, a high-speed transaction processing system. It's influence can also been seen in XML, file systems and geographic information storage. The network model, invented by Charles Bachmann and standardized by CODASYL, generalized the hierarchical model by allowing multiple parents, forming a DAG instead of a tree. These navigational database models came in to vogue in the 1960s and continued their dominance until performance gains made relational databases usable in the 1980s. @@ -118,7 +118,7 @@ Dagoba = {} // the namespace We'll use an object as our namespace. An object in JavaScript is mostly just an unordered set of key/value pairs. We only have four basic data structures to choose from in JavaScript, so we'll be using this one a lot. (A fun question to ask people at parties is "What are the four basic data structures in JavaScript?") -Now we need some graphs. We can build these using a classic OOP pattern, but JavaScript offers us prototypal inheritance, which means we can build up a prototype object --- we'll call it `Dagoba.G` --- and then instantiate copies of that using a factory function. An advantage of this approach is that we can return different types of objects from the factory, instead of binding the creation process to a single class constructor. So we get some extra flexibility for free. +Now we need some graphs. We can build these using a classic OOP pattern, but JavaScript offers us prototypal inheritance, which means we can build up a prototype object—we'll call it `Dagoba.G`—and then instantiate copies of that using a factory function. An advantage of this approach is that we can return different types of objects from the factory, instead of binding the creation process to a single class constructor. So we get some extra flexibility for free. ```javascript Dagoba.G = {} // the prototype @@ -145,7 +145,7 @@ We'll accept two optional arguments: a list of vertices and a list of edges. Jav [^graphbuilding]: The `Array.isArray` checks here are to distinguish our two different use cases, but in general we won't be doing many of the validations one would expect of production code, in order to focus on the architecture instead of the trash bins. -Then we create a new object that has all of our prototype's strengths and none of its weaknesses. We build a brand new array (one of the other basic JS data structures) for our edges, another for the vertices, a new object called `vertexIndex` and an ID counter --- more on those latter two later. (Think: Why can't we just put these in the prototype?) +Then we create a new object that has all of our prototype's strengths and none of its weaknesses. We build a brand new array (one of the other basic JS data structures) for our edges, another for the vertices, a new object called `vertexIndex` and an ID counter—more on those latter two later. (Think: Why can't we just put these in the prototype?) Then we call `addVertices` and `addEdges` from inside our factory, so let's define those now. @@ -154,7 +154,7 @@ Dagoba.G.addVertices = function(vs) { vs.forEach(this.addVertex.bind(this)) } Dagoba.G.addEdges = function(es) { es.forEach(this.addEdge .bind(this)) } ``` -Okay, that was too easy --- we're just passing off the work to `addVertex` and `addEdge`. We should define those now too. +Okay, that was too easy—we're just passing off the work to `addVertex` and `addEdge`. We should define those now too. ```javascript Dagoba.G.addVertex = function(vertex) { // accepts a vertex-like object @@ -178,7 +178,7 @@ In a traditional object-oriented system we would expect to find a vertex class, If we create some `Dagoba.Vertex` instance inside the `addVertex` function, our internal data will never be shared with the host application. If we accept a `Dagoba.Vertex` instance as the argument to our `addVertex` function, the host application could retain a pointer to that vertex object and manipulate it at runtime, breaking our invariants. -So if we create a vertex instance object, we're forced to decide up front whether we will always copy the provided data into a new object --- potentially doubling our space usage --- or allow the host application unfettered access to the database objects. There's a tension here between performance and protection, and the right balance depends on your specific use case. +So if we create a vertex instance object, we're forced to decide up front whether we will always copy the provided data into a new object—potentially doubling our space usage—or allow the host application unfettered access to the database objects. There's a tension here between performance and protection, and the right balance depends on your specific use case. Duck typing on the vertex's properties allows us to make that decision at run time, by either deep copying[^deepcopying] the incoming data or using it directly as a vertex[^vertexdecision]. We don't always want to put the responsibility for balancing safety and performance in the hands of the user, but because these two sets of use cases diverge so widely the extra flexibility is important. @@ -243,7 +243,7 @@ Dagoba.query = function(graph) { // factory Now's a good time to introduce some friends. -A *program* is a series of *steps*. Each step is like a pipe in a pipeline --- a piece of data comes in one end, is transformed in some fashion, and goes out the other end. Our pipeline doesn't quite work like that, but it's a good first approximation. +A *program* is a series of *steps*. Each step is like a pipe in a pipeline—a piece of data comes in one end, is transformed in some fashion, and goes out the other end. Our pipeline doesn't quite work like that, but it's a good first approximation. Each step in our program can have *state*, and `query.state` is a list of per-step states that index correlates with the list of steps in `query.program`. @@ -265,7 +265,7 @@ Dagoba.Q.add = function(pipetype, args) { // add a new step to the query Each step is a composite entity, combining the pipetype function with the arguments to apply to that function. We could combine the two into a partially applied function at this stage, instead of using a tuple [^tupleadt], but then we'd lose some introspective power that will prove helpful later. -[^tupleadt]: A tuple is another abstract data structure --- one that is more constrained than a list. In particular a tuple has a fixed size: in this case we're using a 2-tuple (also known as a "pair" in the technical jargon of data structure researchers). Using the term for the most constrained abstract data structure required is a nicety for future implementors. +[^tupleadt]: A tuple is another abstract data structure—one that is more constrained than a list. In particular a tuple has a fixed size: in this case we're using a 2-tuple (also known as a "pair" in the technical jargon of data structure researchers). Using the term for the most constrained abstract data structure required is a nicety for future implementors. We'll use a small set of query initializers that generate a new query from a graph. Here's one that starts most of our examples: the `v` method. It builds a new query, then uses our `add` helper to populate the initial query program. This makes use of the `vertex` pipetype, which we'll look at soon. @@ -281,11 +281,11 @@ Note that `[].slice.call(arguments)` is JS parlance for "please pass me an array ## The Problem with Being Eager -Before we look at the pipetypes themselves we're going to take a diversion into the exciting world of execution strategy. There are two main schools of thought: the Call By Value clan, also known as eager beavers, are strict in their insistence that all arguments be evaluated before the function is applied. Their opposing faction, the Call By Needians, are content to procrastinate until the last possible moment before doing anything, and even then do as little as possible --- they are, in a word, lazy. +Before we look at the pipetypes themselves we're going to take a diversion into the exciting world of execution strategy. There are two main schools of thought: the Call By Value clan, also known as eager beavers, are strict in their insistence that all arguments be evaluated before the function is applied. Their opposing faction, the Call By Needians, are content to procrastinate until the last possible moment before doing anything, and even then do as little as possible—they are, in a word, lazy. JavaScript, being a strict language, will process each of our steps as they are called. We would then expect the evaluation of `g.v('Thor').out().in()` to first find the Thor vertex, then find all vertices connected to it by outgoing edges, and from each of those vertices finally return all vertices they are connected to by inbound edges. -In a non-strict language we would get the same result --- the execution strategy doesn't make much difference here. But what if we added a few additional calls? Given how well-connected Thor is, our `g.v('Thor').out().out().out().in().in().in()` query may produce many results --- in fact, because we're not limiting our vertex list to unique results, it may produce many more results than we have vertices in our total graph. +In a non-strict language we would get the same result—the execution strategy doesn't make much difference here. But what if we added a few additional calls? Given how well-connected Thor is, our `g.v('Thor').out().out().out().in().in().in()` query may produce many results—in fact, because we're not limiting our vertex list to unique results, it may produce many more results than we have vertices in our total graph. We're probably only interested in getting a few unique results out, so we'll change the query a bit: `g.v('Thor').out().out().out().in().in().in().unique().take(10)`. Now our query produces at most 10 results. What happens if we evaluate this eagerly, though? We're still going to have to build up septillions of results before returning only the first 10. @@ -390,13 +390,13 @@ Note that we're directly mutating the state argument here, and not passing it ba [^garbage]: Very short lived garbage though, which is the second best kind. -We would still need to find a way to deal with the mutations, though, as the call site maintains a reference to the original variable. What if we had some way to determine whether a particular reference is "unique" --- that it is the only reference to that object? +We would still need to find a way to deal with the mutations, though, as the call site maintains a reference to the original variable. What if we had some way to determine whether a particular reference is "unique"—that it is the only reference to that object? If we know a reference is unique then we can get the benefits of immutability while avoiding expensive copy-on-write schemes or complicated persistent data structures. With only one reference we can't tell whether the object has been mutated or a new object has been returned with the changes we requested: "observed immutability" is maintained [^obsimmutability]. [^obsimmutability]: Two references to the same mutable data structure act like a pair of walkie-talkies, allowing whoever holds them to communicate directly. Those walkie-talkies can be passed around from function to function, and cloned to create a whole lot of walkie-talkies. This completely subverts the natural communication channels your code already possesses. In a system with no concurrency you can sometimes get away with it, but introduce multithreading or asynchronous behavior and all that walkie-talkie squawking can become a real drag. -There are a couple of common ways of determining this: in a statically typed system we might make use of uniqueness types [^uniquenesstypes] to guarantee at compile time that each object has only one reference. If we had a reference counter [^referencecounter] --- even just a cheap two-bit sticky counter --- we could know at runtime that an object only has one reference and use that knowledge to our advantage. +There are a couple of common ways of determining this: in a statically typed system we might make use of uniqueness types [^uniquenesstypes] to guarantee at compile time that each object has only one reference. If we had a reference counter [^referencecounter]—even just a cheap two-bit sticky counter—we could know at runtime that an object only has one reference and use that knowledge to our advantage. [^uniquenesstypes]: Uniqueness types were dusted off in the Clean language, and have a non-linear relationship with linear types, which are themselves a subtype of substructural types. @@ -489,7 +489,7 @@ Note that if the property doesn't exist we return `false` instead of the gremlin #### Unique -If we want to collect all Thor's grandparents' grandchildren --- his cousins, his siblings, and himself --- we could do a query like this: `g.v('Thor').in().in().out().out().run()`. That would give us many duplicates, however. In fact there would be at least four copies of Thor himself. (Can you think of a time when there might be more?) +If we want to collect all Thor's grandparents' grandchildren—his cousins, his siblings, and himself—we could do a query like this: `g.v('Thor').in().in().out().out().run()`. That would give us many duplicates, however. In fact there would be at least four copies of Thor himself. (Can you think of a time when there might be more?) To resolve this we introduce a new pipetype called 'unique'. Our new query \linebreak `g.v('Thor').in().in().out().out().unique().run()` produces output in one-to-one correspondence with the grandchildren. @@ -645,7 +645,7 @@ Dagoba.addPipetype('merge', function(graph, args, gremlin, state) { }) ``` -We map over each argument, looking for it in the gremlin's list of labeled vertices. If we find it, we clone the gremlin to that vertex. Note that only gremlins that make it to this pipe are included in the merge --- if Thor's mother's parents aren't in the graph, she won't be in the result set. +We map over each argument, looking for it in the gremlin's list of labeled vertices. If we find it, we clone the gremlin to that vertex. Note that only gremlins that make it to this pipe are included in the merge—if Thor's mother's parents aren't in the graph, she won't be in the result set. #### Except @@ -771,7 +771,7 @@ Dagoba.G.findVertexById = function(vertex_id) { } ``` -Note the use of `vertexIndex` here. Without that index we'd have to go through each vertex in our list one at a time to decide if it matched the ID --- turning a constant time operation into a linear time one, and any $O(n)$ operations that directly rely on it into $O(n^2)$ operations. +Note the use of `vertexIndex` here. Without that index we'd have to go through each vertex in our list one at a time to decide if it matched the ID—turning a constant time operation into a linear time one, and any $O(n)$ operations that directly rely on it into $O(n^2)$ operations. ```javascript Dagoba.G.searchVertices = function(filter) { // match on filter's properties @@ -852,7 +852,7 @@ An index of the last 'done' step that starts behind the first step: var done = -1 ``` -We need a place to store the most recent step's output, which might be a gremlin --- or it might be nothing --- so we'll call it `maybe_gremlin`: +We need a place to store the most recent step's output, which might be a gremlin—or it might be nothing—so we'll call it `maybe_gremlin`: ```javascript var maybe_gremlin = false @@ -1020,7 +1020,7 @@ Now we can add query transformers to our system. A query transformer is a functi [^paramdomain]: Note that we're keeping the domain of the priority parameter open, so it can be an integer, a rational, a negative number, or even things like Infinity or NaN. -We'll assume there won't be an enormous number of transformer additions, and walk the list linearly to add a new one. We'll leave a note in case this assumption turns out to be false --- a binary search is much more time-optimal for long lists, but adds a little complexity and doesn't really speed up short lists. +We'll assume there won't be an enormous number of transformer additions, and walk the list linearly to add a new one. We'll leave a note in case this assumption turns out to be false—a binary search is much more time-optimal for long lists, but adds a little complexity and doesn't really speed up short lists. To run these transformers we're going to inject a single line of code in to the top of our interpreter: @@ -1144,7 +1144,7 @@ All production graph databases share a particular performance characteristic: gr [^ponyexpress]: Though only in operation for 18 months due to the arrival of the transcontinental telegraph and the outbreak of the American Civil War, the Pony Express is still remembered today for delivering mail coast to coast in just ten days. -To alleviate this dismal performance most databases index over oft-queried fields, which turns an $O(n)$ search into an $O(log n)$ search. This gives considerably better search performance, but at the cost of some write performance and a lot of space --- indices can easily double the size of a database. Careful balancing of the space/time tradeoffs of indices is part of the perpetual tuning process for most databases. +To alleviate this dismal performance most databases index over oft-queried fields, which turns an $O(n)$ search into an $O(log n)$ search. This gives considerably better search performance, but at the cost of some write performance and a lot of space—indices can easily double the size of a database. Careful balancing of the space/time tradeoffs of indices is part of the perpetual tuning process for most databases. Graph databases sidestep this issue by making direct connections between vertices and edges, so graph traversals are just pointer jumps; no need to scan through every item, no need for indices, no extra work at all. Now finding your friends has the same price regardless of the total number of people in the graph, with no additional space cost or write time cost. One downside to this approach is that the pointers work best when the whole graph is in memory on the same machine. Effectively sharding a graph database across multiple machines is still an active area of research [^graphdbsharding]. @@ -1177,7 +1177,7 @@ Dagoba.G.findOutEdges = function(vertex) { return vertex._out } Run these yourself to experience the graph database difference [^jslistfilter]. -[^jslistfilter]: In modern JavaScript engines filtering a list is quite fast --- for small graphs the naive version can actually be faster than the index-free version due to the underlying data structures and the way the code is JIT compiled. Try it with different sizes of graphs to see how the two approaches scale. +[^jslistfilter]: In modern JavaScript engines filtering a list is quite fast—for small graphs the naive version can actually be faster than the index-free version due to the underlying data structures and the way the code is JIT compiled. Try it with different sizes of graphs to see how the two approaches scale. ## Serialization @@ -1221,7 +1221,7 @@ We could merge the two replacer functions into a single function, and use that n ## Persistence -Persistence is usually one of the trickier parts of a database: disks are relatively safe, but dreadfully slow. Batching writes, making them atomic, journaling --- these are difficult to make both fast and correct. +Persistence is usually one of the trickier parts of a database: disks are relatively safe, but dreadfully slow. Batching writes, making them atomic, journaling—these are difficult to make both fast and correct. Fortunately, we're building an *in-memory* database, so we don't have to worry about any of that! We may, though, occasionally want to save a copy of the database locally for fast restart on page load. We can use the serializer we just built to do exactly that. First let's wrap it in a helper function: @@ -1240,7 +1240,7 @@ Dagoba.fromString = function(str) { // another graph constructor } ``` -Now we'll use those in our persistence functions. The `toString` function is hiding --- can you spot it? +Now we'll use those in our persistence functions. The `toString` function is hiding—can you spot it? ```javascript Dagoba.persist = function(graph, name) { @@ -1285,7 +1285,7 @@ g.v('Thor').out().as('parent') .run() ``` -This is pretty clumsy, and doesn't scale well --- what if we wanted six layers of ancestors? Or to look through an arbitrary number of ancestors until we found what we wanted? +This is pretty clumsy, and doesn't scale well—what if we wanted six layers of ancestors? Or to look through an arbitrary number of ancestors until we found what we wanted? It'd be nice if we could say something like this instead: @@ -1293,7 +1293,7 @@ It'd be nice if we could say something like this instead: g.v('Thor').out().all().times(3).run() ``` -What we'd like to get out of this is something like the query above --- maybe: +What we'd like to get out of this is something like the query above—maybe: ```javascript g.v('Thor').out().as('a') @@ -1309,13 +1309,13 @@ We could run the `times` transformer first, to produce \linebreak\newline `g.v('Ymir').in().in().in().in().filter({survives: true})`, and manually collect the results ourselves, but that's pretty awful. +There's still the issue of searching through an unbounded number of ancestors—for example, how do we find out which of Ymir's descendants are scheduled to survive Ragnarök? We could make individual queries like `g.v('Ymir').in().filter({survives: true})` and \newline `g.v('Ymir').in().in().in().in().filter({survives: true})`, and manually collect the results ourselves, but that's pretty awful. We'd like to use an adverb like this: @@ -1328,11 +1328,11 @@ which would work like `all`+`times` but without enforcing a limit. We may want t ## Wrapping Up -So what have we learned? Graph databases are great for storing interconnected [^sortainterconnected] data that you plan to query via graph traversals. Adding non-strict semantics allows for a fluent interface over queries you could never express in an eager system for performance reasons, and allows you to cross async boundaries. Time makes things complicated, and time from multiple perspectives (i.e., concurrency) makes things very complicated, so whenever we can avoid introducing a temporal dependency (e.g., state, observable effects, etc.) we make reasoning about our system easier. Building in a simple, decoupled and painfully unoptimized style leaves the door open for global optimizations later on, and using a driver loop allows for orthogonal optimizations --- each without introducing the brittleness and complexity that is the hallmark of most optimization techniques. +So what have we learned? Graph databases are great for storing interconnected [^sortainterconnected] data that you plan to query via graph traversals. Adding non-strict semantics allows for a fluent interface over queries you could never express in an eager system for performance reasons, and allows you to cross async boundaries. Time makes things complicated, and time from multiple perspectives (i.e., concurrency) makes things very complicated, so whenever we can avoid introducing a temporal dependency (e.g., state, observable effects, etc.) we make reasoning about our system easier. Building in a simple, decoupled and painfully unoptimized style leaves the door open for global optimizations later on, and using a driver loop allows for orthogonal optimizations—each without introducing the brittleness and complexity that is the hallmark of most optimization techniques. That last point can't be overstated: keep it simple. Eschew optimization in favor of simplicity. Work hard to achieve simplicity by finding the right model. Explore many possibilities. The chapters in this book provide ample evidence that highly non-trivial applications can have a small, tight kernel. Once you find that kernel for the application you are building, fight to keep complexity from polluting it. Build hooks for attaching additional functionality, and maintain your abstraction barriers at all costs. Using these techniques well is not easy, but they can give you leverage over otherwise intractable problems. -[^sortainterconnected]: Not *too* interconnected, though --- you'd like the number of edges to grow in direct proportion to the number of vertices. In other words, the average number of edges connected to a vertex shouldn't vary with the size of the graph. Most systems we'd consider putting in a graph database already have this property: if Loki had 100,000 additional grandchildren the degree of the Thor vertex wouldn't increase. +[^sortainterconnected]: Not *too* interconnected, though—you'd like the number of edges to grow in direct proportion to the number of vertices. In other words, the average number of edges connected to a vertex shouldn't vary with the size of the graph. Most systems we'd consider putting in a graph database already have this property: if Loki had 100,000 additional grandchildren the degree of the Thor vertex wouldn't increase. ### Acknowledgements diff --git a/flow-shop/flow-shop.markdown b/flow-shop/flow-shop.markdown index 87a23e5ff..d81cf9cc7 100644 --- a/flow-shop/flow-shop.markdown +++ b/flow-shop/flow-shop.markdown @@ -73,7 +73,7 @@ MAX_LNS_NEIGHBOURHOODS = 1000 # Maximum number of neighbours to explore in LNS There are two settings that should be explained further. The `TIME_INCREMENT` setting will be used as part of the dynamic strategy selection, and the `MAX_LNS_NEIGHBOURHOODS` setting will be used as part of the neighbourhood selection strategy. Both are described in more detail below. -These settings could be exposed to the user as command line parameters, but at this stage we instead provide the input data as parameters to the program. The input problem --- a problem from the Taillard benchmark set --- is assumed to be in a standard format for flow shop scheduling. The following code is used as the `__main__` method for the solver file, and calls the appropriate functions based on the number of parameters input to the program: +These settings could be exposed to the user as command line parameters, but at this stage we instead provide the input data as parameters to the program. The input problem—a problem from the Taillard benchmark set—is assumed to be in a standard format for flow shop scheduling. The following code is used as the `__main__` method for the solver file, and calls the appropriate functions based on the number of parameters input to the program: ```python if __name__ == '__main__': @@ -113,7 +113,7 @@ def solve(data): strat_usage = {strategy: 0 for strategy in STRATEGIES} ``` -One appealing feature of the flow shop scheduling problem is that *every* permutation is a valid solution, and at least one will have the optimal makespan (though many will have horrible makespans). Thankfully, this allows us to forgo checking that we stay within the space of feasible solutions when going from one permutation to another --- everything is feasible! +One appealing feature of the flow shop scheduling problem is that *every* permutation is a valid solution, and at least one will have the optimal makespan (though many will have horrible makespans). Thankfully, this allows us to forgo checking that we stay within the space of feasible solutions when going from one permutation to another—everything is feasible! However, to start a local search in the space of permutations, we must have an initial permutation. To keep things simple, we seed our local search by shuffling the list of jobs randomly: @@ -420,7 +420,7 @@ Finally, we construct the neighbourhood by considering every permutation of the return candidates ``` -The final neighbourhood that we consider is commonly referred to as *Large Neighbourhood Search* (LNS). Intuitively, LNS works by considering small subsets of the current permutation in isolation --- locating the best permutation of the subset of jobs gives us a single candidate for the LNS neighbourhood. By repeating this process for several (or all) subsets of a particular size, we can increase the number of candidates in the neighbourhood. We limit the number that are considered through the `MAX_LNS_NEIGHBOURHOODS` parameter, as the number of neighbours can grow quite quickly. The first step in the LNS computation is to compute the random list of job sets that we will consider swapping using the `combinations` function of the `itertools` package: +The final neighbourhood that we consider is commonly referred to as *Large Neighbourhood Search* (LNS). Intuitively, LNS works by considering small subsets of the current permutation in isolation—locating the best permutation of the subset of jobs gives us a single candidate for the LNS neighbourhood. By repeating this process for several (or all) subsets of a particular size, we can increase the number of candidates in the neighbourhood. We limit the number that are considered through the `MAX_LNS_NEIGHBOURHOODS` parameter, as the number of neighbours can grow quite quickly. The first step in the LNS computation is to compute the random list of job sets that we will consider swapping using the `combinations` function of the `itertools` package: ```python def neighbours_LNS(data, perm, size = 2): diff --git a/functionalDB/functionalDB.markdown b/functionalDB/functionalDB.markdown index e422ff92b..2dec2379e 100644 --- a/functionalDB/functionalDB.markdown +++ b/functionalDB/functionalDB.markdown @@ -377,7 +377,7 @@ The function `evolution-of` does exactly that. It returns a sequence of pairs, e So far, our discussion has focused on the structure of our data: what the core components are and how they are aggregated together. It's time to explore the dynamics of our system: how data is changed over time through the add--update--remove _data lifecycle_. -As we've already discussed, data in an archaeologist's world never actually changes. Once it is created, it exists forever and can only be hidden from the world by data in a newer layer. The term "hidden" is crucial here. Older data does not "disappear" --- it is buried, and can be revealed again by exposing an older layer. Conversely, updating data means obscuring the old by adding a new layer on top of it with something else. We can thus "delete" data by adding a layer of "nothing" on top of it. +As we've already discussed, data in an archaeologist's world never actually changes. Once it is created, it exists forever and can only be hidden from the world by data in a newer layer. The term "hidden" is crucial here. Older data does not "disappear"—it is buried, and can be revealed again by exposing an older layer. Conversely, updating data means obscuring the old by adding a new layer on top of it with something else. We can thus "delete" data by adding a layer of "nothing" on top of it. This means that when we talk about data lifecycle, we are really talking about adding layers to our data over time. @@ -1258,7 +1258,7 @@ Finally, we remove all of the result clauses that are "empty" (i.e., their last \end{table} -We are now ready to report the results. The result clause structure is unwieldy for this purpose, so we will convert it into an an index-like structure (map of maps) --- with a significant twist. +We are now ready to report the results. The result clause structure is unwieldy for this purpose, so we will convert it into an an index-like structure (map of maps)—with a significant twist. To understand the twist, we must first introduce the idea of a _binding pair_, which is a pair that matches a variable name to its value. The variable name is the one used at the predicate clauses, and the value is the value found in the result clauses. diff --git a/image-filters/image-filters.markdown b/image-filters/image-filters.markdown index b89ec2b9f..749ae70b2 100644 --- a/image-filters/image-filters.markdown +++ b/image-filters/image-filters.markdown @@ -6,8 +6,8 @@ _Cate left the tech industry and spent a year finding her way back whilst buildi ## A Brilliant Idea (That Wasn’t All That Brilliant) When I was traveling in China I often saw series of four paintings showing the same -place in different seasons. Color — the cool whites of winter, pale hues of -spring, lush greens of summer, and reds and yellows of fall — is what +place in different seasons. Color—the cool whites of winter, pale hues of +spring, lush greens of summer, and reds and yellows of fall—is what visually differentiates the seasons. Around 2011, I had what I thought was a brilliant idea: I wanted to be able to visualize a photo series as a series of colors. I @@ -48,7 +48,7 @@ University, and later work, filled up my time with other people’s ideas and priorities. Part of finishing this project was learning how to carve out time to make progress on my own ideas; I required about four hours of good mental time a week. A tool that allowed me to -move faster was therefore really helpful, even necessary --- although it came with +move faster was therefore really helpful, even necessary—although it came with its own set of problems, especially around writing tests. I felt that thorough @@ -91,8 +91,8 @@ a matter of seconds. However, a long long time ago (in digital terms), it was a process that took weeks. In the old days, we would take the picture, then when we had used a whole roll of film, we would -take it in to be developed (often at the pharmacy). We'd pick up the developed pictures some days later --- -and discover that there was something wrong with many of them. +take it in to be developed (often at the pharmacy). We'd pick up the developed pictures some days +later—and discover that there was something wrong with many of them. Hand not steady enough? Random person or thing that we didn’t notice at the time? Overexposed? Underexposed? Of course by then it was too late to remedy the problem. diff --git a/interpreter/interpreter.markdown b/interpreter/interpreter.markdown index ad4f1eabf..f4960d083 100644 --- a/interpreter/interpreter.markdown +++ b/interpreter/interpreter.markdown @@ -7,7 +7,7 @@ _(This chapter is also available in [Simplified Chinese](http://qingyunha.github ## Introduction -Byterun is a Python interpreter implemented in Python. Through my work on Byterun, I was surprised and delighted to discover that the fundamental structure of the Python interpreter fits easily into the 500-line size restriction. This chapter will walk through the structure of the interpreter and give you enough context to explore it further. The goal is not to explain everything there is to know about interpreters --- like so many interesting areas of programming and computer science, you could devote years to developing a deep understanding of the topic. +Byterun is a Python interpreter implemented in Python. Through my work on Byterun, I was surprised and delighted to discover that the fundamental structure of the Python interpreter fits easily into the 500-line size restriction. This chapter will walk through the structure of the interpreter and give you enough context to explore it further. The goal is not to explain everything there is to know about interpreters—like so many interesting areas of programming and computer science, you could devote years to developing a deep understanding of the topic. Byterun was written by Ned Batchelder and myself, building on the work of Paul Swartz. Its structure is similar to the primary implementation of Python, CPython, so understanding Byterun will help you understand interpreters in general and the CPython interpreter in particular. (If you don't know which Python you're using, it's probably CPython.) Despite its short length, Byterun is capable of running most simple Python programs. @@ -23,7 +23,7 @@ You may be surprised to hear that compiling is a step in executing Python code a Byterun is a Python interpreter written in Python. This may strike you as odd, but it's no more odd than writing a C compiler in C. (Indeed, the widely used C compiler gcc is written in C.) You could write a Python interpreter in almost any language. -Writing a Python interpreter in Python has both advantages and disadvantages. The biggest disadvantage is speed: executing code via Byterun is much slower than executing it in CPython, where the interpreter is written in C and carefully optimized. However, Byterun was designed originally as a learning exercise, so speed is not important to us. The biggest advantage to using Python is that we can more easily implement *just* the interpreter, and not the rest of the Python run-time, particularly the object system. For example, Byterun can fall back to "real" Python when it needs to create a class. Another advantage is that Byterun is easy to understand, partly because it's written in a high-level language (Python!) that many people find easy to read. (We also exclude interpreter optimizations in Byterun --- once again favoring clarity and simplicity over speed.) +Writing a Python interpreter in Python has both advantages and disadvantages. The biggest disadvantage is speed: executing code via Byterun is much slower than executing it in CPython, where the interpreter is written in C and carefully optimized. However, Byterun was designed originally as a learning exercise, so speed is not important to us. The biggest advantage to using Python is that we can more easily implement *just* the interpreter, and not the rest of the Python run-time, particularly the object system. For example, Byterun can fall back to "real" Python when it needs to create a class. Another advantage is that Byterun is easy to understand, partly because it's written in a high-level language (Python!) that many people find easy to read. (We also exclude interpreter optimizations in Byterun—once again favoring clarity and simplicity over speed.) ## Building an Interpreter @@ -31,7 +31,7 @@ Before we start to look at the code of Byterun, we need some higher-level contex The Python interpreter is a _virtual machine_, meaning that it is software that emulates a physical computer. This particular virtual machine is a stack machine: it manipulates several stacks to perform its operations (as contrasted with a register machine, which writes to and reads from particular memory locations). -The Python interpreter is a _bytecode interpreter_: its input is instruction sets called _bytecode_. When you write Python, the lexer, parser, and compiler generate code objects for the interpreter to operate on. Each code object contains a set of instructions to be executed --- that's the bytecode --- plus other information that the interpreter will need. Bytecode is an _intermediate representation_ of Python code: it expresses the source code that you wrote in a way the interpreter can understand. It's analogous to the way that assembly language serves as an intermediate representation between C code and a piece of hardware. +The Python interpreter is a _bytecode interpreter_: its input is instruction sets called _bytecode_. When you write Python, the lexer, parser, and compiler generate code objects for the interpreter to operate on. Each code object contains a set of instructions to be executed—that's the bytecode—plus other information that the interpreter will need. Bytecode is an _intermediate representation_ of Python code: it expresses the source code that you wrote in a way the interpreter can understand. It's analogous to the way that assembly language serves as an intermediate representation between C code and a piece of hardware. ### A Tiny Interpreter @@ -68,7 +68,7 @@ The `LOAD_VALUE` instruction tells the interpreter to push a number on to the st Why not just put the numbers directly in the instructions? Imagine if we were adding strings together instead of numbers. We wouldn't want to have the strings stuffed in with the instructions, since they could be arbitrarily large. This design also means we can have just one copy of each object that we need, so for example to add `7 + 7`, `"numbers"` could be just `[7]`. -You may be wondering why instructions other than `ADD_TWO_VALUES` were needed at all. Indeed, for the simple case of adding two numbers, the example is a little contrived. However, this instruction is a building block for more complex programs. For example, with just the instructions we've defined so far, we can already add together three values --- or any number of values --- given the right set of these instructions. The stack provides a clean way to keep track of the state of the interpreter, and it will support more complexity as we go along. +You may be wondering why instructions other than `ADD_TWO_VALUES` were needed at all. Indeed, for the simple case of adding two numbers, the example is a little contrived. However, this instruction is a building block for more complex programs. For example, with just the instructions we've defined so far, we can already add together three values—or any number of values—given the right set of these instructions. The stack provides a clean way to keep track of the state of the interpreter, and it will support more complexity as we go along. Now let's start to write the interpreter itself. The interpreter object has a stack, which we'll represent with a list. The object also has a method describing how to execute each instruction. For example, for `LOAD_VALUE`, the interpreter will push the value onto the stack. @@ -163,7 +163,7 @@ Next let's add variables to our interpreter. Variables require an instruction fo Our new implementation is below. To keep track of what names are bound to what values, we'll add an `environment` dictionary to the `__init__` method. We'll also add `STORE_NAME` and `LOAD_NAME`. These methods first look up the variable name in question and then use the dictionary to store or retrieve its value. -The arguments to an instruction can now mean two different things: They can either be an index into the "numbers" list, or they can be an index into the "names" list. The interpreter knows which it should be by checking what instruction it's executing. We'll break out this logic --- and the mapping of instructions to what their arguments mean --- into a separate method. +The arguments to an instruction can now mean two different things: They can either be an index into the "numbers" list, or they can be an index into the "names" list. The interpreter knows which it should be by checking what instruction it's executing. We'll break out this logic—and the mapping of instructions to what their arguments mean—into a separate method. ```python class Interpreter: @@ -238,7 +238,7 @@ At this point, we'll abandon our toy instruction sets and switch to real Python ... ``` -Python exposes a boatload of its internals at run time, and we can access them right from the REPL. For the function object `cond`, `cond.__code__` is the code object associated it, and `cond.__code__.co_code` is the bytecode. There's almost never a good reason to use these attributes directly when you're writing Python code, but they do allow us to get up to all sorts of mischief --- and to look at the internals in order to understand them. +Python exposes a boatload of its internals at run time, and we can access them right from the REPL. For the function object `cond`, `cond.__code__` is the code object associated it, and `cond.__code__.co_code` is the bytecode. There's almost never a good reason to use these attributes directly when you're writing Python code, but they do allow us to get up to all sorts of mischief—and to look at the internals in order to understand them. ```python >>> cond.__code__.co_code # the bytecode as raw bytes @@ -249,7 +249,7 @@ b'd\x01\x00}\x00\x00|\x00\x00d\x02\x00k\x00\x00r\x16\x00d\x03\x00Sd\x04\x00Sd\x0 100, 4, 0, 83, 100, 0, 0, 83] ``` -When we just print the bytecode, it looks unintelligible --- all we can tell is that it's a series of bytes. Luckily, there's a powerful tool we can use to understand it: the `dis` module in the Python standard library. +When we just print the bytecode, it looks unintelligible—all we can tell is that it's a series of bytes. Luckily, there's a powerful tool we can use to understand it: the `dis` module in the Python standard library. `dis` is a bytecode disassembler. A disassembler takes low-level code that is written for machines, like assembly code or bytecode, and prints it in a human-readable way. When we run `dis.dis`, it outputs an explanation of the bytecode it has passed. @@ -283,7 +283,7 @@ Consider the first few bytes of this bytecode: [100, 1, 0, 125, 0, 0]. These six 'STORE_FAST' ``` -The second and third bytes --- 1, 0 --- are arguments to `LOAD_CONST`, while the fifth and sixth bytes --- 0, 0 --- are arguments to `STORE_FAST`. Just like in our toy example, `LOAD_CONST` needs to know where to find its constant to load, and `STORE_FAST` needs to find the name to store. (Python's `LOAD_CONST` is the same as our toy interpreter's `LOAD_VALUE`, and `LOAD_FAST` is the same as `LOAD_NAME`.) So these six bytes represent the first line of code, `x = 3`. (Why use two bytes for each argument? If Python used just one byte to locate constants and names instead of two, you could only have 256 names/constants associated with a single code object. Using two bytes, you can have up to 256 squared, or 65,536.) +The second and third bytes—1, 0—are arguments to `LOAD_CONST`, while the fifth and sixth bytes—0, 0—are arguments to `STORE_FAST`. Just like in our toy example, `LOAD_CONST` needs to know where to find its constant to load, and `STORE_FAST` needs to find the name to store. (Python's `LOAD_CONST` is the same as our toy interpreter's `LOAD_VALUE`, and `LOAD_FAST` is the same as `LOAD_NAME`.) So these six bytes represent the first line of code, `x = 3`. (Why use two bytes for each argument? If Python used just one byte to locate constants and names instead of two, you could only have 256 names/constants associated with a single code object. Using two bytes, you can have up to 256 squared, or 65,536.) ### Conditionals and Loops @@ -308,11 +308,11 @@ So far, the interpreter has executed code simply by stepping through the instruc 29 RETURN_VALUE ``` -The conditional `if x < 5` on line 3 of the code is compiled into four instructions: `LOAD_FAST`, `LOAD_CONST`, `COMPARE_OP`, and `POP_JUMP_IF_FALSE`. `x < 5` generates code to load `x`, load 5, and compare the two values. The instruction `POP_JUMP_IF_FALSE` is responsible for implementing the `if`. This instruction will pop the top value off the interpreter's stack. If the value is true, then nothing happens. (The value can be "truthy" --- it doesn't have to be the literal `True` object.) If the value is false, then the interpreter will jump to another instruction. +The conditional `if x < 5` on line 3 of the code is compiled into four instructions: `LOAD_FAST`, `LOAD_CONST`, `COMPARE_OP`, and `POP_JUMP_IF_FALSE`. `x < 5` generates code to load `x`, load 5, and compare the two values. The instruction `POP_JUMP_IF_FALSE` is responsible for implementing the `if`. This instruction will pop the top value off the interpreter's stack. If the value is true, then nothing happens. (The value can be "truthy"—it doesn't have to be the literal `True` object.) If the value is false, then the interpreter will jump to another instruction. The instruction to land on is called the jump target, and it's provided as the argument to the `POP_JUMP` instruction. Here, the jump target is 22. The instruction at index 22 is `LOAD_CONST` on line 6. (`dis` marks jump targets with `>>`.) If the result of `x < 5` is False, then the interpreter will jump straight to line 6 (`return "no"`), skipping line 4 (`return "yes"`). Thus, the interpreter uses jump instructions to selectively skip over parts of the instruction set. -Python loops also rely on jumping. In the bytecode below, notice that the line `while x < 5` generates almost identical bytecode to `if x < 10`. In both cases, the comparison is calculated and then `POP_JUMP_IF_FALSE` controls which instruction is executed next. At the end of line 4 --- the end of the loop's body --- the instruction `JUMP_ABSOLUTE` always sends the interpreter back to instruction 9 at the top of the loop. When x < 5 becomes false, then `POP_JUMP_IF_FALSE` jumps the interpreter past the end of the loop, to instruction 34. +Python loops also rely on jumping. In the bytecode below, notice that the line `while x < 5` generates almost identical bytecode to `if x < 10`. In both cases, the comparison is calculated and then `POP_JUMP_IF_FALSE` controls which instruction is executed next. At the end of line 4—the end of the loop's body—the instruction `JUMP_ABSOLUTE` always sends the interpreter back to instruction 9 at the top of the loop. When x < 5 becomes false, then `POP_JUMP_IF_FALSE` jumps the interpreter past the end of the loop, to instruction 34. ```python >>> def loop(): @@ -354,9 +354,9 @@ I encourage you to try running `dis.dis` on functions you write. Some interestin So far, we've learned that the Python virtual machine is a stack machine. It steps and jumps through instructions, pushing and popping values on and off a stack. There are still some gaps in our mental model, though. In the examples above, the last instruction is `RETURN_VALUE`, which corresponds to the `return` statement in the code. But where does the instruction return to? -To answer this question, we must add one additional layer of complexity: the frame. A frame is a collection of information and context for a chunk of code. Frames are created and destroyed on the fly as your Python code executes. There's one frame corresponding to each *call* of a function --- so while each frame has one code object associated with it, a code object can have many frames. If you had a function that called itself recursively ten times, you'd have eleven frames --- one for each level of recursion and one for the module you started from. In general, there's a frame for each scope in a Python program. For example, each module, each function call, and each class definition has a frame. +To answer this question, we must add one additional layer of complexity: the frame. A frame is a collection of information and context for a chunk of code. Frames are created and destroyed on the fly as your Python code executes. There's one frame corresponding to each *call* of a function—so while each frame has one code object associated with it, a code object can have many frames. If you had a function that called itself recursively ten times, you'd have eleven frames—one for each level of recursion and one for the module you started from. In general, there's a frame for each scope in a Python program. For example, each module, each function call, and each class definition has a frame. -Frames live on the _call stack_, a completely different stack from the one we've been discussing so far. (The call stack is the stack you're most familiar with already --- you've seen it printed out in the tracebacks of exceptions. Each line in a traceback starting with "File 'program.py', line 10" corresponds to one frame on the call stack.) The stack we've been examining --- the one the interpreter is manipulating while it executes bytecode --- we'll call the _data stack_. There's also a third stack, called the _block stack_. Blocks are used for certain kinds of control flow, particularly looping and exception handling. Each frame on the call stack has its own data stack and block stack. +Frames live on the _call stack_, a completely different stack from the one we've been discussing so far. (The call stack is the stack you're most familiar with already—you've seen it printed out in the tracebacks of exceptions. Each line in a traceback starting with "File 'program.py', line 10" corresponds to one frame on the call stack.) The stack we've been examining—the one the interpreter is manipulating while it executes bytecode—we'll call the _data stack_. There's also a third stack, called the _block stack_. Blocks are used for certain kinds of control flow, particularly looping and exception handling. Each frame on the call stack has its own data stack and block stack. Let's make this concrete with an example. Suppose the Python interpreter is currently executing the line marked 3 below. The interpreter is in the middle of a call to `foo`, which is in turn calling `bar`. The diagram shows a schematic of the call stack of frames, the block stacks, and the data stacks. (This code is written like a REPL session, so we've first defined the needed functions.) At the moment we're interested in, the interpreter is executing `foo()`, at the bottom, which then reaches in to the body of `foo` and then up into `bar`. @@ -485,7 +485,7 @@ class VirtualMachine(object): ### The `Function` Class -The implementation of the `Function` object is somewhat twisty, and most of the details aren't critical to understanding the interpreter. The important thing to notice is that calling a function --- invoking the `__call__` method --- creates a new `Frame` object and starts running it. +The implementation of the `Function` object is somewhat twisty, and most of the details aren't critical to understanding the interpreter. The important thing to notice is that calling a function—invoking the `__call__` method—creates a new `Frame` object and starts running it. ```python class Function(object): @@ -602,7 +602,7 @@ class VirtualMachine(object): return byte_name, argument ``` -The next method is `dispatch`, which looks up the operations for a given instruction and executes them. In the CPython interpreter, this dispatch is done with a giant switch statement that spans 1,500 lines! Luckily, since we're writing Python, we can be more compact. We'll define a method for each byte name and then use `getattr` to look it up. Like in the toy interpreter above, if our instruction is named `FOO_BAR`, the corresponding method would be named `byte_FOO_BAR`. For the moment, we'll leave the content of these methods as a black box. Each bytecode method will return either `None` or a string, called `why`, which is an extra piece of state the interpreter needs in some cases. These return values of the individual instruction methods are used only as internal indicators of interpreter state --- don't confuse these with return values from executing frames. +The next method is `dispatch`, which looks up the operations for a given instruction and executes them. In the CPython interpreter, this dispatch is done with a giant switch statement that spans 1,500 lines! Luckily, since we're writing Python, we can be more compact. We'll define a method for each byte name and then use `getattr` to look it up. Like in the toy interpreter above, if our instruction is named `FOO_BAR`, the corresponding method would be named `byte_FOO_BAR`. For the moment, we'll leave the content of these methods as a black box. Each bytecode method will return either `None` or a string, called `why`, which is an extra piece of state the interpreter needs in some cases. These return values of the individual instruction methods are used only as internal indicators of interpreter state—don't confuse these with return values from executing frames. ```python @@ -930,7 +930,7 @@ class VirtualMachine(object): ## Dynamic Typing: What the Compiler Doesn't Know -One thing you've probably heard is that Python is a "dynamic" language --- particularly that it's "dynamically typed". The context we've just built up on the interpreter sheds some light on this description. +One thing you've probably heard is that Python is a "dynamic" language—particularly that it's "dynamically typed". The context we've just built up on the interpreter sheds some light on this description. One of the things "dynamic" means in this context is that a lot of work is done at run time. We saw earlier that the Python compiler doesn't have much information about what the code actually does. For example, consider the short function `mod` below. `mod` takes two arguments and returns the first modulo the second. In the bytecode, we see that the variables `a` and `b` are loaded, then the bytecode `BINARY_MODULO` performs the modulo operation itself. @@ -946,7 +946,7 @@ One of the things "dynamic" means in this context is that a lot of work is done 4 ``` -Calculating 19 `%` 5 yields 4 --- no surprise there. What happens if we call it with different kinds of arguments? +Calculating 19 `%` 5 yields 4—no surprise there. What happens if we call it with different kinds of arguments? ```python >>> mod("by%sde", "teco") @@ -960,11 +960,11 @@ What just happened? You've probably seen this syntax before, but in a different bytecode ``` -Using the symbol `%` to format a string for printing means invoking the instruction `BINARY_MODULO`. This instruction mods together the top two values on the stack when the instruction executes --- regardless of whether they're strings, integers, or instances of a class you defined yourself. The bytecode was generated when the function was compiled (effectively, when it was defined) and the same bytecode is used with different types of arguments. +Using the symbol `%` to format a string for printing means invoking the instruction `BINARY_MODULO`. This instruction mods together the top two values on the stack when the instruction executes—regardless of whether they're strings, integers, or instances of a class you defined yourself. The bytecode was generated when the function was compiled (effectively, when it was defined) and the same bytecode is used with different types of arguments. The Python compiler knows relatively little about the effect the bytecode will have. It's up to the interpreter to determine the type of the object that `BINARY_MODULO` is operating on and do the right thing for that type. This is why Python is described as _dynamically typed_: you don't know the types of the arguments to this function until you actually run it. By contrast, in a language that's statically typed, the programmer tells the compiler up front what type the arguments will be (or the compiler figures them out for itself). -The compiler's ignorance is one of the challenges to optimizing Python or analyzing it statically --- just looking at the bytecode, without actually running the code, you don't know what each instruction will do! In fact, you could define a class that implements the `__mod__` method, and Python would invoke that method if you use `%` on your objects. So `BINARY_MODULO` could actually run any code at all! +The compiler's ignorance is one of the challenges to optimizing Python or analyzing it statically—just looking at the bytecode, without actually running the code, you don't know what each instruction will do! In fact, you could define a class that implements the `__mod__` method, and Python would invoke that method if you use `%` on your objects. So `BINARY_MODULO` could actually run any code at all! Just looking at the following code, the first calculation of `a % b` seems wasteful. @@ -974,13 +974,13 @@ def mod(a,b): return a %b ``` -Unfortunately, a static analysis of this code --- the kind of you can do without running it --- can't be certain that the first `a % b` really does nothing. Calling `__mod__` with `%` might write to a file, or interact with another part of your program, or do literally anything else that's possible in Python. It's hard to optimize a function when you don't know what it does! In Russell Power and Alex Rubinsteyn's great paper "How fast can we make interpreted Python?", they note, "In the general absence of type information, each instruction must be treated as `INVOKE_ARBITRARY_METHOD`." +Unfortunately, a static analysis of this code—the kind of you can do without running it—can't be certain that the first `a % b` really does nothing. Calling `__mod__` with `%` might write to a file, or interact with another part of your program, or do literally anything else that's possible in Python. It's hard to optimize a function when you don't know what it does! In Russell Power and Alex Rubinsteyn's great paper "How fast can we make interpreted Python?", they note, "In the general absence of type information, each instruction must be treated as `INVOKE_ARBITRARY_METHOD`." ## Conclusion Byterun is a compact Python interpreter that's easier to understand than CPython. Byterun replicates CPython's primary structural details: a stack-based interpreter operating on instruction sets called bytecode. It steps or jumps through these instructions, pushing to and popping from a stack of data. The interpreter creates, destroys, and jumps between frames as it calls into and returns from functions and generators. Byterun shares the real interpreter's limitations, too: because Python uses dynamic typing, the interpreter must work hard at run time to determine the correct behavior for any series of instructions. -I encourage you to disassemble your own programs and to run them using Byterun. You'll quickly run into instructions that this shorter version of Byterun doesn't implement. The full implementation can be found at https://github.com/nedbat/byterun --- or, by carefully reading the real CPython interpreter's `ceval.c`, you can implement it yourself! +I encourage you to disassemble your own programs and to run them using Byterun. You'll quickly run into instructions that this shorter version of Byterun doesn't implement. The full implementation can be found at https://github.com/nedbat/byterun—or, by carefully reading the real CPython interpreter's `ceval.c`, you can implement it yourself! ## Acknowledgements diff --git a/pedometer/pedometer.markdown b/pedometer/pedometer.markdown index a1ab28228..afb73d765 100644 --- a/pedometer/pedometer.markdown +++ b/pedometer/pedometer.markdown @@ -71,7 +71,7 @@ User acceleration is the acceleration of the device due to the movement of the u To count steps, we're interested in the bounces created by the user in the direction of gravity. That means we're interested in isolating the 1-dimensional time series which describes **user acceleration in the direction of gravity** from our 3-dimensional acceleration signal (\aosafigref{500l.pedometer.componentsignals}). -In our simple example, gravitational acceleration is 0 in $x(t)$ and $z(t)$ and constant at 9.8 $m/s^2$ in $y(t)$. Therefore, in our total acceleration plot, $x(t)$ and $z(t)$ fluctuate around 0 while $y(t)$ fluctuates around -1 *g*. In our user acceleration plot, we notice that --- because we have removed gravitational acceleration --- all three time series fluctuate around 0. Note the obvious peaks in $y_{u}(t)$. Those are due to step bounces! In our last plot, gravitational acceleration, $y_{g}(t)$ is constant at -1 *g*, and $x_{g}(t)$ and $z_{g}(t)$ are constant at 0. +In our simple example, gravitational acceleration is 0 in $x(t)$ and $z(t)$ and constant at 9.8 $m/s^2$ in $y(t)$. Therefore, in our total acceleration plot, $x(t)$ and $z(t)$ fluctuate around 0 while $y(t)$ fluctuates around -1 *g*. In our user acceleration plot, we notice that—because we have removed gravitational acceleration—all three time series fluctuate around 0. Note the obvious peaks in $y_{u}(t)$. Those are due to step bounces! In our last plot, gravitational acceleration, $y_{g}(t)$ is constant at -1 *g*, and $x_{g}(t)$ and $z_{g}(t)$ are constant at 0. So, in our example, the 1-dimensional user acceleration in the direction of gravity time series we're interested in is $y_{u}(t)$. Although $y_{u}(t)$ isn't as smooth as our perfect sine wave, we can identify the peaks, and use those peaks to count steps. So far, so good. Now, let's add even more reality to our world. @@ -340,7 +340,7 @@ The input into our system will be data from an accelerometer, information on the \aosafigure[240pt]{pedometer-images/pipeline.png}{The pipeline}{500l.pedometer.pipeline} -In the spirit of separation of concerns, we'll write the code for each distinct component of the pipeline --- parsing, processing, and analyzing --- individually. +In the spirit of separation of concerns, we'll write the code for each distinct component of the pipeline—parsing, processing, and analyzing—individually. ### Parsing diff --git a/same-origin-policy/same-origin-policy.markdown b/same-origin-policy/same-origin-policy.markdown index f4f289a35..9511d3123 100644 --- a/same-origin-policy/same-origin-policy.markdown +++ b/same-origin-policy/same-origin-policy.markdown @@ -27,7 +27,7 @@ Furthermore, the design of the SOP has evolved organically over the years and puzzles many developers. The goal of this chapter is to capture the essence of -this important --- yet often misunderstood --- feature. In particular, we +this important—yet often misunderstood—feature. In particular, we will attempt to answer the following questions: * Why is the SOP necessary? What are the types of security violations that it prevents? @@ -36,7 +36,7 @@ will attempt to answer the following questions: * How secure are these mechanisms? What are potential security issues that they introduce? Covering the SOP in its entirety is a daunting task, given the -complexity of the parts that are involved --- web servers, browsers, +complexity of the parts that are involved—web servers, browsers, HTTP, HTML documents, client-side scripts, and so on. We would likely get bogged down by the gritty details of all these parts (and consume our 500 lines before even reaching SOP). But how can we @@ -165,10 +165,10 @@ database table. Thus `protocol` is a table with the first column containing URLs and the second column containing protocols. And the innocuous looking dot operator is in fact a rather general kind of relational join, so that you could also write `protocol.p` for all the -URLs with a protocol `p` --- but more on that later. +URLs with a protocol `p`—but more on that later. Note that paths, unlike URLs, are treated as if they have -no structure --- a simplification. The keyword `lone` (which can be +no structure—a simplification. The keyword `lone` (which can be read "less than or equal to one") says that each URL has at most one port. The path is the string that follows the host name in the URL, and which (for a simple static server) corresponds to the file path of @@ -323,7 +323,7 @@ client, but with two different servers. (In the Alloy visualizer, objects of the same type are distinguished by appending numeric suffixes to their names; if there is only one object of a given type, no suffix is added. Every name that appears in a snapshot diagram is -the name of an object. So --- perhaps confusingly at first sight --- the +the name of an object. So—perhaps confusingly at first sight—the names `Domain`, `Path`, `Resource`, `Url` all refer to individual objects, not to types.) @@ -465,7 +465,7 @@ properties of a document. The flexibility of client-side scripts is one of the main catalysts of the rapid development of Web 2.0, but is also the reason why the SOP was created in the first place. Without the SOP, scripts would be able to send arbitrary requests to servers, -or freely modify documents inside the browser --- which would be bad +or freely modify documents inside the browser—which would be bad news if one or more of the scripts turned out to be malicious. A script can communicate to a server by sending an `XmlHttpRequest`: @@ -519,7 +519,7 @@ A script can read from and write to various parts of a document number of API functions for accessing the DOM (e.g., `document.getElementById`), but enumerating all of them is not important for our purpose. Instead, we will simply group them into two -kinds --- `ReadDom` and `WriteDom` --- and model modifications as +kinds—`ReadDom` and `WriteDom`—and model modifications as wholesale replacements of the entire document: ```alloy @@ -644,7 +644,7 @@ is _secure_? Not surprisingly, this is a tricky question to answer. For our purposes, we will turn to two well-studied concepts in information -security --- _confidentiality_ and _integrity_. Both of these concepts +security—_confidentiality_ and _integrity_. Both of these concepts talk about how information should be allowed to pass through the various parts of the system. Roughly, _confidentiality_ means that a critical piece of data should only be accessible to parts that are @@ -816,7 +816,7 @@ identity (e.g., a session cookie), `EvilScript` can effectively pretend to be the user and trick the server into responding with the user's private data (`MyInboxInfo`). Here, the problem is again related to the liberal ways in which a script may be used to access -information across different domains --- namely, that a script executing +information across different domains—namely, that a script executing under one domain is able to make an HTTP request to a server with a different domain. @@ -853,7 +853,7 @@ fact domSop { ``` An instance such as the first script scenario (from the previous section) is not possible under `domSop`, since `Script` is not allowed to invoke `ReadDom` on a document from a different origin. -The second part of the policy says that a script cannot send an HTTP request to a server unless its context has the same origin as the target URL --- effectively preventing instances such as the second script scenario. +The second part of the policy says that a script cannot send an HTTP request to a server unless its context has the same origin as the target URL—effectively preventing instances such as the second script scenario. ```alloy fact xmlHttpReqSop { all x: XmlHttpRequest | origin[x.url] = origin[x.from.context.src] @@ -1331,7 +1331,7 @@ modeling would potentially be even more beneficial if it is done during the early stage of system design. Besides the SOP, Alloy has been used to model and reason about a -variety of systems across different domains --- ranging from network +variety of systems across different domains—ranging from network protocols, semantic web, bytecode security to electronic voting and medical systems. For many of these systems, Alloy's analysis led to discovery of design flaws and bugs that had eluded the developers, in @@ -1391,7 +1391,7 @@ execution; for example, `cookies` in the `Browser` signature. In this sense, `Time` objects are nothing but helper objects used as a kind of index. -Each call occurs between two points in time --- its `start` and `end` +Each call occurs between two points in time—its `start` and `end` times, and is associated with a sender (represented by `from`) and a receiver (`to`): diff --git a/sampler/sampler.markdown b/sampler/sampler.markdown index bd2ac0856..a74e25b53 100644 --- a/sampler/sampler.markdown +++ b/sampler/sampler.markdown @@ -556,7 +556,7 @@ underflow: Still, doing all our computations with logs can save a lot of headache. We might be forced to lose that precision if we need to go back to the original numbers, but we at least maintain *some* information about -the probabilities---enough to compare them, for example---that would +the probabilities—enough to compare them, for example—that would otherwise be lost. #### Writing the PMF Code @@ -792,9 +792,9 @@ def _sample_stats(self): return stats ``` -We *could* have made these a single method---especially since +We *could* have made these a single method—especially since `_sample_stats` is the only function that depends on -`_sample_bonus`---but I have chosen to keep them separate, both +`_sample_bonus`—but I have chosen to keep them separate, both because it makes the sampling routine easier to understand, and because breaking it up into smaller pieces makes the code easier to test. @@ -822,8 +822,8 @@ def sample(self): The `sample` function does essentially the same thing as `_sample_stats`, except that it returns a dictionary with the stats' names as keys. This provides a clean and understandable interface for -sampling items---it is obvious which stats have how many bonus -points---but it also keeps open the option of using just +sampling items—it is obvious which stats have how many bonus +points—but it also keeps open the option of using just `_sample_stats` if one needs to take many samples and efficiency is required. @@ -1175,5 +1175,5 @@ know (e.g., discovering how much damage a player with two items is likely to deal). Almost every type of sampling you might encounter falls under one of these two categories; the differences only have to do with what distributions you are sampling from. The general -structure of the code---independent of those distributions---remains +structure of the code—independent of those distributions—remains the same. diff --git a/static-analysis/static-analysis.markdown b/static-analysis/static-analysis.markdown index 4c53533fc..e804d22f8 100644 --- a/static-analysis/static-analysis.markdown +++ b/static-analysis/static-analysis.markdown @@ -7,7 +7,7 @@ _Leah Hanson is a proud alumni of Hacker School and loves helping people learn a You may be familiar with a fancy IDE that draws red underlines under parts of your code that don't compile. You may have run a linter on your code to check for formatting or style problems. You might run your compiler in super-picky mode with all the warnings turned on. All of these tools are applications of static analysis. -Static analysis is a way to check for problems in your code without running it. "Static" means at compile time rather than at run time, and "analysis" means we're analyzing the code. When you've used the tools I mentioned above, it may have felt like magic. But those tools are just programs --- they are made of source code that was written by a person, a programmer like you. In this chapter, we're going to talk about how to implement a couple of static analysis checks. In order to do this, we need to know what we want the check to do and how we want to do it. +Static analysis is a way to check for problems in your code without running it. "Static" means at compile time rather than at run time, and "analysis" means we're analyzing the code. When you've used the tools I mentioned above, it may have felt like magic. But those tools are just programs—they are made of source code that was written by a person, a programmer like you. In this chapter, we're going to talk about how to implement a couple of static analysis checks. In order to do this, we need to know what we want the check to do and how we want to do it. We can get more specific about what you need to know by describing the process as having three stages: @@ -196,13 +196,13 @@ We'll need to find out which variables are used inside loops and we'll need to f * How do we print the results? * How do we tell if the type is unstable? -I'm going to tackle the last question first, since this whole endeavour hinges on it. We've looked at an unstable function and seen, as programmers, how to identify an unstable variable, but we need our program to find them. This sounds like it would require simulating the function to look for variables whose values might change --- which sounds like it would take some work. Luckily for us, Julia's type inference already traces through the function's execution to determine the types. +I'm going to tackle the last question first, since this whole endeavour hinges on it. We've looked at an unstable function and seen, as programmers, how to identify an unstable variable, but we need our program to find them. This sounds like it would require simulating the function to look for variables whose values might change—which sounds like it would take some work. Luckily for us, Julia's type inference already traces through the function's execution to determine the types. The type of `sum` in `unstable` is `Union(Float64,Int64)`. This is a `UnionType`, a special kind of type that indicates that the variable may hold any of a set of types of values. A variable of type `Union(Float64,Int64)` can hold values of type `Int64` or `Float64`; a value can only have one of those types. A `UnionType` joins any number of types (e.g., `UnionType(Float64, Int64, Int32)` joins three types). The specific thing that we're going to look for is `UnionType`d variables inside loops. Parsing code into a representative structure is a complicated business, and gets more complicated as the language grows. In this chapter, we'll be depending on internal data structures used by the compiler. This means that we don't have to worry about reading files or parsing them, but it does mean we have to work with data structures that are not in our control and that sometimes feel clumsy or ugly. -Besides all the work we'll save by not having to parse the code by ourselves, working with the same data structures that the compiler uses means that our checks will be based on an accurate assessment of the compilers understanding --- which means our check will be consistent with how the code actually runs. +Besides all the work we'll save by not having to parse the code by ourselves, working with the same data structures that the compiler uses means that our checks will be based on an accurate assessment of the compilers understanding—which means our check will be consistent with how the code actually runs. This process of examining Julia code from Julia code is called introspection. When you or I introspect, we're thinking about how and why we think and feel. When code introspects, it examines the representation or execution properties of code in the same language (possibly its own code). When code's introspection extends to modifying the examined code, it's called metaprogramming (programs that write or modify programs). @@ -648,7 +648,7 @@ end Sometimes, as you're typing in your program, you mistype a variable name. The program can't tell that you meant for this to be the same variable that you spelled correctly before; it sees a variable used only one time, where you might see a variable name misspelled. Languages that require variable declarations naturally catch these misspellings, but many dynamic languages don’t require declarations and thus need an extra layer of analysis to catch them. -We can find misspelled variable names (and other unused variables) by looking for variables that are only used once --- or only used one way. +We can find misspelled variable names (and other unused variables) by looking for variables that are only used once—or only used one way. Here is an example of a little bit of code with one misspelled name. @@ -663,9 +663,9 @@ function foo(variable_name::Int) end ``` -This kind of mistake can cause problems in your code that are only discovered when it's run. Let's assume you misspell each variable name only once. We can separate variable usages into writes and reads. If the misspelling is a write (i.e., `worng = 5`), then no error will be thrown; you'll just be silently putting the value in the wrong variable --- and it could be frustrating to find the bug. If the misspelling is a read (i.e., `right = worng + 2`), then you'll get a runtime error when the code is run; we'd like to have a static warning for this, so that you can find this error sooner, but you will still have to wait until you run the code to see the problem. +This kind of mistake can cause problems in your code that are only discovered when it's run. Let's assume you misspell each variable name only once. We can separate variable usages into writes and reads. If the misspelling is a write (i.e., `worng = 5`), then no error will be thrown; you'll just be silently putting the value in the wrong variable—and it could be frustrating to find the bug. If the misspelling is a read (i.e., `right = worng + 2`), then you'll get a runtime error when the code is run; we'd like to have a static warning for this, so that you can find this error sooner, but you will still have to wait until you run the code to see the problem. -As code becomes longer and more complicated, it becomes harder to spot the mistake --- unless you have the help of static analysis. +As code becomes longer and more complicated, it becomes harder to spot the mistake—unless you have the help of static analysis. ### Left-Hand Side and Right-Hand Side @@ -899,7 +899,7 @@ check_locals(e::Expr) = isempty(unused_locals(e)) ``` ## Conclusion -We’ve done two static analyses of Julia code --- one based on types and one based on variable usages. +We’ve done two static analyses of Julia code—one based on types and one based on variable usages. Statically-typed languages already do the kind of work our type-based analysis did; additional type-based static analysis is mostly useful in dynamically typed languages. There have been (mostly research) projects to build static type inference systems for languages including Python, Ruby, and Lisp. These systems are usually built around optional type annotations; you can have static types when you want them, and fall back to dynamic typing when you don’t. This is especially helpful for integrating some static typing into existing code bases. diff --git a/template-engine/template-engine.markdown b/template-engine/template-engine.markdown index 51e195583..f6f37f574 100644 --- a/template-engine/template-engine.markdown +++ b/template-engine/template-engine.markdown @@ -612,7 +612,7 @@ template engine only needs to define one function. But it's better software design to keep that implementation detail in the template engine code, and out of our CodeBuilder class. -Even as we're actually using it --- to define a single function --- having `get_globals` +Even as we're actually using it—to define a single function—having `get_globals` return the dictionary keeps the code more modular because it doesn't need to know the name of the function we've defined. Whatever function name we define in our Python source, we can retrieve that name from the dict returned by diff --git a/tex/500L.tex b/tex/500L.tex index 0028269b0..f465e12c5 100644 --- a/tex/500L.tex +++ b/tex/500L.tex @@ -260,49 +260,49 @@ \mainmatter -\include{image-filters} +\include{blockcode} -\include{dagoba} +\include{ci} -\include{ocr} +\include{cluster} \include{contingent} -\include{same-origin-policy} +\include{crawler} -\include{blockcode} +\include{dagoba} -\include{interpreter} +\include{data-store} -\include{web-server} +\include{event-web-framework} -\include{static-analysis} +\include{flow-shop} \include{functionalDB} -\include{flow-shop} +\include{image-filters} -\include{template-engine} +\include{interpreter} -\include{pedometer} +\include{modeller} -\include{sampler} +\include{objmodel} -\include{spreadsheet} +\include{ocr} -\include{cluster} +\include{pedometer} -\include{data-store} +\include{same-origin-policy} -\include{objmodel} +\include{sampler} -\include{ci} +\include{spreadsheet} -\include{crawler} +\include{static-analysis} -\include{modeller} +\include{template-engine} -\include{event-web-framework} +\include{web-server} \makeatletter diff --git a/tex/blockcode.tex b/tex/blockcode.tex index 245c916c2..ccb130a1f 100644 --- a/tex/blockcode.tex +++ b/tex/blockcode.tex @@ -8,7 +8,7 @@ Learning a programming language can be difficult because they are extremely sensitive to even the slightest of typos. Most programming languages are case-sensitive, have obscure syntax, and will refuse to -run if you get so much as a semicolon in the wrong place --- or worse, +run if you get so much as a semicolon in the wrong place---or worse, leave one out. Further, most programming languages in use today are based on English and their syntax cannot be localized. @@ -164,7 +164,7 @@ and \texttt{blocks.css} are basic structure and style for the app and won't be discussed. Two of the JavaScript files won't be discussed in any detail either: \texttt{util.js} contains some helpers and serves as -a bridge between different browser implementations --- similar to a +a bridge between different browser implementations---similar to a library like jQuery but in less than 50 lines of code. \texttt{file.js} is a similar utility used for loading and saving files and serializing scripts. diff --git a/tex/dagoba.tex b/tex/dagoba.tex index ca4449d30..95f4aed43 100644 --- a/tex/dagoba.tex +++ b/tex/dagoba.tex @@ -5,13 +5,13 @@ \begin{quote} ``When we try to pick out anything by itself we find that it is bound fast by a thousand invisible cords that cannot be broken, to everything -in the universe.'' --- John Muir +in the universe.'' ---John Muir \end{quote} \begin{quote} ``What went forth to the ends of the world to traverse not itself, God, the sun, Shakespeare, a commercial traveller, having itself traversed in -reality itself becomes that self.'' --- James Joyce +reality itself becomes that self.'' ---James Joyce \end{quote} A long time ago, when the world was still young, all data walked happily @@ -63,7 +63,7 @@ The distributed revolution changed everything, again. Data broke free of spacial constraints and roamed from machine to machine. CAP-wielding theorists busted the relational monopoly, opening the door to a plethora -of new herding techniques --- some of which hark back to the earliest +of new herding techniques---some of which hark back to the earliest attempts to domesticate random-access data. We're going to look at one of these, a style known as the graph database. @@ -247,10 +247,10 @@ Now we need some graphs. We can build these using a classic OOP pattern, but JavaScript offers us prototypal inheritance, which means we can -build up a prototype object --- we'll call it \texttt{Dagoba.G} --- and -then instantiate copies of that using a factory function. An advantage -of this approach is that we can return different types of objects from -the factory, instead of binding the creation process to a single class +build up a prototype object---we'll call it \texttt{Dagoba.G}---and then +instantiate copies of that using a factory function. An advantage of +this approach is that we can return different types of objects from the +factory, instead of binding the creation process to a single class constructor. So we get some extra flexibility for free. \begin{verbatim} @@ -292,7 +292,7 @@ Then we create a new object that has all of our prototype's strengths and none of its weaknesses. We build a brand new array (one of the other basic JS data structures) for our edges, another for the vertices, a new -object called \texttt{vertexIndex} and an ID counter --- more on those +object called \texttt{vertexIndex} and an ID counter---more on those latter two later. (Think: Why can't we just put these in the prototype?) Then we call \texttt{addVertices} and \texttt{addEdges} from inside our @@ -303,7 +303,7 @@ Dagoba.G.addEdges = function(es) { es.forEach(this.addEdge .bind(this)) } \end{verbatim} -Okay, that was too easy --- we're just passing off the work to +Okay, that was too easy---we're just passing off the work to \texttt{addVertex} and \texttt{addEdge}. We should define those now too. \begin{verbatim} @@ -341,8 +341,8 @@ runtime, breaking our invariants. So if we create a vertex instance object, we're forced to decide up -front whether we will always copy the provided data into a new object ---- potentially doubling our space usage --- or allow the host +front whether we will always copy the provided data into a new +object---potentially doubling our space usage---or allow the host application unfettered access to the database objects. There's a tension here between performance and protection, and the right balance depends on your specific use case. @@ -446,9 +446,9 @@ Now's a good time to introduce some friends. A \emph{program} is a series of \emph{steps}. Each step is like a pipe -in a pipeline --- a piece of data comes in one end, is transformed in -some fashion, and goes out the other end. Our pipeline doesn't quite -work like that, but it's a good first approximation. +in a pipeline---a piece of data comes in one end, is transformed in some +fashion, and goes out the other end. Our pipeline doesn't quite work +like that, but it's a good first approximation. Each step in our program can have \emph{state}, and \texttt{query.state} is a list of per-step states that index correlates with the list of @@ -492,9 +492,9 @@ Each step is a composite entity, combining the pipetype function with the arguments to apply to that function. We could combine the two into a partially applied function at this stage, instead of using a tuple -\footnote{A tuple is another abstract data structure --- one that is - more constrained than a list. In particular a tuple has a fixed size: - in this case we're using a 2-tuple (also known as a ``pair'' in the +\footnote{A tuple is another abstract data structure---one that is more + constrained than a list. In particular a tuple has a fixed size: in + this case we're using a 2-tuple (also known as a ``pair'' in the technical jargon of data structure researchers). Using the term for the most constrained abstract data structure required is a nicety for future implementors.}, but then we'd lose some introspective power @@ -529,7 +529,7 @@ beavers, are strict in their insistence that all arguments be evaluated before the function is applied. Their opposing faction, the Call By Needians, are content to procrastinate until the last possible moment -before doing anything, and even then do as little as possible --- they +before doing anything, and even then do as little as possible---they are, in a word, lazy. JavaScript, being a strict language, will process each of our steps as @@ -539,11 +539,11 @@ vertices finally return all vertices they are connected to by inbound edges. -In a non-strict language we would get the same result --- the execution +In a non-strict language we would get the same result---the execution strategy doesn't make much difference here. But what if we added a few additional calls? Given how well-connected Thor is, our \texttt{g.v('Thor').out().out().out().in().in().in()} query may produce -many results --- in fact, because we're not limiting our vertex list to +many results---in fact, because we're not limiting our vertex list to unique results, it may produce many more results than we have vertices in our total graph. @@ -743,8 +743,8 @@ We would still need to find a way to deal with the mutations, though, as the call site maintains a reference to the original variable. What if we -had some way to determine whether a particular reference is ``unique'' ---- that it is the only reference to that object? +had some way to determine whether a particular reference is +``unique''---that it is the only reference to that object? If we know a reference is unique then we can get the benefits of immutability while avoiding expensive copy-on-write schemes or @@ -769,10 +769,9 @@ has only one reference. If we had a reference counter \footnote{Most modern JS runtimes employ generational garbage collectors, and the language is intentionally kept at arm's length from the engine's - memory management to curtail a source of programmatic non-determinism.} ---- even just a cheap two-bit sticky counter --- we could know at -runtime that an object only has one reference and use that knowledge to -our advantage. + memory management to curtail a source of programmatic non-determinism.}---even +just a cheap two-bit sticky counter---we could know at runtime that an +object only has one reference and use that knowledge to our advantage. JavaScript doesn't have either of these facilities, but we can get almost the same effect if we're really, really disciplined. Which we @@ -909,8 +908,8 @@ \aosasectiii{Unique}\label{unique} -If we want to collect all Thor's grandparents' grandchildren --- his -cousins, his siblings, and himself --- we could do a query like this: +If we want to collect all Thor's grandparents' grandchildren---his +cousins, his siblings, and himself---we could do a query like this: \texttt{g.v('Thor').in().in().out().out().run()}. That would give us many duplicates, however. In fact there would be at least four copies of Thor himself. (Can you think of a time when there might be more?) @@ -1122,8 +1121,8 @@ We map over each argument, looking for it in the gremlin's list of labeled vertices. If we find it, we clone the gremlin to that vertex. Note that only gremlins that make it to this pipe are included in the -merge --- if Thor's mother's parents aren't in the graph, she won't be -in the result set. +merge---if Thor's mother's parents aren't in the graph, she won't be in +the result set. \aosasectiii{Except}\label{except} @@ -1281,7 +1280,7 @@ Note the use of \texttt{vertexIndex} here. Without that index we'd have to go through each vertex in our list one at a time to decide if it -matched the ID --- turning a constant time operation into a linear time +matched the ID---turning a constant time operation into a linear time one, and any $O(n)$ operations that directly rely on it into $O(n^2)$ operations. @@ -1389,7 +1388,7 @@ \end{verbatim} We need a place to store the most recent step's output, which might be a -gremlin --- or it might be nothing --- so we'll call it +gremlin---or it might be nothing---so we'll call it \texttt{maybe\_gremlin}: \begin{verbatim} @@ -1633,7 +1632,7 @@ We'll assume there won't be an enormous number of transformer additions, and walk the list linearly to add a new one. We'll leave a note in case -this assumption turns out to be false --- a binary search is much more +this assumption turns out to be false---a binary search is much more time-optimal for long lists, but adds a little complexity and doesn't really speed up short lists. @@ -1825,7 +1824,7 @@ To alleviate this dismal performance most databases index over oft-queried fields, which turns an $O(n)$ search into an $O(log n)$ search. This gives considerably better search performance, but at the -cost of some write performance and a lot of space --- indices can easily +cost of some write performance and a lot of space---indices can easily double the size of a database. Careful balancing of the space/time tradeoffs of indices is part of the perpetual tuning process for most databases. @@ -1876,11 +1875,11 @@ \end{verbatim} Run these yourself to experience the graph database difference -\footnote{In modern JavaScript engines filtering a list is quite fast - --- for small graphs the naive version can actually be faster than the - index-free version due to the underlying data structures and the way - the code is JIT compiled. Try it with different sizes of graphs to see - how the two approaches scale.}. +\footnote{In modern JavaScript engines filtering a list is quite + fast---for small graphs the naive version can actually be faster than + the index-free version due to the underlying data structures and the + way the code is JIT compiled. Try it with different sizes of graphs to + see how the two approaches scale.}. \aosasecti{Serialization}\label{serialization} @@ -1949,8 +1948,7 @@ Persistence is usually one of the trickier parts of a database: disks are relatively safe, but dreadfully slow. Batching writes, making them -atomic, journaling --- these are difficult to make both fast and -correct. +atomic, journaling---these are difficult to make both fast and correct. Fortunately, we're building an \emph{in-memory} database, so we don't have to worry about any of that! We may, though, occasionally want to @@ -1977,7 +1975,7 @@ \end{verbatim} Now we'll use those in our persistence functions. The \texttt{toString} -function is hiding --- can you spot it? +function is hiding---can you spot it? \begin{verbatim} Dagoba.persist = function(graph, name) { @@ -2062,7 +2060,7 @@ .run() \end{verbatim} -This is pretty clumsy, and doesn't scale well --- what if we wanted six +This is pretty clumsy, and doesn't scale well---what if we wanted six layers of ancestors? Or to look through an arbitrary number of ancestors until we found what we wanted? @@ -2072,8 +2070,8 @@ g.v('Thor').out().all().times(3).run() \end{verbatim} -What we'd like to get out of this is something like the query above --- -maybe: +What we'd like to get out of this is something like the query +above---maybe: \begin{verbatim} g.v('Thor').out().as('a') @@ -2101,13 +2099,12 @@ To solve that first problem we're going to have to treat \texttt{all}s as something more than just as/merge. We need each parent gremlin to actually skip the intervening steps. We can think of this as a kind of -teleportation --- jumping from one part of the pipeline directly to -another --- or we can think of it as a certain kind of branching -pipeline, but either way it complicates our model somewhat. Another -approach would be to think of the gremlin as passing through the -intervening pipes in a sort of suspended animation, until awoken by a -special pipe. Scoping the suspending/unsuspending pipes may be tricky, -however. +teleportation---jumping from one part of the pipeline directly to +another---or we can think of it as a certain kind of branching pipeline, +but either way it complicates our model somewhat. Another approach would +be to think of the gremlin as passing through the intervening pipes in a +sort of suspended animation, until awoken by a special pipe. Scoping the +suspending/unsuspending pipes may be tricky, however. The next two problems are easier. To modify just part of a query we'll wrap that portion in special start/end steps, like @@ -2123,9 +2120,9 @@ re-mark all marked \texttt{all}s uniquely. There's still the issue of searching through an unbounded number of -ancestors --- for example, how do we find out which of Ymir's -descendants are scheduled to survive Ragnarök? We could make individual -queries like \texttt{g.v('Ymir').in().filter(\{survives: true\})} and +ancestors---for example, how do we find out which of Ymir's descendants +are scheduled to survive Ragnarök? We could make individual queries like +\texttt{g.v('Ymir').in().filter(\{survives: true\})} and \newline \texttt{g.v('Ymir').in().in().in().in().filter(\{survives: true\})}, and manually collect the results ourselves, but that's pretty awful. @@ -2147,7 +2144,7 @@ \aosasecti{Wrapping Up}\label{wrapping-up} So what have we learned? Graph databases are great for storing -interconnected \footnote{Not \emph{too} interconnected, though --- you'd +interconnected \footnote{Not \emph{too} interconnected, though---you'd like the number of edges to grow in direct proportion to the number of vertices. In other words, the average number of edges connected to a vertex shouldn't vary with the size of the graph. Most systems we'd @@ -2163,8 +2160,8 @@ reasoning about our system easier. Building in a simple, decoupled and painfully unoptimized style leaves the door open for global optimizations later on, and using a driver loop allows for orthogonal -optimizations --- each without introducing the brittleness and -complexity that is the hallmark of most optimization techniques. +optimizations---each without introducing the brittleness and complexity +that is the hallmark of most optimization techniques. That last point can't be overstated: keep it simple. Eschew optimization in favor of simplicity. Work hard to achieve simplicity by finding the diff --git a/tex/flow-shop.tex b/tex/flow-shop.tex index c5bfc51ea..3e2c5b78f 100644 --- a/tex/flow-shop.tex +++ b/tex/flow-shop.tex @@ -170,9 +170,9 @@ These settings could be exposed to the user as command line parameters, but at this stage we instead provide the input data as parameters to the -program. The input problem --- a problem from the Taillard benchmark set ---- is assumed to be in a standard format for flow shop scheduling. The -following code is used as the \texttt{\_\_main\_\_} method for the +program. The input problem---a problem from the Taillard benchmark +set---is assumed to be in a standard format for flow shop scheduling. +The following code is used as the \texttt{\_\_main\_\_} method for the solver file, and calls the appropriate functions based on the number of parameters input to the program: @@ -226,8 +226,8 @@ \emph{every} permutation is a valid solution, and at least one will have the optimal makespan (though many will have horrible makespans). Thankfully, this allows us to forgo checking that we stay within the -space of feasible solutions when going from one permutation to another ---- everything is feasible! +space of feasible solutions when going from one permutation to +another---everything is feasible! However, to start a local search in the space of permutations, we must have an initial permutation. To keep things simple, we seed our local @@ -632,10 +632,10 @@ The final neighbourhood that we consider is commonly referred to as \emph{Large Neighbourhood Search} (LNS). Intuitively, LNS works by -considering small subsets of the current permutation in isolation --- -locating the best permutation of the subset of jobs gives us a single -candidate for the LNS neighbourhood. By repeating this process for -several (or all) subsets of a particular size, we can increase the +considering small subsets of the current permutation in +isolation---locating the best permutation of the subset of jobs gives us +a single candidate for the LNS neighbourhood. By repeating this process +for several (or all) subsets of a particular size, we can increase the number of candidates in the neighbourhood. We limit the number that are considered through the \texttt{MAX\_LNS\_NEIGHBOURHOODS} parameter, as the number of neighbours can grow quite quickly. The first step in the diff --git a/tex/functionalDB.tex b/tex/functionalDB.tex index 66154cd6c..c817ebc33 100644 --- a/tex/functionalDB.tex +++ b/tex/functionalDB.tex @@ -516,9 +516,9 @@ As we've already discussed, data in an archaeologist's world never actually changes. Once it is created, it exists forever and can only be hidden from the world by data in a newer layer. The term ``hidden'' is -crucial here. Older data does not ``disappear'' --- it is buried, and -can be revealed again by exposing an older layer. Conversely, updating -data means obscuring the old by adding a new layer on top of it with +crucial here. Older data does not ``disappear''---it is buried, and can +be revealed again by exposing an older layer. Conversely, updating data +means obscuring the old by adding a new layer on top of it with something else. We can thus ``delete'' data by adding a layer of ``nothing'' on top of it. @@ -1531,7 +1531,7 @@ Finally, we remove all of the result clauses that are ``empty'' (i.e., their last item is empty). We do this in the last line of the \texttt{query-index} function. Our example leaves us with the items in -\aosatblref{500.functionaldb.filteredqueryresults}. +\aosatblref{500l.functionaldb.filteredqueryresults}. \begin{table} \centering @@ -1554,7 +1554,7 @@ We are now ready to report the results. The result clause structure is unwieldy for this purpose, so we will convert it into an an index-like -structure (map of maps) --- with a significant twist. +structure (map of maps)---with a significant twist. To understand the twist, we must first introduce the idea of a \emph{binding pair}, which is a pair that matches a variable name to its diff --git a/tex/image-filters.tex b/tex/image-filters.tex index 613aa518f..83635e3c6 100644 --- a/tex/image-filters.tex +++ b/tex/image-filters.tex @@ -15,10 +15,10 @@ Brilliant)}\label{a-brilliant-idea-that-wasnt-all-that-brilliant} When I was traveling in China I often saw series of four paintings -showing the same place in different seasons. Color --- the cool whites -of winter, pale hues of spring, lush greens of summer, and reds and -yellows of fall --- is what visually differentiates the seasons. Around -2011, I had what I thought was a brilliant idea: I wanted to be able to +showing the same place in different seasons. Color---the cool whites of +winter, pale hues of spring, lush greens of summer, and reds and yellows +of fall---is what visually differentiates the seasons. Around 2011, I +had what I thought was a brilliant idea: I wanted to be able to visualize a photo series as a series of colors. I thought it would show travel, and progression through the seasons. @@ -58,8 +58,8 @@ and priorities. Part of finishing this project was learning how to carve out time to make progress on my own ideas; I required about four hours of good mental time a week. A tool that allowed me to move faster was -therefore really helpful, even necessary --- although it came with its -own set of problems, especially around writing tests. +therefore really helpful, even necessary---although it came with its own +set of problems, especially around writing tests. I felt that thorough tests were especially important for validating how the project was working, and for making it easier to pick up and resume @@ -101,7 +101,7 @@ In the old days, we would take the picture, then when we had used a whole roll of film, we would take it in to be developed (often at the -pharmacy). We'd pick up the developed pictures some days later --- and +pharmacy). We'd pick up the developed pictures some days later---and discover that there was something wrong with many of them. Hand not steady enough? Random person or thing that we didn't notice at the time? Overexposed? Underexposed? Of course by then it was too late to remedy diff --git a/tex/interpreter.tex b/tex/interpreter.tex index ec752431c..2efee487c 100644 --- a/tex/interpreter.tex +++ b/tex/interpreter.tex @@ -8,7 +8,7 @@ 500-line size restriction. This chapter will walk through the structure of the interpreter and give you enough context to explore it further. The goal is not to explain everything there is to know about -interpreters --- like so many interesting areas of programming and +interpreters---like so many interesting areas of programming and computer science, you could devote years to developing a deep understanding of the topic. @@ -69,8 +69,8 @@ Byterun can fall back to ``real'' Python when it needs to create a class. Another advantage is that Byterun is easy to understand, partly because it's written in a high-level language (Python!) that many people -find easy to read. (We also exclude interpreter optimizations in Byterun ---- once again favoring clarity and simplicity over speed.) +find easy to read. (We also exclude interpreter optimizations in +Byterun---once again favoring clarity and simplicity over speed.) \aosasecti{Building an Interpreter}\label{building-an-interpreter} @@ -88,7 +88,7 @@ instruction sets called \emph{bytecode}. When you write Python, the lexer, parser, and compiler generate code objects for the interpreter to operate on. Each code object contains a set of instructions to be -executed --- that's the bytecode --- plus other information that the +executed---that's the bytecode---plus other information that the interpreter will need. Bytecode is an \emph{intermediate representation} of Python code: it expresses the source code that you wrote in a way the interpreter can understand. It's analogous to the way that assembly @@ -171,10 +171,10 @@ case of adding two numbers, the example is a little contrived. However, this instruction is a building block for more complex programs. For example, with just the instructions we've defined so far, we can already -add together three values --- or any number of values --- given the -right set of these instructions. The stack provides a clean way to keep -track of the state of the interpreter, and it will support more -complexity as we go along. +add together three values---or any number of values---given the right +set of these instructions. The stack provides a clean way to keep track +of the state of the interpreter, and it will support more complexity as +we go along. Now let's start to write the interpreter itself. The interpreter object has a stack, which we'll represent with a list. The object also has a @@ -310,9 +310,9 @@ The arguments to an instruction can now mean two different things: They can either be an index into the ``numbers'' list, or they can be an index into the ``names'' list. The interpreter knows which it should be -by checking what instruction it's executing. We'll break out this logic ---- and the mapping of instructions to what their arguments mean --- -into a separate method. +by checking what instruction it's executing. We'll break out this +logic---and the mapping of instructions to what their arguments +mean---into a separate method. \begin{verbatim} class Interpreter: @@ -404,8 +404,8 @@ \texttt{cond.\_\_code\_\_} is the code object associated it, and \texttt{cond.\_\_code\_\_.co\_code} is the bytecode. There's almost never a good reason to use these attributes directly when you're writing -Python code, but they do allow us to get up to all sorts of mischief --- -and to look at the internals in order to understand them. +Python code, but they do allow us to get up to all sorts of +mischief---and to look at the internals in order to understand them. \begin{verbatim} >>> cond.__code__.co_code # the bytecode as raw bytes @@ -416,7 +416,7 @@ 100, 4, 0, 83, 100, 0, 0, 83] \end{verbatim} -When we just print the bytecode, it looks unintelligible --- all we can +When we just print the bytecode, it looks unintelligible---all we can tell is that it's a series of bytes. Luckily, there's a powerful tool we can use to understand it: the \texttt{dis} module in the Python standard library. @@ -466,8 +466,8 @@ 'STORE_FAST' \end{verbatim} -The second and third bytes --- 1, 0 --- are arguments to -\texttt{LOAD\_CONST}, while the fifth and sixth bytes --- 0, 0 --- are +The second and third bytes---1, 0---are arguments to +\texttt{LOAD\_CONST}, while the fifth and sixth bytes---0, 0---are arguments to \texttt{STORE\_FAST}. Just like in our toy example, \texttt{LOAD\_CONST} needs to know where to find its constant to load, and \texttt{STORE\_FAST} needs to find the name to store. (Python's @@ -517,7 +517,7 @@ \texttt{POP\_JUMP\_IF\_FALSE} is responsible for implementing the \texttt{if}. This instruction will pop the top value off the interpreter's stack. If the value is true, then nothing happens. (The -value can be ``truthy'' --- it doesn't have to be the literal +value can be ``truthy''---it doesn't have to be the literal \texttt{True} object.) If the value is false, then the interpreter will jump to another instruction. @@ -535,8 +535,8 @@ the line \texttt{while x \textless{} 5} generates almost identical bytecode to \texttt{if x \textless{} 10}. In both cases, the comparison is calculated and then \texttt{POP\_JUMP\_IF\_FALSE} controls which -instruction is executed next. At the end of line 4 --- the end of the -loop's body --- the instruction \texttt{JUMP\_ABSOLUTE} always sends the +instruction is executed next. At the end of line 4---the end of the +loop's body---the instruction \texttt{JUMP\_ABSOLUTE} always sends the interpreter back to instruction 9 at the top of the loop. When x \textless{} 5 becomes false, then \texttt{POP\_JUMP\_IF\_FALSE} jumps the interpreter past the end of the loop, to instruction 34. @@ -599,21 +599,21 @@ the frame. A frame is a collection of information and context for a chunk of code. Frames are created and destroyed on the fly as your Python code executes. There's one frame corresponding to each -\emph{call} of a function --- so while each frame has one code object +\emph{call} of a function---so while each frame has one code object associated with it, a code object can have many frames. If you had a function that called itself recursively ten times, you'd have eleven -frames --- one for each level of recursion and one for the module you +frames---one for each level of recursion and one for the module you started from. In general, there's a frame for each scope in a Python program. For example, each module, each function call, and each class definition has a frame. Frames live on the \emph{call stack}, a completely different stack from the one we've been discussing so far. (The call stack is the stack -you're most familiar with already --- you've seen it printed out in the +you're most familiar with already---you've seen it printed out in the tracebacks of exceptions. Each line in a traceback starting with ``File `program.py', line 10'' corresponds to one frame on the call stack.) The -stack we've been examining --- the one the interpreter is manipulating -while it executes bytecode --- we'll call the \emph{data stack}. There's +stack we've been examining---the one the interpreter is manipulating +while it executes bytecode---we'll call the \emph{data stack}. There's also a third stack, called the \emph{block stack}. Blocks are used for certain kinds of control flow, particularly looping and exception handling. Each frame on the call stack has its own data stack and block @@ -822,8 +822,8 @@ The implementation of the \texttt{Function} object is somewhat twisty, and most of the details aren't critical to understanding the -interpreter. The important thing to notice is that calling a function ---- invoking the \texttt{\_\_call\_\_} method --- creates a new +interpreter. The important thing to notice is that calling a +function---invoking the \texttt{\_\_call\_\_} method---creates a new \texttt{Frame} object and starts running it. \begin{verbatim} @@ -976,7 +976,7 @@ \texttt{None} or a string, called \texttt{why}, which is an extra piece of state the interpreter needs in some cases. These return values of the individual instruction methods are used only as internal indicators of -interpreter state --- don't confuse these with return values from +interpreter state---don't confuse these with return values from executing frames. \begin{verbatim} @@ -1328,9 +1328,10 @@ \aosasecti{Dynamic Typing: What the Compiler Doesn't Know}\label{dynamic-typing-what-the-compiler-doesnt-know} -One thing you've probably heard is that Python is a ``dynamic'' language ---- particularly that it's ``dynamically typed''. The context we've just -built up on the interpreter sheds some light on this description. +One thing you've probably heard is that Python is a ``dynamic'' +language---particularly that it's ``dynamically typed''. The context +we've just built up on the interpreter sheds some light on this +description. One of the things ``dynamic'' means in this context is that a lot of work is done at run time. We saw earlier that the Python compiler @@ -1353,8 +1354,8 @@ 4 \end{verbatim} -Calculating 19 \texttt{\%} 5 yields 4 --- no surprise there. What -happens if we call it with different kinds of arguments? +Calculating 19 \texttt{\%} 5 yields 4---no surprise there. What happens +if we call it with different kinds of arguments? \begin{verbatim} >>> mod("by%sde", "teco") @@ -1371,11 +1372,11 @@ Using the symbol \texttt{\%} to format a string for printing means invoking the instruction \texttt{BINARY\_MODULO}. This instruction mods -together the top two values on the stack when the instruction executes ---- regardless of whether they're strings, integers, or instances of a -class you defined yourself. The bytecode was generated when the function -was compiled (effectively, when it was defined) and the same bytecode is -used with different types of arguments. +together the top two values on the stack when the instruction +executes---regardless of whether they're strings, integers, or instances +of a class you defined yourself. The bytecode was generated when the +function was compiled (effectively, when it was defined) and the same +bytecode is used with different types of arguments. The Python compiler knows relatively little about the effect the bytecode will have. It's up to the interpreter to determine the type of @@ -1388,7 +1389,7 @@ itself). The compiler's ignorance is one of the challenges to optimizing Python -or analyzing it statically --- just looking at the bytecode, without +or analyzing it statically---just looking at the bytecode, without actually running the code, you don't know what each instruction will do! In fact, you could define a class that implements the \texttt{\_\_mod\_\_} method, and Python would invoke that method if you @@ -1404,8 +1405,8 @@ return a %b \end{verbatim} -Unfortunately, a static analysis of this code --- the kind of you can do -without running it --- can't be certain that the first \texttt{a \% b} +Unfortunately, a static analysis of this code---the kind of you can do +without running it---can't be certain that the first \texttt{a \% b} really does nothing. Calling \texttt{\_\_mod\_\_} with \texttt{\%} might write to a file, or interact with another part of your program, or do literally anything else that's possible in Python. It's hard to optimize @@ -1430,7 +1431,7 @@ I encourage you to disassemble your own programs and to run them using Byterun. You'll quickly run into instructions that this shorter version of Byterun doesn't implement. The full implementation can be found at -https://github.com/nedbat/byterun --- or, by carefully reading the real +https://github.com/nedbat/byterun---or, by carefully reading the real CPython interpreter's \texttt{ceval.c}, you can implement it yourself! \aosasecti{Acknowledgements}\label{acknowledgements} diff --git a/tex/pedometer.tex b/tex/pedometer.tex index c80f8d285..477679beb 100644 --- a/tex/pedometer.tex +++ b/tex/pedometer.tex @@ -150,11 +150,11 @@ $z(t)$ and constant at 9.8 $m/s^2$ in $y(t)$. Therefore, in our total acceleration plot, $x(t)$ and $z(t)$ fluctuate around 0 while $y(t)$ fluctuates around -1 \emph{g}. In our user acceleration plot, we notice -that --- because we have removed gravitational acceleration --- all -three time series fluctuate around 0. Note the obvious peaks in -$y_{u}(t)$. Those are due to step bounces! In our last plot, -gravitational acceleration, $y_{g}(t)$ is constant at -1 \emph{g}, and -$x_{g}(t)$ and $z_{g}(t)$ are constant at 0. +that---because we have removed gravitational acceleration---all three +time series fluctuate around 0. Note the obvious peaks in $y_{u}(t)$. +Those are due to step bounces! In our last plot, gravitational +acceleration, $y_{g}(t)$ is constant at -1 \emph{g}, and $x_{g}(t)$ and +$z_{g}(t)$ are constant at 0. So, in our example, the 1-dimensional user acceleration in the direction of gravity time series we're interested in is $y_{u}(t)$. Although @@ -648,8 +648,8 @@ \aosafigure[240pt]{pedometer-images/pipeline.png}{The pipeline}{500l.pedometer.pipeline} In the spirit of separation of concerns, we'll write the code for each -distinct component of the pipeline --- parsing, processing, and -analyzing --- individually. +distinct component of the pipeline---parsing, processing, and +analyzing---individually. \aosasectii{Parsing}\label{parsing} diff --git a/tex/same-origin-policy.tex b/tex/same-origin-policy.tex index bc784afb0..1bf567a18 100644 --- a/tex/same-origin-policy.tex +++ b/tex/same-origin-policy.tex @@ -18,9 +18,9 @@ design of the SOP has evolved organically over the years and puzzles many developers. -The goal of this chapter is to capture the essence of this important --- -yet often misunderstood --- feature. In particular, we will attempt to -answer the following questions: +The goal of this chapter is to capture the essence of this +important---yet often misunderstood---feature. In particular, we will +attempt to answer the following questions: \begin{aosaitemize} @@ -37,11 +37,11 @@ \end{aosaitemize} Covering the SOP in its entirety is a daunting task, given the -complexity of the parts that are involved --- web servers, browsers, -HTTP, HTML documents, client-side scripts, and so on. We would likely -get bogged down by the gritty details of all these parts (and consume -our 500 lines before even reaching SOP). But how can we hope to be -precise without representing crucial details? +complexity of the parts that are involved---web servers, browsers, HTTP, +HTML documents, client-side scripts, and so on. We would likely get +bogged down by the gritty details of all these parts (and consume our +500 lines before even reaching SOP). But how can we hope to be precise +without representing crucial details? \aosasecti{Modeling with Alloy}\label{modeling-with-alloy} @@ -165,13 +165,13 @@ first column containing URLs and the second column containing protocols. And the innocuous looking dot operator is in fact a rather general kind of relational join, so that you could also write \texttt{protocol.p} for -all the URLs with a protocol \texttt{p} --- but more on that later. +all the URLs with a protocol \texttt{p}---but more on that later. -Note that paths, unlike URLs, are treated as if they have no structure ---- a simplification. The keyword \texttt{lone} (which can be read -``less than or equal to one'') says that each URL has at most one port. -The path is the string that follows the host name in the URL, and which -(for a simple static server) corresponds to the file path of the +Note that paths, unlike URLs, are treated as if they have no +structure---a simplification. The keyword \texttt{lone} (which can be +read ``less than or equal to one'') says that each URL has at most one +port. The path is the string that follows the host name in the URL, and +which (for a simple static server) corresponds to the file path of the resource; we're assuming that it's always present, but can be an empty path. @@ -333,7 +333,7 @@ same type are distinguished by appending numeric suffixes to their names; if there is only one object of a given type, no suffix is added. Every name that appears in a snapshot diagram is the name of an object. -So --- perhaps confusingly at first sight --- the names \texttt{Domain}, +So---perhaps confusingly at first sight---the names \texttt{Domain}, \texttt{Path}, \texttt{Resource}, \texttt{Url} all refer to individual objects, not to types.) @@ -484,8 +484,8 @@ catalysts of the rapid development of Web 2.0, but is also the reason why the SOP was created in the first place. Without the SOP, scripts would be able to send arbitrary requests to servers, or freely modify -documents inside the browser --- which would be bad news if one or more -of the scripts turned out to be malicious. +documents inside the browser---which would be bad news if one or more of +the scripts turned out to be malicious. A script can communicate to a server by sending an \texttt{XmlHttpRequest}: @@ -539,8 +539,8 @@ API functions for accessing the DOM (e.g., \texttt{document.getElementById}), but enumerating all of them is not important for our purpose. Instead, we will simply group them into two -kinds --- \texttt{ReadDom} and \texttt{WriteDom} --- and model -modifications as wholesale replacements of the entire document: +kinds---\texttt{ReadDom} and \texttt{WriteDom}---and model modifications +as wholesale replacements of the entire document: \begin{verbatim} sig ReadDom extends BrowserOp { result: Resource }{ @@ -667,13 +667,13 @@ is \emph{secure}? Not surprisingly, this is a tricky question to answer. For our purposes, -we will turn to two well-studied concepts in information security --- -\emph{confidentiality} and \emph{integrity}. Both of these concepts talk -about how information should be allowed to pass through the various -parts of the system. Roughly, \emph{confidentiality} means that a -critical piece of data should only be accessible to parts that are -deemed trusted, and \emph{integrity} means that trusted parts only rely -on data that have not been maliciously tampered with. +we will turn to two well-studied concepts in information +security---\emph{confidentiality} and \emph{integrity}. Both of these +concepts talk about how information should be allowed to pass through +the various parts of the system. Roughly, \emph{confidentiality} means +that a critical piece of data should only be accessible to parts that +are deemed trusted, and \emph{integrity} means that trusted parts only +rely on data that have not been maliciously tampered with. \aosasectii{Dataflow Properties}\label{dataflow-properties} @@ -860,8 +860,8 @@ server into responding with the user's private data (\texttt{MyInboxInfo}). Here, the problem is again related to the liberal ways in which a script may be used to access information across -different domains --- namely, that a script executing under one domain -is able to make an HTTP request to a server with a different domain. +different domains---namely, that a script executing under one domain is +able to make an HTTP request to a server with a different domain. These two counterexamples tell us that extra measures are needed to restrict the behavior of scripts, especially since some of those scripts @@ -909,7 +909,7 @@ The second part of the policy says that a script cannot send an HTTP request to a server unless its context has the same origin as the target -URL --- effectively preventing instances such as the second script +URL---effectively preventing instances such as the second script scenario. \begin{verbatim} @@ -1465,7 +1465,7 @@ stage of system design. Besides the SOP, Alloy has been used to model and reason about a variety -of systems across different domains --- ranging from network protocols, +of systems across different domains---ranging from network protocols, semantic web, bytecode security to electronic voting and medical systems. For many of these systems, Alloy's analysis led to discovery of design flaws and bugs that had eluded the developers, in some cases, for @@ -1524,7 +1524,7 @@ \texttt{Browser} signature. In this sense, \texttt{Time} objects are nothing but helper objects used as a kind of index. -Each call occurs between two points in time --- its \texttt{start} and +Each call occurs between two points in time---its \texttt{start} and \texttt{end} times, and is associated with a sender (represented by \texttt{from}) and a receiver (\texttt{to}): diff --git a/tex/spreadsheet.tex b/tex/spreadsheet.tex index 49ded8dd7..5083be2ac 100644 --- a/tex/spreadsheet.tex +++ b/tex/spreadsheet.tex @@ -1,11 +1,8 @@ \begin{aosachapter}{Web Spreadsheet}{s:spreadsheet}{Audrey Tang} -This chapter introduces a -\href{http://audreyt.github.io/500lines/spreadsheet/}{web spreadsheet} -written in -\href{https://github.com/audreyt/500lines/tree/master/spreadsheet/code}{99 -lines} of the three languages natively supported by web browsers: HTML, -JavaScript, and CSS. +This chapter introduces a web spreadsheet written in 99 lines of the +three languages natively supported by web browsers: HTML, JavaScript, +and CSS. The ES5 version of this project is available as a \href{http://jsfiddle.net/audreyt/LtDyP/}{jsFiddle}. diff --git a/tex/static-analysis.tex b/tex/static-analysis.tex index 532d7e2cc..8cf9c6ebd 100644 --- a/tex/static-analysis.tex +++ b/tex/static-analysis.tex @@ -12,7 +12,7 @@ running it. ``Static'' means at compile time rather than at run time, and ``analysis'' means we're analyzing the code. When you've used the tools I mentioned above, it may have felt like magic. But those tools -are just programs --- they are made of source code that was written by a +are just programs---they are made of source code that was written by a person, a programmer like you. In this chapter, we're going to talk about how to implement a couple of static analysis checks. In order to do this, we need to know what we want the check to do and how we want to @@ -390,10 +390,9 @@ hinges on it. We've looked at an unstable function and seen, as programmers, how to identify an unstable variable, but we need our program to find them. This sounds like it would require simulating the -function to look for variables whose values might change --- which -sounds like it would take some work. Luckily for us, Julia's type -inference already traces through the function's execution to determine -the types. +function to look for variables whose values might change---which sounds +like it would take some work. Luckily for us, Julia's type inference +already traces through the function's execution to determine the types. The type of \texttt{sum} in \texttt{unstable} is \texttt{Union(Float64,Int64)}. This is a \texttt{UnionType}, a special @@ -415,8 +414,8 @@ Besides all the work we'll save by not having to parse the code by ourselves, working with the same data structures that the compiler uses means that our checks will be based on an accurate assessment of the -compilers understanding --- which means our check will be consistent -with how the code actually runs. +compilers understanding---which means our check will be consistent with +how the code actually runs. This process of examining Julia code from Julia code is called introspection. When you or I introspect, we're thinking about how and @@ -1020,7 +1019,7 @@ layer of analysis to catch them. We can find misspelled variable names (and other unused variables) by -looking for variables that are only used once --- or only used one way. +looking for variables that are only used once---or only used one way. Here is an example of a little bit of code with one misspelled name. @@ -1039,15 +1038,15 @@ discovered when it's run. Let's assume you misspell each variable name only once. We can separate variable usages into writes and reads. If the misspelling is a write (i.e., \texttt{worng = 5}), then no error will be -thrown; you'll just be silently putting the value in the wrong variable ---- and it could be frustrating to find the bug. If the misspelling is a -read (i.e., \texttt{right = worng + 2}), then you'll get a runtime error -when the code is run; we'd like to have a static warning for this, so -that you can find this error sooner, but you will still have to wait -until you run the code to see the problem. +thrown; you'll just be silently putting the value in the wrong +variable---and it could be frustrating to find the bug. If the +misspelling is a read (i.e., \texttt{right = worng + 2}), then you'll +get a runtime error when the code is run; we'd like to have a static +warning for this, so that you can find this error sooner, but you will +still have to wait until you run the code to see the problem. As code becomes longer and more complicated, it becomes harder to spot -the mistake --- unless you have the help of static analysis. +the mistake---unless you have the help of static analysis. \aosasectii{Left-Hand Side and Right-Hand Side}\label{left-hand-side-and-right-hand-side} @@ -1379,7 +1378,7 @@ \aosasecti{Conclusion}\label{conclusion} -We've done two static analyses of Julia code --- one based on types and +We've done two static analyses of Julia code---one based on types and one based on variable usages. Statically-typed languages already do the kind of work our type-based diff --git a/tex/template-engine.tex b/tex/template-engine.tex index f49e8f070..75172857c 100644 --- a/tex/template-engine.tex +++ b/tex/template-engine.tex @@ -223,8 +223,6 @@ \begin{verbatim} {% if user.is_logged_in %}

Welcome, {{ user.name }}!

-{% else %} -

Log in

{% endif %} \end{verbatim} @@ -628,11 +626,11 @@ that implementation detail in the template engine code, and out of our CodeBuilder class. -Even as we're actually using it --- to define a single function --- -having \texttt{get\_globals} return the dictionary keeps the code more -modular because it doesn't need to know the name of the function we've -defined. Whatever function name we define in our Python source, we can -retrieve that name from the dict returned by \texttt{get\_globals}. +Even as we're actually using it---to define a single function---having +\texttt{get\_globals} return the dictionary keeps the code more modular +because it doesn't need to know the name of the function we've defined. +Whatever function name we define in our Python source, we can retrieve +that name from the dict returned by \texttt{get\_globals}. Now we can get into the implementation of the Templite class itself, and see how CodeBuilder is used. @@ -1340,7 +1338,7 @@ \item Arguments to filters \item - Complex logic like elif and for/else + Complex conditional logic like else and elif \item Loops with more than one loop variable \item diff --git a/tex/web-server.tex b/tex/web-server.tex index e48fee3e3..e8842cc2b 100644 --- a/tex/web-server.tex +++ b/tex/web-server.tex @@ -408,12 +408,12 @@ self.handle_error(msg) \end{verbatim} -Note that we open the file in binary mode --- the `b' in `rb' --- so -that Python won't try to ``help'' us by altering byte sequences that -look like a Windows line ending. Note also that reading the whole file -into memory when serving it is a bad idea in real life, where the file -might be several gigabytes of video data. Handling that situation is -outside the scope of this chapter. +Note that we open the file in binary mode---the `b' in `rb'---so that +Python won't try to ``help'' us by altering byte sequences that look +like a Windows line ending. Note also that reading the whole file into +memory when serving it is a bad idea in real life, where the file might +be several gigabytes of video data. Handling that situation is outside +the scope of this chapter. To finish off this class, we need to write the error handling method and the template for the error reporting page: diff --git a/web-server/web-server.markdown b/web-server/web-server.markdown index 7cfdc7066..8c560b771 100644 --- a/web-server/web-server.markdown +++ b/web-server/web-server.markdown @@ -458,7 +458,7 @@ and uses our existing `send_content` to send it back to the client: self.handle_error(msg) ``` -Note that we open the file in binary mode --- the 'b' in 'rb' --- so that +Note that we open the file in binary mode—the 'b' in 'rb'—so that Python won't try to "help" us by altering byte sequences that look like a Windows line ending. Note also that reading the whole file into memory when serving it is a bad idea in real life, where the file might be several gigabytes of video data. @@ -805,9 +805,9 @@ the core idea is simple: 2. Capture whatever that subprocess sends to standard output. 3. Send that back to the client that made the request. -The full CGI protocol is much richer than this---in particular, +The full CGI protocol is much richer than this—in particular, it allows for parameters in the URL, -which the server passes into the program being run---but +which the server passes into the program being run—but those details don't affect the overall architecture of the system... ...which is once again becoming rather tangled.