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

Review: The Truffle Hunters

Beware of Big Truffle

The Truffle Hunters is a documentary film about a bunch of old dudes in Italy who work with dogs to find truffles1 buried in the ground, deep inside forests. As a dog lover, I envy the working relationship that these people have with their dogs. My dog is great, but she’s never going to help me write any Javascript.

The film is visually gorgeous, made up mostly of static shots that are perfectly composed, lit and colour-graded. One shot that springs to mind is of a married couple sitting behind a stack of tomatoes, washing them one by one. Every unblemished tomato is a deep, rich shade of red.

While it looks great, the impeccable style makes me question the authenticity of some of the more emotional scenes. I couldn’t help but imagine the filmmakers meticulously setting up the camera and the subjects, and waiting for the perfect sun, and then saying “action” to what is supposed to be a genuine outpouring of emotion. For example, there’s one scene involving a distraught and crying truffle hunter, telling a police officer that one of his dogs has been poisoned by ruthless corporate truffle hunters trying to encroach on his territory. I felt bad for him, but still, I had to ask myself, Is he acting here?

Which brings us to the dark side of the film: because truffles are so rare and valuable, greed and competition have entered into the truffle hunters’ lives. I’ve always had a distaste for “foodie” culture because of the accompanying snobbishness, and this film pushed those buttons for sure. The Truffle Hunters depicts the middlemen and consumers of the truffles as shady characters. They haggle for low prices with the hunters in nighttime back-alley meetings, before turning around and selling to restaurants for a huge profit margin. They berate hunters for leaving a little bit of dirt on the goods. They demand attention from journalists and photographers by holding truffle exhibitions. And worst of all, they are seemingly involved in the intentional harm of innocent animals.

The cost of elevating food to a status symbol is that honest and hard-working people (and their beloved pets) are exploited and hurt.

Footnotes

  1. Personally, I don’t get what the big deal is about truffles. Generally, I have a strong sense of smell—I once identified blue cheese as a condiment in a colleague’s sandwich from all the way at the other end of the lunch cafeteria table—but when I’ve tried truffle-based dishes, the supposedly distinctive aroma just doesn’t hit me.

Revive (again)

Hello again! (again)

It’s kind of embarassing, looking back at this previous post. It sounded like I was arriving somewhere, where in fact, those “big changes in my life” just kept rolling. I got married, got a dog, moved house, changed jobs (twice). Where I’ve landed is a place where I’ve seen my creative output trail off, enough to miss it, enough to want to return to it.

I’ll start off by describing the journey that my online presence has taken. I started this blog in 2004, publishing on a Blogger.com site called “A Logical Waste of Space,” which was shared with friend of mine. (His stuff is still there.)

Over time, the kinds of things that we wanted to write about diverged, and it made sense for me to split off. For archival purposes, I’ll keep that site around, even though I’m not too proud of some of the dumb stuff I used to write.

Then, I hosted this site on Squarespace for a while. I like the templates and WYSIWYG design tools that they provide, but felt like my data was trapped. I’m the type of person who likes to work in non-proprietary environments, so I started to look for alternatives.

The current iteration of this site is built using Jekyll, which is a geek’s dream. I’m writing this in a Markdown text file, and Jekyll takes care of compiling it into HTML. I have a DigitalOcean app pointed at a GitHub repo where these text files live, and it updates the site whenever I make changes. Sure, it takes some coding to customize the look of the site, but hey, I know how to do that stuff! It doesn’t even cost anything (unless I get an unexpected uptick in fame).

So, here I am again.

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.