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.

The Overcompensating Backslash

Mean geeks

You hear a lot of news reporters and journalists on TV and radio referring to website URLs these days. I have noticed, not without ire, that many of them make the mistake of saying “backslash” when they really mean “slash”, e.g. “Visit our site at http-colon-backslash-backslash www-dot-ctv-dot-ca backslash news.”

I can only imagine the media personality in her youth, back in the 80’s, perhaps working a lowly office job before she got her first reporter gig, running into an unpleasant DOS geek system administrator:

Pre-media personality: “Can you help me with this? I saved the file to C colon slash documents, but I can’t open it anymore…”

DOS geek system administrator: “It’s backslash, you idiot! C colon backslash! Don’t you know how to use a computer?!”

The emotional trauma of being yelled at by a scruffy, heavy-set man has undoubtedly stayed with our media personality all these years, and now she subconsciously overcompensates every time she sees a slash. That is, until this happens:

Media personality: “Can you help me with this? I want to put up a link for http-colon-backslash-backslash microsoft dot com backslash windows to this story I did on software obsolescence…”

Web geek system administrator: “It’s slash, you idiot! Don’t you know how to use the Internet?!”

Emotional Trauma 2.0

Pack this, Google! [rude gesture]

Just an installer

So the big announcement by Google at CES was the release of Google Pack. (Actually, it wasn’t. The real big announcement was the Google Video Store, but that’s not out yet… and besides, I don’t have anything to complain about with that.)

That said, the Google Pack is the first time I’ve been disappointed with something Google has done. First of all, it’s not a product at all… it’s just an installer. There’s nothing here that I can’t download and install myself (e.g. Firefox, Acrobat Reader), but they’re hyping it like it’s revolutionizing the desktop experience.

What I disliked about the Pack is that it gives you no control over what you’re installing. You download the executable, run it, and the next thing you know, the thing is downloading 50+ Megs of software and installing it. There is apparently a way to configure what gets installed, as explained on the About page:

Do I have to install all the software in the Google Pack?
While we believe you’ll find all of the software in the Google Pack useful, you’re welcome to install as much or as little as you’d like. To customize your installation package, please click the “Add or remove software” link under the download button on the Google Pack homepage. On the Customize page, simply uncheck the box next to any program you’d prefer not to install and click the “Continue” button to complete the installation process.

I honestly didn’t see the link under the download button when I downloaded it, and there’s no excuse for not reading carefully before downloading, but this is the kind of thing that you would expect from the RealPlayer installer (a checkbox saying “Do you not want to not install the RealPlayer Message Centre?”). Configuring an installation before you download it is completely counterintuitive to what we expect from every other application out there.

I’m willing to give Google the benefit of the doubt on this one—maybe they felt that the average user would prefer to have a one-click installation—but if I see this kind of trickery from them again… I’ll… uh… start using MSN Search!

In Defence of Wikipedia

Anyone can edit

There’s been some controversy over Wikipedia lately, and it takes the form of a lawsuit against the online encyclopedia. Someone has started a website at WikipediaClassAction.org (update 2021/12/30: this link has been disabled because it now leads to an adult site), which plans to file a lawsuit against Wikipedia, on the basis that there is harmful and inaccurate information in some Wikipedia articles.

Wikipedia is well known for being an open encyclopedia, i.e. anyone can edit the articles. The idea is that for any given topic, someone in the world has the knowledge to be able to write about it. If this person posts some erroneous information (either intentionally or not), then someone else in the world will have the knowledge to correct it. In my experience, this model works, and Wikipedia has become one of my favourite sources of information. If something in an article seems controversial or somehow “not right” to me, a quick look at the discussion page for the article will show that the community is aware of the potential issues and is working to amend the entry.

There is no doubt that Wikipedia will never be 100% accurate at all times. The fact that it’s a collaborative effort necessarily implies that any information posted can evolve. Also, differences between one author’s perspective of a certain topic and another’s are bound to arise. The point is that it’s open for everyone to discuss and decide on an article’s final content cooperatively. I believe that when it comes to information, a large open community has more to offer than a single “authoritative” source, even if it means the occasional error or bug.

This lawsuit was sparked in part by an incident involving the biographical entry of John Seigenthaler Sr. Someone posted false information (apparently as a joke) implying that Seigenthaler had been involved in JFK’s assassination. The lawsuit claims that because of Wikipedia’s open nature, such misinformation is more likely to be spread. The ironic part of it is, as soon as the story came out, it was loudly discussed, and much attention was brought to that particular article. It has since been fixed, and in fact, there is now another article explaining the whole situation. Doesn’t this show the openness of the system actually serves to increase its accuracy? People will make false statements, through all kinds of media, whether they are open to the public or not. What’s worse, a self-correcting system such as Wikipedia, where information becomes fact only when agreed upon by everyone? Or supposedly authorative sources (e.g. the news media) which routinely present opinions as fact?

I fear that this lawsuit might actually have some success. They’ll never be able to shut down Wikipedia (at least, I certainly hope not), but they might succeed in forcing them to do something stupid like dropping the “pedia” from the name, just because it’s “misleading.” No matter what happens, I’ll continue to use and support the Wikipedia project.

Let me explain. There’s this online music player called Pandora, which is supposed to “read your mind” and play music that you would like. You start off by giving it the name of an artist or song that you like, and it will play things that it deems “similar.” The people behind Pandora analyzed a whole bunch of songs and assigned traits to them using a lot of pretentious musical terms, like “subtle syncopation” and “minor key harmonic tonalities”. (They call this the “Music Genome Project.”) The player uses these traits and builds a playlist of other songs or artists that share the same properties.

I’m sure that the system works well, but for some reason, I just don’t like most of the stuff it plays. I give it some of my favourites like Radiohead and Björk, and it just starts playing absolute crap. The common traits are definitely there, but I end up not really enjoying the playlist it generates.

It could be that I’m just picky, but I think the whole concept is fundamentally flawed. It’s very difficult to quantify the elements that make you like or dislike music. For example, you might like a particular song for its repetitive beat, but another song with a repetitive beat might drive you nuts. Pandora is a noble effort, and I’m sure many people will like it, but unfortunately it just doesn’t work for me.

Apple slave #655321

Shiny and pretty

I have never been a big fan of Apple. Their philosophy of computer-as-appliance has always struck me as a bit repulsive. It’s easy to dislike Microsoft (and I do) for keeping everything closed and proprietary, but in some ways Apple is even worse. At least with a Windows PC, you can open the thing up and add and remove parts yourself.

Just look at the iPod, for example. The fact that it uses a non-standard connector to interface with the PC means that there are a whole bunch of products (e.g. USB cables) that you can’t use with the iPod, and also that there are a whole bunch of iPod-specific products that don’t work with anything else.

Yeah, but that means the good Apple engineers can optimize for the hardware and make it work really fast…

Uh… but it’s harmful to consumers when these incompatibilities arise… it takes away their choices…

That’s okay, if everybody uses an iPod, then there won’t be any incompatibilities… and it’ll be really easy to find iPod-compatible products because all the manufacturers will be on the iPod bandwagon…

But… but… Apple is an evil corporation… they’re trying to take over the world!

Just look at this iPod… it’s so shiny and pretty… and cute..

NOOOOO!!!!!

Apple rulzzzzz!!!

As promised, I did a bit of research to find out what it would take to get LongPlayer to work together with amaroK. It turns out that amaroK supports a DCOP interface. DCOP is a protocol shared by KDE applications that allows you to control them via the command line. For example, you can add songs to the amaroK playlist by executing the following shell command:

dcop amarok playlist addMedia  

It’s definitely possible to modify LongPlayer to use these DCOP commands to control amaroK, instead of performing the equivalent operations with XMMS. And it’s probably not even difficult.

Unfortunately (or fortunately), while I was looking into this, I realized that everything I liked about amaroK were just gimmicks (e.g. lyrics lookup, album cover images). After initially being impressed with all these bells and whistles, I came to see that there’s ultimately no real usefulness to any of it. On the other hand, LongPlayer really does perform a useful function, and changes the user’s experience.

Therefore, I declare the LongPlayer/XMMS combination to be the winner of this little competition. Somewhat anti-climactic, but I’m glad it turned out this way… saves me the trouble of actually going through with modifying LongPlayer code.

(Aside: I think I learned a bit about myself through this little “ordeal.” I may at first be impressed by superficial things, but I eventually tend to gravitate towards a more practical ideal. In general, this probably does describe my general outlook on things. So, there you go: self-discovery through software.)

Battle of the audio players

No probability theory

I’ve arrived at a bit of a crossroads concerning audio player software, and which one to use. I have a moderately large collection of music on my computer, and dumping everything onto a playlist and shuffling isn’t quite good enough for me.

I’ve narrowed it down to two candidates:

XMMS + LongPlayer

XMMS is pretty much the standard audio player on Linux (it’s basically a clone of Winamp). However, its shuffle functionality (as with most audio players) is pretty rudimentary. In particular, it is susceptible to the so-called birthday paradox. I have no desire to start explaining probability theory here, but essentially, this means that it does not take long for the same song to be played twice, which quickly becomes annoying.

LongPlayer is a program that runs alongside XMMS (it also works with Winamp on Windows, and iTunes on MacOS), which basically continually populates your playlist queue with “random” songs. It is not completely random, because that would lead to the birthday paradox; instead, it tries to play the songs in such a way as to maximize the average time between playing the same song twice. Also, it supports a rating system which causes the higher-rated songs to be played more often.

The combination of XMMS and LongPlayer does a very good job of randomizing my playlist. On average, it takes about 4 weeks for a song to be played again (according to LongPlayer’s statistics).

amaroK

amaroK takes a completely different approach. This is the most full-featured music player I’ve seen. It places a strong emphasis on organizing a collection of music, and can group your tracks based on genre, artist, year, etc. It also uses this information to make suggestions as you’re playing something; for example, it gives you a list of albums by the same artist, and other artists of a similar genre.

I also like the fact that amaroK downloads album covers from Amazon.com and displays them as you’re playing tracks. It even displays lyrics for the currently playing song. (How this works, I don’t know—I should take a look at the source code.) Yes, I know that these are kind of frivolous features, but it gives the app a professional, polished feel.

So, here’s the dilemma. I really like the full-featuredness of amaroK, but its shuffling mechanism is pretty basic. There is a rating mechanism, but it doesn’t seem to obviously affect the selection of tracks. Now, amaroK is a larger project than LongPlayer, so it is likely that future updates will have an improved shuffling function. In fact, many people have requested a smarter shuffle on the amaroK forums.

The “best-of-both-worlds” solution would be if LongPlayer could interface with amaroK and feed songs into the amaroK playlist. I believe that this should be possible, because both apps are open-source. If I have time, I’ll try to find out more about this… (to be continued…)

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.