Eamonn O'Brien-Strain

Mastodon โ€“ Bluesky โ€“ Threads

standard JS logo

War is over, if you want it

War is over now

โ€” Lennon

Have you been a belligerent in the code-formatting wars? Do you have strong opinions on tabs vs. spaces or on the merits of The One True Brace Style over other indentation styles?

Something has now changed that renders these wars obsolete.

When I joined Google one culture shock was discovering how picky Google was about seemingly trivial details of formatting. Time and again my code reviewers objected to what I thought was my creative expression of coding craft. They were unmoved by my assertion that b*b - 4*a*c was more readable than b * b - 4 * a * c or that FOO was a better name for a constant than kFoo.

Quickly I came to peace with the style guides and recognized their value. I realized that operating in Google's fabled monorepo I was reading more code than I was writing, making me appreciative of how a consistent style made the code much easier to read. Also as a code reviewer and tech lead I appreciated how just a link to a section of the quasi-legal style guides could cut short tedious bikeshedding on formatting issues and allow us to concentrate on the important issues.

Initially it was a bit tedious getting all the spacing right and remembering all the subtle line-breaking rules.

But over time it got better. First the linting tools improved and covered more of the style guide so that I generally could find and fix all the issues before sending code changes for review.

But then it got even better, with the linting tools supplemented by tools that automatically reformatted code according to the style guide for that language. Now I just basically no longer spend any brain cycles on formatting: I write arbitrary sloppily-formatted code and have it fixed up when I save the file.

The designers of the Go language were Googlers, and they bought into this philosophy. Pretty much all Go code is formatted by the gofmt program, which always reformats to the same standard style.

So when I started a personal JavaScript project I really wanted a JavaScript equivalent to gofmt which enforces a standard style.

In the JavaScript ecosystem, Eslint is the standard linter, which not only can warn against style violations, but can automatically fix them. And what's more it can go beyond simple indentation and spacing and fix more complex rules (all the ones marked with a ๐Ÿ”ง wrench in the list of rules).

Initially I assumed I would want to use the Google JavaScript style guide and the eslint rules that enforce it. I configured Eslint with

 {
    "extends": [
        "eslint:recommended",
        "google"
    ],
    "env": {
        "browser": true,
        "node": true
    },
    "parser": "esprima",
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module"
    }
}

However, it was a bit unsatisfying to have a non-trivial config file with various things that need deciding and tweaking. It's also a bit annoying that it cannot not autofix all the problems it finds, so you still have to manually fix some of them.

Nevertheless it works fine, and produces nicely formatted code like below in standard Google format.

export const newCards = () => {
  const cards = [];
  for (let i = 0; i < 2; ++i) {
    const reversed = (i === 1);
    forEachPhrase((phrase) => {
      const responses = [];
      cards.push({phrase, reversed, responses});
    });
  }
  return cards;
};

But then I discovered standard JS. This has everything a auto style formatter should have. It is really easy to install, has good VS Code support, requires no configuration, and can fix all the problems it finds. It produces code like this:

export const newCards = () => {
  const cards = []
  for (let i = 0; i < 2; ++i) {
    const reversed = (i === 1)
    forEachPhrase((phrase) => {
      const responses = []
      cards.push({ phrase, reversed, responses })
    })
  }
  return cards
}

The most notable difference is that it omits semicolons.

Omitting semicolons is something the JavaScript language allows, but the exact rules for when you can do so have some subtle corner cases that can cause ambiguity and unexpected interpretation of code, so the conventional wisdom has long been to always include semicolons. That's the view of the Google style guide.

But the standard JS community have decided that the cleaner semicolonless look is preferred and have tackled the corner cases (which are rare) by making sure they are handled by other standard JS rules.

Personally, I don't care whether there are semicolons or not, but I'm going to stick with using standard JS for my projects as the tooling is better and it looks like it's gaining momentum in the JavaScript community.

Now of course pretty-printing has existed for a long time. The indent formatter dates from 1976, while text editors and IDEs have long been able to autoformat. But all these pretty-printers have one fatal flaw: they are too flexible, and can be configured with different style options.

The revolutionary novelty of Go's gofmt, standard JS, and Google's internal tool is that they have no options. They enforce a single standard style.

The wars are over! Everyone should just use a standard auto-formatter with no options.


Flash Card

There were some interesting aspects to building this web app.

  1. The spaced repetition algorithm for displaying flash cards to maximise learning.
  2. Using locale-specific web search to fetch the images
  3. Architecting the app as a static web site that uses local storage to store user state.
  4. Using vanilla JavaScript with no framework.
  5. Using 3D animations for flipping cards.
  6. Custom creating all the CSS styling.

In this post, I'll discuss the spaced-repetition algorithm, and defer the other aspects to future posts.

From Wikipedia

Spaced repetition is an evidence-based learning technique that is usually performed with flashcards. Newly introduced and more difficult flashcards are shown more frequently while older and less difficult flashcards are shown less frequently in order to exploit the psychological spacing effect. The use of spaced repetition has been shown to increase rate of learning.

There is a variety of published research on spaced-repetition, and different flashcard apps use a variety of different algorithms, some quite complicated.

I decided to start with something much simpler. Every time a user sees the card they say how accurately they predicted the reverse side of the card. We store a response object that has this correctness score along timestmp t of the response. For each card we calculate an average score, but weighted by time so that more recent scores count for more. Then we sort the list of cards so that the cards with the lowest scores come first.

The core of the algorithm is in common.js:

const TAO = 1000.0 * 60 * 60 * 24

export const score = (responses) => {
  if (responses.length === 0) {
    return -1
  }
  const now = Date.now()
  const weightedSum = responses
    .map((response) =>
         response.correctness * Math.exp((response.t - now) / TAO))
    .reduce((sum, x) => sum + x)
  return weightedSum / responses.length
}

The score function goes through the array of responses and sums up the correctness values weighted by an exponential decay function with a time constant TAO of 24 hours. This means, for example, that a day-old response is weighted at about 0.37 of a recent response.

This score is called from the sort function in index.js:

  const sort = () => {
    cards.sort((a, b) => score(a.responses) - score(b.responses))
  }

My hypothesis is that this simple algorithm with its single TAO parameter is as effective as the more complex algorithms. Hopefully by tuning TAO I can emulate those algorithms.

An observant reader will have noticed some significant algorithmic inefficiencies in the above naive implementation of the algorithm. In particular there are some superlinear big-O complexities that I'll probably need to mitigate as I add more cards to the app.


If you are reading this article on a phone or other touch device, did you know you can swipe left and right to get to other articles? Go on, try it now. I'll wait.

[Edit 2023-02-07: Actually you will need to try this on the old version of this blog.]

This turned out to be easy to implement thanks to Hammer.js. Here's how I did it.

  1. I downloaded the Hammer.js library into my JavaScript directory:
   wget https://hammerjs.github.io/dist/hammer.min.js

and creates a new file called swipe.js in that directory. 2. I added the following to the HTML template of my article pages:

   <script src="/js/hammer.min.js" defer></script>
   <script src="/js/swipe.js" defer></script>

This loads the Hammer library and my application code into the web page. As an optimization I add the defer attribute so that the script loading does not block the initial page load: after all this is optional stuff that should not slow sown the normal functionality of the page. 3. I already had the following navigational structure in the article pages for going to the previous and next pages:

   <nav>
       <a id="prev" href="...">&laquo; ...</a>
       <a id="next" href="...">... &raquo;</a>
   </nav>

The JavaScript can pluck out the destination URLs from the href attributes of the #t and #prev elements. 4. I added the following to swipe.js:

   const mc = new Hammer(document.body)

   mc.on('swipe', (ev) => {
     const dir = (ev.deltaX < 0) ? 'next' : 'prev'
     const a = document.getElementById(dir)
     if (a) {
       window.location = a.href
     }
   })

This code initializes Hammer.js and sets up a swipe lister. When a swipe is detected the code looks at the deltaX event property to determine if this was a left or right swipe, pulls out the URL from the href of the corresponding link, and navigates to that link.

This simple solution works fine, but there is no feedback to the user to show that a swipe has happened. In the next article I'll show how to add animation to improve the UX.


In a follow-up to the last article which described Adding swipe actions to a web site, I'll show how to add simple animation to give some user feedback that the swipe has happened.

First of all, we'll modify the JavaScript to add a single line as follows:

mc.on('swipe', (ev) => {
  const dir = (ev.deltaX < 0) ? 'next' : 'prev'
  const a = document.getElementById(dir)
  if (a) {

    // Added line for animation:
    document.body.classList.add('animate-' + dir)

    window.location = a.href
  }
})

So, just before loading in the new page this code adds a CSS class animate-next or animate-prev to the body element of the page (i.e. to the entire page). This will trigger an animation that will run while waiting for the new page to load.

To actually cause the animation we add the following CSS:

  @keyframes slideLeft {
    to {
      transform: translateX(100%);
      opacity: 0;
    }
  }
  @keyframes slideRight {
    to {
      transform: translateX(-100%);
      opacity: 0;
    }
  }
  .animate-prev {
    animation: 0.2s ease-out 0s 1 slideLeft;
  }
  .animate-next {
    animation: 0.2s ease-out 0s 1 slideRight;
  }

Let's walk through this:

  1. Each @keyframes includes a single to (100%) key-frame which is the state to animate to. Because there is no from (0%) key-frame the animation will default to using the current state as the starting state. It
    • translates 100% to the left or right (which for the body will translate it offscreen)
    • fades from opaque to transparent.
  2. Each class selector has an animation that uses the corresponding key-frame, specifying that it should animate
    • over the given time
    • using ease-out (start fast then slow down)
    • with no delay
    • playing just once
    • the given #keyframes name

Note we are careful to only use High Performance Animations that can be efficiently implemented by a phone's GPU, namely translation, scaling, rotation, or opacity change. We could obviously use these four types of animations to further enhance the page-swiping animation to make it more effective, while still keeping it efficient.


Figure 3 of paper by Maughan et al "BHIs for 13 commonly consumed and commercially available drinks after correction for water content of drink ingested.", with added red line


I was intrigued enough by an article on hydration, Which drink is best for hydration? Hint: It isn't water, which appeared in my news feeds that I dug up the original paper, A randomized trial to assess the potential of different beverages to affect hydration status: development of a beverage hydration index, which seems to be from legit scientists, so I tweeted about it:

This seems to go against the conventional wisdom in the United States, where people feel the need to drink a lot of water to be healthy.

As someone who grew up in Ireland, I do find it strange how much value Americans put on water. In Dublin we had very high quality water from the Wicklow Mountains but we did not drink plain water that often. At family dinner we would not have water on the table, or indeed any drink. Instead we would drink copious amounts of tea after each meal and throughout the day.

Of course most of the United States gets much hotter in the Summer than Ireland, and indeed I'm suspicious that the kidney stones I developed within a few years of coming to the US were caused by me not upping my liquid intake to match the increased temperatures I was experiencing.

But even in hotter parts of Europe people don't drink as much water as Americans. Ubiquitous water is not the norm. In most restaurants you have to pay for bottled water if you want water with your meal.

But I've always been a bit dubious that there is a scientific basis for drinking quite as much water as many Americans believe is necessary to be healthy, and I've been suspicious of the assertion that coffee and beer โ€œdon't countโ€ because of the diuretic effect of caffeine and alcohol.

The paper does seem to confirm my suspicions, and a quick Google search finds some confirmation. For example the New York Times UpShot column determined No, You Do Not Have to Drink 8 Glasses of Water a Day, which is confirmed by WebMD in The Quest for Hydration.

The โ€œ8 glasses of waterโ€ requirement actually includes all the water in food, and since the majority of most food is water, you can get a lot of your hydration needs just from eating. And if you consider water from coffee, tea, and beer you can easily get all your water needs without drinking actual water.

So, this emphasis on drinking water is a weird peculiarity of American culture, but it is a pretty harmless peculiarity. Even if the hydration benefits are greatly exaggerated, it is still good for the health if consumption of water displaces excessive consumption of alcohol or sugary drinks.


Jekyll logo

Well, itโ€™s been about a year a year since I started this blog, and Iโ€™ve finally got fed up with my idiosyncratic static site templating framework that uses the venerable m4.

I was working on building a better templating language, inspired by the one we use in Google to power the main Google search page, but doing that was preventing me actually writing any content for the blog.

So, for now I have abandoned my non-standard approach and have converted to using the Jekyll static site generator. So now eamonn.org is generated by Jekyll, though I have kept the old version alive with links to the old versions of the pages (because Cool URIs don't change).

For now Iโ€™m just using the bare-bones default theme, but Iโ€™m thinking of working on making the a little nicer.

And now that itโ€™s easier for me to add content, maybe Iโ€™ll get around to adding old content such as my Medium articles.


Image attribution Tom Preston-Werner CC BY 4.0


Image by Tim Urban (https://waitbutwhy.com)




Screenshot from https://www.ispa.org.uk/ispa-announces-finalists-for-2019-internet-heroes-and-villains-trump-and-mozilla-lead-the-way-as-villain-nominees/