Over the weekend I started working on a node.js client library for Toshl's new API. An old side project stopped working recently and I need to fix it because life without frequent emails about my money is disorienting as hell.
The Toshl beta API opened up in July is well thought out, has good documentation and provides everything you could possibly want. I love the well granulated permissions system.
But for users of my node.js library I wanted to make life even simpler. Let's look at fetching expenses.
After you authenticate - testing OAuth clients sucks, and Toshl invalidates your token very often. Think I had to refresh it four times in a five hour coding spree. - you can talk to https://api.toshl.com/expenses
to get a list of expenses for the current user.
This will return the last 30 entries.
But there are a bunch of options. You have pagination, you can set specific to
and from
dates, you can filter things by tags and by not-tags.
How can a library make this easy to use?
One approach is to offer a way of specifying an options hash when calling a function, but can we make it even simpler?
The answer lies in polymorphism.
Let's say you have a function called toshl.expenses
, in Haskell you could do something like this:
expenses::Result
expenses = general_expenses ""
expenses::Number -> Result
expenses N = general_expenses "?per_page="+str(N)
expenses::[String] -> Result
expenses tags = general_expenses "?tags="+(tags.join "&")
expenses::Date -> Date -> Result
expenses from to = general_expenses "?from="+str(from)+"&"+str(to)
expenses::Json -> Result
expenses params = general_expenses "?"+to_query(params)
general_expenses::String -> Result
general_expenses query = make_request "/expenses" query
make_request::String -> String -> Result
make_request endpoint query = ;; do stuff to read from full URL
The syntax is likely wrong but you get what I'm pointing at. You can always call expenses
with the argument you care about and it will construct a call to the more generalized version of the function magically.
You can tell what calling expenses
will do in each case at a glance and checking what type of arguments the function accepts is trivial. Even somebody who isn't familiar with Haskell could understand that code.
Here's that same polymorphic code in Javascript. This time tested working code.
exports.Toshl.prototype.expenses = function (params, to, callback) {
var options = {},
query = "";
callback = arguments[arguments.length - 1];
if (params) {
if (typeof params == "number") {
options["per_page"] = params;
} else if (arguments.length == 3) {
options["from"] = util.iso_date(params);
options["to"] = util.iso_date(to);
} else if (params instanceof Array) {
var tags = util.transform_tags(params);
options[tags.type] = tags.tags;
} else if (params instanceof Object) {
options = params;
["from", "to"].forEach(function (key) {
if (options[key]) {
options[key] = util.iso_date(options[key]);
}
});
}
query = "?" + querystring.stringify(options);
}
this._request("/expenses" + query, callback);
};
Oh wow, what?
Even if you're very comfortable with Javascript you're going to have a hard time figuring out what's going on. It seems the majority of the function deals with translating arguments
into a query
, then it defers to this._request
for the hard work.
This is the cleanest implementation I could think of so far. Let's investigate.
First we ensured that callback
is always the last supplied argument, that makes sense when you expect variable amounts of arguments. Everything from one to three is okay.
Then, if params
is a number we use it to construct a ?per_page=N
query. If there are three arguments, we use the first two to construct a ?from=Date&to=Date
query. If the first argument is an Array we use it to get tags and if it's an Object we assume it represents a parameters hash for the API.
The result is that we can do this:
var toshl = new Toshl();
toshl.expenses(console.log); // prints last 30 expenses
toshl.expenses(5, console.log); // prints last 5 expenses
toshl.expenses("2013-10-01", new Date(), console.log); // prints all expenses between October 1st and now
toshl.expenses(["coffee", "food"], console.log); // prints last 30 expenses tagged with coffee or food
toshl.expenses({ per_page: 10, tags: ["coffee", "food"] }, console.log); // prints last 10 expenses tagged coffee or food
The date magic is done with the wonderful moment.js library - you can supply a Date object, a date string or a moment object, but we could obviously improve on this by assuming the to
date is "now", if none is provided.
But the code is getting complicated as it is.
Does anyone know a better way to achieve polymorphism in Javascript? I really like it when I'm using libraries, but I hate implementing it this way ...
Continue reading about About achieving polymorphism in Javascript
Semantically similar articles hand-picked by GPT-4
- Sabbatical week day 1: Toshl and Toggl datasets
- My old code is atrocious
- Sabbatical week day 2: I fail at Octave
- You don't *have to* build it sloppy to go fast
- Writing a REST client in Haskell
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️