I was a fan of the TV show The Good Place, and this book dives deeper into the philosophical concepts behind that show’s exploration of morality. The subject matter is pretty dry and basic (I was familiar with much of it from taking a 100-level philosophy course in university), but the author, who also created and ran the TV show, imbues his sitcom-honed sense of humour into it. A bonus if you’re listening to the audiobook: the Good Place cast comes along for the ride; a highlight is Ted Danson’s dramatic reading of chapter titles.

storygraph link

Recently, I’ve been working on some social and gamification features in a web application. I decided to use a backend service called brainCloud instead of implementing all of the database stuff myself. The brainCloud service stores things like player statistics, leaderboards and social relationships (i.e. who’s friends with who?), and provides an HTTP API to read and write the data. They also provide a Javascript client library to make it easier to work with those APIs.

Without getting into too many specifics, the library follows a callback style for handling asynchronous behaviour. When you call one of their functions, it sends an HTTP request; when the server responds, it executes the code in the callback function that you pass as a parameter. For example,

postScoreToLeaderboard(leaderboardId, score, (resp) => {
	if (resp.successful) {
		// do something with the data
		console.log('We got data!', resp.data);
	} else {
		console.log('We got an error!', resp.error);
	}
});

Personally, I prefer to handle asynchronous code using promises in the await/async style.

A general pattern to follow to convert a callback interface to a promise-based one is to build a Promise object and return it.

(N.B.: For the purposes of this post, I’ll follow the convention of prefixing a function name with an underscore to indicate that it’s a “wrapper” around a brainCloud API function.)

const _postScoreToLeaderboard = (leaderboardId, score) => {
	return new Promise(resolve, reject) => {
		postScoreToLeaderboard(leaderboardId, score, (resp) => {
			if (resp.successful) {
				resolve(resp.data);
			} else {
				reject(resp.error);
			}
		});
	});
};

Now, we can use this function without a callback:

try {
	const data = await _postScoreToLeaderboard(leaderboardId, score);
	// do something with the data
	console.log('We got data!', data);
} catch (e) {
	console.log('We got an error!', e);
}

This works, but there are many functions in the brainCloud API. How can we extend this technique in a generic way?

To illustrate, here’s a couple of other brainCloud methods for gamification:

getLeaderboardTop(leaderboardId, (resp) => {
	// handle response
});

incrementUserStats(statId, value, (resp) => {
	// handle response
});

It would be a lot of repetitive code to wrap each of these in a function:

// TOO MUCH CODE!

const _getLeaderboardTop = (leaderboardId) => {
	return new Promise(resolve, reject) => {
		getLeaderboardTop(leaderboardId, (resp) => {
			// handle response
		})
	});
};

const _incrementUserStats = (statId, value) => {
	return new Promise(resolve, reject) => {
		incrementUserStats(statId, value, (resp) => {
			// handle response
		})
	});
};

To reduce duplication, we can curry the functions.

A brief introduction to currying

If you’re not familiar with the term, currying a function creates a new function with parameters that can be called one by one.

The classic example is a function that adds three numbers together.

const add = (a, b, c) => a + b + c;

// create a curried function
const curriedAdd = curry(add);

const addOne = curriedAdd(1);

// this is equivalent to calling add(1, 2, 3)
const x = addOne(2, 3);

// x is 6

Note that this only returns the final result of 6 if we “use up” all three parameters of the original function. If, instead, you leave the final parameter “unused,” it will return yet another function.

// we have "used up" two out of three parameters
const addSeven = addOne(6);

// this is equivalent to calling add(1, 6, 10)
const y = addSeven(10);

// y is 17

Back to our regularly scheduled programming

With this approach, we can curry the functions from the brainCloud API. For example,

const _incrementUserStats = curry(incrementUserStats);

This particular function has two input parameters, and a third for a callback. If we call the curried version with the two input parameters, it will return a function that accepts a callback and executes the original function once it receives the callback.

const fn = _incrementUserStats('statId', 15);

const handleResponse = (resp) => {
	// do something
};

// this is equivalent to calling 
// incrementUserStats('statId', 15, handleResponse)
fn(handleResponse);

I named the final function fn because this part can be generalized to any API call that takes a callback as its final parameter.

If we create a function that takes a function like fn and calls it, we can then use a Promise-based pattern for making these API calls.

const makeAPICall = (fn) => {
	return new Promise((resolve, reject) => {
		fn((resp) => {
			if (resp.successful) {
				resolve(resp.data);
			} else {
				reject(resp.error);
			}
		});
	});
};

To use this, we call one of the curried functions with all of its parameters except for the final callback parameter, and pass the result to the makeAPICall function.

try {
	const data = await makeAPICall(_incrementUserStats('statId', 15));
	// do something with the result
	console.log('We got data!', data);
} catch (e) {
	console.log('We got an error!', e)
}

Putting it all together:

// curry all of the functions that we want to use
const _postScoreToLeaderboard = curry(postScoreToLeaderboard);
const _getLeaderboardTop = curry(getLeaderboardTop);
const _incrementUserStats = curry(incrementUserStats);

// define the function that receives a callback 
// and executes the original function
const makeAPICall = (fn) => {
	return new Promise((resolve, reject) => {
		fn((resp) => {
			if (resp.successful) {
				resolve(resp);
			} else {
				reject(resp);
			}
		});
	});
};

// now we can make API calls like this
const result1 = await makeAPICall(_postScoreToLeaderboard('id', 100));
const result2 = await makeAPICall(_getLeaderboardTop('id'));
const result3 = await makeAPICall(_incrementUserStats('statId', 15));

As a final note, the curry function itself is available in the popular lodash library of Javascript utilities.

Review: China in Ten Words by Yu Hua

Compare and contrast

After my experience with Beijing Confidential, I felt compelled to expose myself to writing about China by native and contemporary authors. China in Ten Words wasn’t exactly what I was looking for—I wanted more focus on modern China, i.e. what is it like now—but it was still an enlightening read.

Each of the ten essays in this collection is a meditation on a Chinese phrase, which are all shown on the cover, in case you’re too lazy to open to the table of contents.

I was a bit impatient through the first half of the book, which deals with the author’s childhood during the Cultural Revolution in the 1960’s. It covers ground that doesn’t feel new to me because of my time with other books. I wanted him to get to the present day, which he does indeed do towards the second half. I would say that the earlier parts of the book do lay an important foundation for the rest of it, because as a whole, the book can be seen as a compare-and-contrast exercise between the time of Mao’s rule and today.

In one memorable passage, Yu describes an incident during his childhood, when he and some other kids enacted vigilante justice against a man who was illegally trading food stamps. The kids physically assaulted the man, but the man didn’t retaliate; instead, he broke down and cried in remorse.

In contrast, Yu recounts a story from more recent times, where an unlicensed street vendor stabbed and killed a government official who was trying to enforce the rules by shutting down the shop.

Yu sees this as a breakdown in the morality of the country. He seems almost nostalgic for the black-and-white authoritarianism of Mao’s day, as opposed to the everyone-for-themselves mentality of today. He acknowledges the brutality of the past, especially for the violent acts of his youth, but suggests that there was some value in unity, where everyone agrees what’s right and wrong. The man that Yu and the other kids assaulted didn’t retaliate because he knew he was breaking the rules.

So China moved from Mao Zedong’s monochrome era of politics-in-command to Deng Xiaoping’s polychrome era of economics above all. “Better a socialist weed than a capitalist seedling,” we used to say in the Cultural Revolution. Today we can’t tell the difference between what is capitalist and what is socialist—weeds and seedlings come from one and the same plant.

  • Yu Hua

Personally, I’ve seen this attitude from the older generations of my family, who seem to believe that obedience is paramount. I don’t always agree, but I’ve been trying to recognize where they’re coming from. This book has further enlightened me to the differences between the two cultures that I inhabit.

I’m clearly two years late on this, but when I recently looked back at my post of favourite albums of the previous decade, I couldn’t help doing this again, if only to leave myself something to look back on in another 10 years.

  1. 12 Bit Blues - Kid Koala (2012)
  2. The Navigator - Hurray for the Riff Raff (2017)
  3. Lonerism - Tame Impala (2012)
  4. My Beautiful Dark Twisted Fantasy - Kanye West (2010)
  5. Trouble Will Find Me - The National (2013)
  6. Love & Hate - Michael Kiwanuka (2016)
  7. The ArchAndroid - Janelle Monae (2010)
  8. A Moon Shaped Pool - Radiohead (2016)
  9. Art Angels - Grimes (2015)
  10. I Am Easy to Find - The National (2019)

Bonus List - Favourite Songs That Aren’t on the Aforementioned Albums

In no particular order:

  • “begin again” - Purity Ring, Another Eternity (2015)
  • “Piss Crowns are Trebled” - Godspeed You! Black Emperor, Asunder, Sweet and Other Distress (2015)
  • “Wide Open” - Chemical Brothers (feat. Beck), Born in the Echoes (2015)
  • “The Heart Wants What it Wants” - Selena Gomez, For You (2014)
  • “Blood and Rockets - Movement I, Saga of Jack Parsons - Movement II, Too the Moon” - The Claypool Lennon Delirium, South of Reality (2019)
  • “Good Intentions Paving Company” - Joanna Newsom, Have One On Me (2010)
  • “Optimist” - David Byrne & St. Vincent, Love This Giant (2012)
  • “Time” - Hans Zimmer, Inception (2010)
  • “Do I Wanna Know?” - Arctic Monkeys, AM (2013)

Bonus Bonus List - A couple of artists who have been incredibly prolific during the 2010’s, and who don’t have any specific works that I especially love, but still, I like almost everything they do

  • Lana Del Rey
  • Run the Jewels
Albert

About Me

Hi! Albert here. Canadian. Chinese.

Writing software since 2001. “Blogging” since 2004. Reading since forever.

You can find me on socials with the links below, or contact me here.