Coronavirus Growth Rate
To make it easier to compare how COVID-19 is affecting different countries I created these population-adjusted graphs.
To make it easier to compare how COVID-19 is affecting different countries I created these population-adjusted graphs.
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.
So, I'll be going to Mexico in a few months and I wanted to brush up my Spanish vocabulary ...
โ Eamonn O'Brien-Strain ๐จโ๐ป๐๐๐ฎ๐ช๐ช๐บ๐บ๐ฒ๐บ๐ณโ๏ธ (@eob) November 6, 2019
So I built https://t.co/lNgilbwR3o with flashcards that have Spanish on one side, and images from a web search in Mexico on the other side.
Source code at: https://t.co/Phl3B2Dd1K pic.twitter.com/xNtR6WrmZz
There were some interesting aspects to building this web app.
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.
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="...">« ...</a>
<a id="next" href="...">... »</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:
@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
animation
that uses the corresponding key-frame,
specifying that it should animate
ease-out
(start fast then slow down)#keyframes
nameNote 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.
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:
It turns out that water is not the best way to keep hydrated. Milk is better.
โ Eamonn O'Brien-Strain ๐จโ๐ป๐๐๐ฎ๐ช๐ช๐บ๐บ๐ฒ๐บ๐ณโ๏ธ (@eob) September 26, 2019
And beer or drip coffee are almost as good as water for hydration.
Good news for me as I seldom drink water, but drink lots of coffee all day, capped by a beer in the evening.https://t.co/KFaqQZtvZe pic.twitter.com/FTSd2VGgnJ
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.
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
More interesting is that everyone in Earth could fit into New York City, while all standing on the ground shoulder to shoulder but not actually touching.
โ Eamonn O'Brien-Strain ๐จโ๐ป๐๐๐ฎ๐ช๐ช๐บ๐บ๐ฒ๐บ๐ณโ๏ธ (@eob) August 12, 2019
This image from that same article shows how to fit people from the different continents into the different the boroughs. pic.twitter.com/5iANOxd6ia
Not sure why it deserves a condescending name, but a pseudo-Boolean function is a function that takes Boolean parameters and returns a real number.
โ Eamonn O'Brien-Strain ๐จโ๐ป๐๐๐ฎ๐ช๐ช๐บ๐บ๐ฒ๐บ๐ณโ๏ธ (@eob) August 10, 2019
By comparison an honest-to-goodness Boolean function, worthy of great respect, takes Boolean parameters and returns a Boolean.
"Push" releases behind flags.
โ Eamonn O'Brien-Strain ๐จโ๐ป๐๐๐ฎ๐ช๐ช๐บ๐บ๐ฒ๐บ๐ณโ๏ธ (@eob) July 7, 2019
Run "live traffic experiments" by enabling flags for small numbers of user.
"Launch" by ramping up flag enablement to all users.
"Soft launch" without fanfare.
"Dark launch" by starting traffic flow through infra, but not (yet) to users.