An Algorithmic Lucidity

a blog

The Foundations of Erasure Codes

(cross-posted from the SwiftStack Blog)

In enabling mechanism to combine together general symbols, in successions of unlimited variety and extent, a uniting link is established between the operations of matter and the abstract mental processes of the most abstract branch of mathematical science. A new, a vast, and a powerful language is developed for the future use of analysis, in which to wield its truths so that these may become of more speedy and accurate practical application for the purposes of mankind [sic] than the means hitherto in our possession have rendered possible.

—Ada Lovelace on Charles Babbage's Analytical Engine, 1842

Dear reader, if you're reading [the SwiftStack Blog], you may have already heard that erasure codes have been added to OpenStack Swift (in beta for the 2.3.0 Kilo release, with continuing improvements thereafter) and that this is a really great thing that will make the world a better place.

All of this is entirely true. But what is perhaps less widely heard is exactly what erasure codes are and exactly why their arrival in Swift is a really great thing that will make the world a better place. That is what I aim to show you in this post—and I do mean show, not merely tell, for while integrating erasure codes into a production-grade storage system is (was!) an immense effort requiring months of work by some of the finest programmers the human race has to offer, the core idea is actually simple enough to fit in a (longish) blog post. Indeed, by the end of this post, we will have written a complete working implementation of a simple variant of Reed–Solomon coding, not entirely unlike what is used in Swift itself. No prior knowledge will be assumed except a working knowledge of high-school algebra and the Python programming language.

But first, we need to understand the problem that erasure codes solve. The strategy Swift has traditionally used to achieve its reliability and fault-tolerance properties is replication: keep more than one copy of each object (typically three), preferably in entirely different datacenters, failing that, on different machines, and failing that, at least on different hard drives. Your data is safe from occasional drive failures because the probability of all the drives containing a particular object failing at the same time is very, very small.

The problem with replication is that it's expensive: if you keep three replicas, then for every terabyte that you want to use, you have to pay for three terabytes of actual physical storage. The cost would appear to be unavoidable, unless ... unless there were some way to reap the benefits of distributing the information across different failure domains without storing the entire object at each location ...

"But surely this is impossible!" I hear you cry. "It's useless to make half a copy of something, because you can't know in advance of a disaster whether the half you made a backup of is the half that will need to be restored. In order to enjoy the safety of having a spare, you need a spare of the whole thing."

My dear reader, this objection is compelling, well-stated—and gloriously, one-hundred-percent wrong. We can achieve reliability guarantees similar to that of the replication strategy, keeping our data safe even as some of its fragments are damaged, lost, or erased. (Hence the name, erasure codes.) The method will have its own costs in the form of increased CPU load and more network requests; it won't make sense for all use cases, but when appropriate, the efficiency gain is impressive. It all depends on applying a deep philosophical insight into the nature of space itself.

Specifically: two points make a line.

Given any two distinct points on a plane, there is one and exactly one line that passes through both of them. We reconstruct anything we might want to know about a particular line just by remembering two points that it passes through.

But suppose we were to remember three points. Then we could still reconstruct the line from any two of them, which means that the information about our line hasn't been lost even if we forget one of the points.

graph of a cubic polynomial curve passing through four marked points

Similarly, three points make a parabola, four points make a cubic curve, and in full generality, m+1 points make a degree-m polynomial. Given n points on a polynomial curve where n is greater than m+1, any m+1 of them suffice to reconstruct the polynomial.

Thus, we have a clear strategy for storing data in a reliable, failure-tolerant way, without going to the expense of storing complete replicas: all we have to do is pretend our data is made out of polynomials, and store more points than are strictly necessary to reconstruct the data.

But don't take my word for it! Mere verbal arguments can be deceptive, but code is proof and code is truth, so if you still doubt that such an idea can really be made to work—and maybe you should—you won't after we're done implementing it.

So suppose we want to save some textual data; say, the string, "THE FUNDAMENTAL PROBLEM OF COMMUNICATION IS THAT OF REPRODUCING AT ONE POINT EITHER EXACTLY OR APPROXIMATELY A MESSAGE SELECTED AT ANOTHER POINT". Now, this data is made out of letters and spaces, not polynomials, but we can process it into a form that will make it easier to make-believe that it is. Say, we split the text into chunks of a fixed size (padding the end with extra spaces if necessary so that our chunk size evenly divides it), and convert the characters into integers from 0 to 26 (space is 0, A is 1, B is 2, &c.). Here are some functions to help with that—

from string import ascii_uppercase

ALPHABET = " "+ascii_uppercase
CHAR_TO_INT = dict(zip(ALPHABET, range(27)))
INT_TO_CHAR = dict(zip(range(27), ALPHABET))

def pad(text, chunk_size):
    return text + ' '*(chunk_size - len(text) % chunk_size)

def chunkify(text, chunk_size):
    return [text[i:i+chunk_size]
            for i in range(0, len(text), chunk_size)]

def convert(string):
    return [CHAR_TO_INT[c] for c in string]

After turning our text into converted chunks (lists of integers), we can interpret each chunk as representing the coefficients of a polynomial function: say, in order of increasing degree, so that, e.g., the list [1, 2, 3] represents the function \(1 + 2x + 3x^2\). Then we can take points on that polynomial at \(n\) different values of the independent variable \(x\) for some \(n\) greater than the chunk size to get a properly redundant encoding.

(It's actually better if you use polynomials over the finite field \(\mathbb{F}_q\) of the integers modulo \(q\) for some \(q\) which is a prime raised to the power of something, but let's not worry about that.)

def evaluate_polynomial(coefficients, x):
    return sum(c * x**i for i, c in enumerate(coefficients))

def encode(chunk, n):
    return [evaluate_polynomial(chunk, i) for i in range(n)]

def erasure_code(text, chunk_size, encoded_chunk_size):
    chunks = chunkify(pad(text, chunk_size), chunk_size)
    converted_chunks = [convert(chunk) for chunk in chunks]
    return [list(enumerate(encode(chunk, encoded_chunk_size)))
            for chunk in converted_chunks]

Then, with a choice for the original chunk size (which you'll recall will also be the number of terms each in the polynomials used to encode each chunk) and the size of the resulting encoded chunk (that is, the number of points we'll sample from the polynomials), we can encode our text.

$ python3
>>> from reed_solomon import *
>>> text = "THE FUNDAMENTAL PROBLEM OF COMMUNICATION IS THAT OF
REPRODUCING AT ONE POINT EITHER EXACTLY OR APPROXIMATELY A MESSAGE
SELECTED AT ANOTHER POINT"
>>> encoded = erasure_code(text, 5, 8)
>>> encoded
[[(0, 20), (1, 39), (2, 152), (3, 575), (4, 1668), (5, 3935), (6,
8024), (7, 14727)], [(0, 21), (1, 53), (2, 281), (3, 1179), (4, 3533),
(5, 8441), (6, 17313), (7, 31871)], [(0, 5), ...

[further output redacted]

At this point, our text has been transformed into a Python list, whose elements are Python lists representing the individual chunks, whose elements are tuples representing (x, y) coordinate pairs representing points on the polynomial representing that chunk.

Let's simulate distributing that encoded information across several storage nodes by writing points with different x-values to different files. We'll make-believe that each file is a different storage node. We'll write another function for that.

import json

def disperse(encoded_chunks):
    node_count = len(encoded_chunks[0])
    for i in range(node_count):
        with open('node'+str(i), 'w') as node:
            node.write(json.dumps([chunk[i] for chunk in encoded_chunks]))

And try it out—

>>> disperse(encoded)
>>>
$ ls
node0  node1  node2  node3  node4  node5  node6  node7
$ cat node4
[[4, 1668], [4, 3533], [4, 3517], [4, 1824], [4, 4080], [4, 4342],
[4, 1665], [4, 4769], [4, 5460], [4, 4172], [4, 4710], [4, 2254], [
4, 433], [4, 2436], [4, 4464], [4, 5796], [4, 1596], [4, 4428], [4,
 1417], [4, 5313], [4, 5452], [4, 709], [4, 6212], [4, 4973], [4, 5
445], [4, 5205], [4, 6308], [4, 4412], [4, 1555]]

In conclusion, that's how you use Reed–Solomon coding to turn comprehensible English text into inscrutable lists of lists of numbers distributed across several files. Thank you, and—

What's that you say, dear reader? Demonstrating how to encode something is useless unless you also demonstrate how to decode it? Well, I suppose you may have a point. Never fear—we can do that, too! But first, we'll need some functions for manipulating polynomials (in the "list of coefficients in order of ascending power" form that we've been using).

def get_coefficient(P, i):
    if 0 <= i < len(P):
        return P[i]
    else:
        return 0

def add_polynomials(P, Q):
    n = max(len(P), len(Q))
    return [get_coefficient(P, i) + get_coefficient(Q, i) for i in range(n)]

def scale_polynomial(P, a):
    return [a*c for c in P]

def multiply_polynomials(P, Q):
    maximum_terms = len(P) + len(Q)
    R = [0 for _ in range(maximum_terms)]
    for i, c in enumerate(P):
        for j, d in enumerate(Q):
            R[i+j] += c * d
    return R

Once we can do arithmetic with polynomials, we can write functions to reconstruct the polynomial representing a chunk of our text given our saved points, which is probably the most intricate part of this entire endeavor. We'll use a technical trick called Lagrange interpolation, after the great mathematician-astronomer Joseph-Louis Lagrange.

Suppose we want to reconstruct a cubic polynomial from the four points \((x_1, y_1)\), \((x_2, y_2)\), \((x_3, y_3)\), and \((x_4, y_4)\). It turns out that a formula for the polynomial is

$$y_1\ell_1(x) + y_2\ell_2(x) + y_3\ell_3(x) + y_4\ell_4(x)$$

where \(\ell_1(x)\) (the first Lagrange basis element) stands for

$$\frac{(x - x_2)(x - x_3)(x - x_4)}{(x_1 - x_2)(x_1 - x_3)(x_1 - x_4)}$$

and so on—for each \(i\) between 1 and the number of points we have, the numerator of the \(i\)th Lagrange basis element is the product of \((x - x_j)\) for all \(j\) from 1 up to the number of points we have but not equal to \(i\), and the denominator follows a similar pattern but with \(x_i\) instead of \(x\). (Note that we're using letters with subscripts, like \(x_i\), to represent specific constants, whereas \(x\) without a subscript is a function's independent variable.)

I hear you ask, "But why this particular arbitrary-looking formula out of the space of all possible arbitrary-looking formulae?" But the grace and beauty of this formula is exactly that it's engineered specifically to pass through our points. Consider what happens when we choose \(x\) equal to \(x_1\). The second through fourth terms \(y_2\ell_2(x_1)\) through \(y_4\ell_4(x_1)\) all contain a factor of \((x_1 - x_1)\) and are thus zero, but the first term becomes

$$y_1\frac{(x_1 - x_2)(x_1 - x_3)(x_1 - x_4)}{(x_1 - x_2)(x_1 - x_3)(x_1 - x_4)}$$
$$= y_1(1)$$
$$= y_1$$

So by design, our interpolated polynomial takes value \(y_1\) at \(x_1\), \(y_2\) at \(x_2\), and so forth. In Python, the whole process looks like this—

def lagrange_basis_denominator(xs, i):
    denominator = 1
    for j, x in enumerate(xs):
        if j == i:
            continue
        denominator *= xs[i] - xs[j]
    return denominator

def lagrange_basis_element(xs, i):
    element = [1]
    for j in range(len(xs)):
        if j == i:
            continue
        element = multiply_polynomials(element, [-xs[j], 1])
    scaling_factor = 1/lagrange_basis_denominator(xs, i)
    return scale_polynomial(element, scaling_factor)

def interpolate(points):
    result = [0]
    xs, ys = zip(*points)
    for i in range(len(points)):
        result = add_polynomials(
            result,
            scale_polynomial(lagrange_basis_element(xs, i), ys[i])
        )
    return [round(k) for k in result]

(Note that we're rounding off our computed coefficients because this implementation isn't very numerically stable—the subtle differences between true real-number arithmetic and the approximate floating-point arithmetic implemented by computers start to accumulate, and if we choose too large of a chunk size, our program will actually start giving the wrong answers—but let's not worry about that, either.)

With this technique, we now have all the tools we need to recover our text from a subset of the data we wrote to our various "nodes" earlier. What we need to do is this: for each chunk, arbitrarily select a number of stored points equal to our chunk size, interpolate the polynomial from them, deconvert the numbers which are the coefficients of that polynomial back into their character equivalents, unchunkify the chunks into a unified whole, and unpad any whitespace we added to the end when we began.

def deconvert(sequence):
    return ''.join(INT_TO_CHAR[i] for i in sequence)

def unchunkify(chunks):
    return ''.join(chunks)

def unpad(text):
    return text.rstrip()

def erasure_decode(encoded_chunks, chunk_size, encoded_chunk_size):
    converted_chunks = [interpolate(chunk[:chunk_size])[:chunk_size]
                        for chunk in encoded_chunks]
    return unpad(unchunkify(deconvert(chunk) for chunk in converted_chunks))

But about that data that we wrote out earlier.

$ ls
node0  node1  node2  node3  node4  node5  node6  node7

It would hardly be a compelling test of our erasure-coding skills if there were any suspicion that we actually needed all of those files—we really only need as many as our chunk size. So let's suppose that three of our nodes die in a fire—

$ rm node1 node3 node6
$ ls
node0  node2  node4  node5  node7

Could this the end of our data? With a full three-eighths of our encoding having been utterly destroyed, is it delusional to hold out hope that our text might yet be faithfully recovered? No! No, it is not! We only need one more function to retrieve the encoded chunks—

def retrieve(*nodes):
    responses = []
    for node in nodes:
        with open(node) as our_node:
            responses.append(json.loads(our_node.read()))
    return [[response[i] for response in responses]
            for i in range(len(responses[0]))]

—and then—

$ python3
>>> from reed_solomon import *
>>> node_data = retrieve("node0", "node2", "node4", "node5", "node7")

—successfully decode them!

>>> erasure_decode(node_data, 5, 8)
'THE FUNDAMENTAL PROBLEM OF COMMUNICATION IS THAT OF REPRODUCING AT
 ONE POINT EITHER EXACTLY OR APPROXIMATELY A MESSAGE SELECTED AT AN
OTHER POINT'

Dear reader, it is true our toy implementation here was crude, the hundred-and-change bytes of data we demonstrated it on was of no intrinsic interest, and many obvious and not-so-obvious subtleties were ignored. But I implore you to consider the implications for your own storage needs of more advanced, not-merely-educational application of these vast and powerful techniques. Imagine just how soundly you'll be able to sleep at night knowing that you're well under your budget and yet your data is just as safe as if you had three complete independent copies of it!

And even if you personally have no intention of deploying Swift—as my SwiftStack colleague and project technical lead for OpenStack Swift John Dickinson has pointed out, we are rapidly entering an era in which everyone uses object storage, whether they realize it or not. In a hyperconnected global economy, even minor efficiency improvements in key infrastructure components can reap enormous benefits elsewhere, which is to say that the rest of your life will contain more happiness and less pain if the financial institution that invests your retirement savings, or the medical research institute that develops a cure for the cancer you'll get twenty years from now, or the image hosting service that serves you cute cat pictures today, have access to cheaper, faster, and more reliable storage than the means hitherto in our possession have rendered possible. And that's why erasure codes being in OpenStack Swift is a really great thing that will make the world a better place; quod erat demonstrandum.

The code in this post is available separately.

T.O.P.

"Don't worry, we've got our T.O.P. engineer working on it," said the support man on the phone with our most important customer, glancing meaningfully across the open-plan office in my direction; I winced briefly, then spasmed back towards my screen and fumbled with the keyboard, intending to return my attention to the definition of the DeviceAssignmentRuleComponentManagerFactory, but somehow fat-fingering C-x C-c along the way, every awkward, ungainly movement bearing testimony to the most casual of onlookers that I was Totally Observably Pathetic.

Post-Ingress

Her tense reaction was contorted,
Hands slid forward to will defense from the ransack, in shock,
Whispered: "Current transaction is aborted;
Commands ignored until the end of the transaction block."

Epistolary

(Previously.)

[19:26:50] <bob>    alice: you still around?
[19:27:08] <alice>  bob, sort of
[19:27:20] <bob>    alice: ok. never mind.
[19:27:41] <alice>  bob, what were you going to ask? I am at the office, 
                    trying to finish up an email but I'm really slow at 
                    choosing words
[19:28:21] <bob>    alice: i was just wondering if you happened to know a 
                    way to manually foo the bar-quuxing device
[19:28:22] <alice>  perhaps because of my overly-ornate and wordy writing 
                    style, which, for not-well-understood psychological 
                    reasons, I nevertheless continue to use despite its 
                    obvious disadvantages in business communication

XXX III

const PSEUDO_DIGITS: [char; 7] = ['M', 'D', 'C', 'L', 'X', 'V', 'I'];
const PSEUDO_PLACE_VALUES: [usize; 7] = [1000, 500, 100, 50, 10, 5, 1];

#[allow(unused_parens)]
fn integer_to_roman(integer: usize) -> String {
    let mut remaining = integer;
    let mut bildungsroman = String::new();
    // get it?? It sounds like _building Roman_ (numerals), but it's
    // also part of the story about me coming into my own as a
    // programmer by learning a grown-up language
    //
    // XXX http://tvtropes.org/pmwiki/pmwiki.php/Main/DontExplainTheJoke
    for ((index, value), &figure) in PSEUDO_PLACE_VALUES.iter()
        .enumerate().zip(PSEUDO_DIGITS.iter())
    {
        let factor = remaining / value;
        remaining = remaining % value;

        if figure == 'M' || factor < 4 {
            for _ in 0..factor {
                bildungsroman.push(figure);
            }
        }

        // IV, IX, XL, &c.
        let smaller_unit_index = index + 2 - (index % 2);
        if smaller_unit_index < PSEUDO_PLACE_VALUES.len() {
            let smaller_unit_value = PSEUDO_PLACE_VALUES[smaller_unit_index];
            let smaller_unit_figure = PSEUDO_DIGITS[smaller_unit_index];

            if value - remaining <= smaller_unit_value {
                bildungsroman.push(smaller_unit_figure);
                bildungsroman.push(figure);
                remaining -= (value - smaller_unit_value);
            }
        }
    }
    bildungsroman
}

Mock

Some people, when confronted with a Python unit-testing problem, think, "I know, I'll use mock." Now they have <MagicMock name='two_problems' id='140279267635776'>.

XXX II

// XXX: old_io is probably facing deprecation if names mean anything
#![feature(old_io)]
use std::old_io;
use std::collections::HashMap;

fn main() {
    let things_to_ask_about = ["name", "age", "username"];
    let mut collected_information = HashMap::new();
    for askable in things_to_ask_about.iter() {
        println!("What is your {}?", askable);
        let input = old_io::stdin()
            .read_line()
            .ok().expect("failure message here");
        // XXX EVIDENCE OF MY IMPENDING DEATH in these moments when I
        // want to scream with the righteous fury of a person who has
        // been genuinely wronged, on the topic of what the fuck is wrong
        // with this bullshit language where you can't even trim a string
        // because "`input` does not live long enough" this and "borrowed
        // value is only valid for the block suffix following statement 1
        // at 21:48" that
        //
        // But what the fuck is wrong with this bullshit language is in
        // the map, not the territory
        //
        // on the balance of available evidence, doesn't it seem more
        // likely that the borrow checker is smarter than you, or that
        // the persons who wrote the borrow checker are smarter than you?
        //
        // and if you can't even follow their work even after several
        // scattered hours of dutifully trying to RTFM, will an
        // increasingly competitive global Economy remain interested in
        // keeping you alive and happy in the decades to come?
        //
        // I am not a person who has been genuinely wronged, just a man
        // not smart enough to know any better
        collected_information.insert(askable, input.trim());
    }

    for (askable, response) in collected_information.iter() {
        println!("You claimed that your {} is {}.", askable, response);
    }
}

"Pi Day" Is an Unholy Festival of Sin That Is Corrupting Our Children

Dear reader, it's the fourteenth day of the third month of the year, and if you're reading this blog, some charlatans or overenthusiastic youth (the subject of whose enthusiasm is not what they think it is) have probably tried to convince you to celebrate it as "Pi Day." You see (these quacks implored you) π is around 3.14, and March fourteenth is 3/14. And furthermore (they may have put to you) furthermore this year's Pi Day is special, because it's 3/14/15, which is like 3.1415! Why (an especially impudent few might have continued to venture), we should plan some grand spectacle on 9:26 a.m. on the day, which is like 3.1415926! With (and this is the part that is most inevitable and offensive) pie! Get it, because it sounds like pi and is shaped like a circle?

Dear reader, it is lies or it is worse than lies; it is blasphemy, treason, superstitious superficiality, degenerate folderol, and frivolous depravity! Do not mistake me; of course I can see as clearly as any other ape can that the numeric subsequence of the string "3.14" is same as that of the string "3/14". The former string represents an occasionally useful approximation of the circle constant which is ubiquitous in mathematics (give or take a factor of two); the latter is how people in my country abbreviate today's date. Perhaps to those who don't have anything really interesting to think about, this trivial coincidence might be worth a passing mention; apes love anything for an innocent distraction, and why begrudge that?

What is intolerable, however, is for a mathematically meaningless coincidence to be marketed as a day to celebrate mathematics, which marketing can only propagate the cruel slander that mathematics is about memorizing and manipulating figures. Not that seekers of true mathematical knowledge don't have occasion to manipulate figures from time to time—we do—but we do it because the figures actually mean something. And the similarity between the decimal number "3.14" and the date "3/14" ... doesn't really mean anything. Our dominant culture happens to prefer a base-ten place-value system, in which the representation of the quantity π happens to start with 3.14. That means: three (times ten-to-the-zeroth), plus one-tenth (which is one times ten-to-the-negative-first), plus four one-hundredths (which is four times ten-to-the-negative-second), plus other terms that our to-two-decimal-places approximation neglects. Whereas 3/14 means: the fourteenth day of the third month. It's not the same number as 3.14, even if its standard representation happens to involve the same digits in the same order.

If there must be a Pi Day, I can imagine sensible arguments for holding it once every three years and fifty-two days (around π years), or on January fourth at 3:23 a.m. (about π days into the new year), or on one of the solstices or equinoxes (when the Earth is π radians around the sun from some imagined "zero" at the opposite solstice or equinox).

But March fourteenth? What is wrong with the world such that such a travesty could gain common currency? Can't our innocent distractions at least rise to making a pretense of meaning something? Don't our cultural symbols deserve to have semantics deeper than mere empty tokens that can only be recognized, compared, and gawked at?

For the children.

Permalink or It Didn't Happen

As far as I can tell, I don't have any kind of synesthesia. You can't be too sure (which means, you can easily be entirely too sure), what with our na(t)ive theories of psychology being so inadequate that everything we believe about other minds is but a filament of noise and conjecture, but your probability distribution about the mapping of sensory inputs to perceptions for me is probably not so different as mine of the same for you (dear reader of whom I know nothing)—roses seem red, violets would seem blue if we spoke a language that didn't already have a word for violet—which means that when I tell you that there's a musty, stale odor around a blog that hasn't been updated in a month and change, it's only a trite metaphor and not a perceptual reality of any sort. Still, even if you can't smell it (if your senses are like mine; if your fox, like mine, still hasn't bothered to implement the HTML5 <aroma> element), it's an ominous thing, to see a blog hovering near the boundary between life and death, a corpus perhaps on the way to being a corpse. The internet is littered with the latter, monuments to people who reliably had something to say, month after month ... until they missed a month, and then it wasn't long before they missed another.

a GitHub-style contribution graph of green squares

Now I can assure you that that will never happen to this place while I'm still breathing—this blog lives exactly as long as I do—only that's not a precise way of speaking; what I can do is offer you my assurance, which is a different thing from you actually feeling assured, which is a different thing still from that which was assured against actually never coming to pass. But I think these differences—between feeling and reality, between saying and reality—I think these enormous differences are much greater than the tiny, barely-perceptible gap between seeing so many gloriously intricate things to say, and making the time and words to express them on your blog when you are so busy with your trade in the manufacture of useful machinery (and the green tiles which are its highly-coveted industrial byproduct). But if all I can observe is that the gap is barely perceptible, then by the enormity of the earlier differences, I am not licensed to infer that the gap is tiny, not when the only reason I am telling you this is that I would die of shame if my monthly archives sidebar skipped a month for the first time since May of 'aught-twelve, not during this second year of my life in which I am supposed to write a compiler and a bad novelette even though it is for all intents and tens of intensive purposes practically March.

Speculative Rules of Engagement

"Whoever displays intense negative emotion first, loses" is not in any way a law inherent to the nature of interpersonal conflict, but we can make-believe that it were profitable to believe as much. What would that look like?

XXX I

;; XXX: adorable
(defmacro λ [& code]
  `(fn ~@code))

2014 Year in Reverse

(Previously.)

If I had any readers who still believe in the A-theory of time, I might say: 2014 is dead! Gone! Over! But since I probably don't have any readers like that (since I probably don't have any readers, full stop?), it's better to face the truth: 2014 is an immutable part of our universe; just because we don't—get to?—have to?—experience it "now", doesn't mean it has "stopped" existing, any more than 2016 doesn't exist "yet" just because we don't remember it.

Anyway. In that two-thousand-and-fourteenth year of our Common Era, the first year of my life (that I feel comfortable admitting to), and (unfortunately) not actually the Year of the Em Dash, this blog saw 45 posts and 40 comments. Among these—

The weariness of being monolingual was confessed to. We saw how to convert Markdown to HTML within Emacs (a technique which is proving itself to be of some convenience to your author in preparing blog posts for publication). We considered one weird trick for what to write when you can't infer the correct spelling of someone's name from what you heard. It turned out that the word apology can mean different things, and that characters in popular 1990s science-fiction television programs aren't always completely honest in interpreting the moral law. We were prompted to prove why we will never write anything. We had a wild Halloween party, noted a baffling error message from Git (hint: commit hooks and virtualenv), and drowned our sorrows in tower defense. The American coffee hegemon started serving pumpkin spice again. There were feelings of inadequacy, at least one contrived distraction from writing that ineffectually pretended to not be a distraction, and the occasional obscure pun. We examined where I stand and were enlightened by some standard advice. There were more feelings of inadequacy. Even conditional on the hypothesis that all's well that ends well, I think it's important to consider the condition of people for which all is not looking to end well. We heard a poem for OpenStack object storage, and a lament against git push --force. I argued that Twilight Sparkle is a disaster waiting to happen and confessed that perhaps too many of my life decisions are determined by what things GitHub happens to provide graphs for. I ate too much ice-cream once and explained how consistent hashing works.

And as for that other nearby immutable span of reality, the one called 2015? Well, that would be telling (and I can't know that from here).

The Year of the Em Dash, Not

"2014 is the Unicodepoint for the em dash! Isn't that the greatest thing ever? How did I not know this before December of this glorious year?"

"That's two zero one four in hex, dummy. It's not the same number."

"But, but—that means the year of the em dash isn't until—four, plus sixteen, plus two-to-the-thirteenth ... the year eighty-two twelve! I'll probably be dead by then!"

"Well, you can still celebrate the year of the N'ko letter Ka."

"That is small consolation, my friend!"

Native Tongue

"Don't you ever get tired of coding everything in Python?"

"Do you ever get tired of saying everything in English?"

A beat. In unison: "Yes."

Convert Markdown to HTML Within Emacs Using Pandoc

Okay, so there actually is a pandoc-mode, but I couldn't figure out how to configure and use it, so it was easier to just write the one command that I wanted—

(defun markdown-to-html ()
  (interactive)
  (let* ((basename (file-name-sans-extension (buffer-file-name)))
         (html-filename (format "%s.html" basename)))
    (shell-command (format "pandoc -o %s %s"
                           html-filename (buffer-file-name)))
    (find-file-other-window html-filename)))

Coffee Names

"Hi, could I have a grande vanilla iced-coffee?"

"And your name?"

"Zack."

"Is that Zach with an ch or Zack with a ck?"

"You know, I've even seen it done with just a c. But really, isn't this what we have regular expressions for?"

"What?"

"May I?"

The barista hands over the pen and cup, whereupon the customer writes:

/Zac[hk]?/

"There. Now you can't possibly be wrong!"

Missing Words VI

We need different words for apology in the sense of "I'm sorry; I won't do it again," and apology in the sense of "I'm sorry that this lowers your utility, but not sorry enough to actually change the behavior in question; maybe we could negotiate some other behavior change that might partially make up for it." Both can be sincere, but they mean different things.

Yet Another Idle Wish for a Future Star Trek Series

(Previously, previously on An Algorithmic Lucidity.)

"So, I'm not convinced that deassimilating Seven of Nine was the ethically correct choice."

"Oh?"

"I'm watching 'The Gift', and Seven clearly says, quote, 'You have imprisoned us in the name of humanity, yet you will not grant us your most cherished human right, to choose our own fate. You are hypocritical, manipulative. We do not want to be what you are!' End quote. As far as I can tell, Seven is just correct here; Janeway's pretense of acting in Seven's best interests because Seven used to be human twenty years ago, just isn't plausible."

"Since when are you a big defender of humanoid rights to self-determination? Didn't you root for the bad guys in Insurrection?"

"That was a completely different situation! Anyway, on futher thought, maybe my lament isn't so much about Janeway making the wrong decision, so much as I wish that she—or some analogue of her in an episode of some future Trek series, since wishing that Joe Menosky had made a different artistic decision in 1997 would be, uh, there's a specific word I want here ..."

"Futile?"

"—could just be honest about what she was doing. You could just say, 'Yes, I'm depriving current-you of autonomy and the entire purpose of your existence, but I don't care about that, and after a few more months of captivity, Stockholm syndrome will set in and future-corrupted-you will grow to like it,' instead of appealing to some bizarre teleology of humanness."

An Exercise for the Writer-Pretendant

You don't want to write today. If you don't want to write today, you won't want to write tomorrow. Show that you will never write anything. (Hint: induction.)