Skip to content

Instantly share code, notes, and snippets.

@tmcw
Last active September 18, 2018 04:07
Show Gist options
  • Save tmcw/f86ea395af32f08f826bd8f3ace985c9 to your computer and use it in GitHub Desktop.
Save tmcw/f86ea395af32f08f826bd8f3ace985c9 to your computer and use it in GitHub Desktop.

Quick thoughts on implicit casting

What's implicit casting? Say, you have a function that returns a negative number, like

function negate(x) { return -x; }

Implicit casting is saying that 'if the user provides a string that can be coerced to a number as input, then we’ll turn it into a number. For example

function negate(x) {
  let number = +x;
  return -number;
}

There are a bunch of valid ways, like parseFloat, or this + trick.

Pros: why cast implicitly

For some reason, a user might have stringy-number inputs and casting those inputs will make them 'just work'.

A bunch of JavaScript builtins cast for you. For example:

typeof Math.max('10', '1', '5') === 'number'

And a bunch of DOM APIs are very stringy. HTML attributes, for example, and localStorage, both convert all their inputs to strings to store internally. If you're always converting back from those strings, you might want to embrace the situation and just accept strings.

Cons: why not

Stringy numbers usually indicate a bug in your code. So, if someone wants to know the average of '10' and '20', the strings, most likely they parsed a CSV file and forgot that CSV data is strings-only, or they parsed a querystring, which is also strings-only, or they conjured up the data some other way.

So, you can really argue that casting lets applications fail quietly when they should fail noisily, as written in the Unix Philosophy, which is not always 100% on the ball but that particular suggestion has worked for me.

Casting leads to some odd situations. For example:

let myArray = ['1', '2', '3'];
Math.max.apply(Math, myArray); // 3
assert(Math.max.apply(Math, myArray) === myArray[2]); // nope.

So, you're looking for the maximum element in that array, and what Math.max gives you is a totally different value. Which, maybe that's fine, but here’s a reasonably real-world example of how this is a bad bad situation:

const products = [
  { price: '1', name: 'Apples' },
  { price: '2', name: 'Bananas' },
  { price: '3', name: 'Apricots' },
];

const mostExpensivePrice = Math.max(...products.map(p => p.price));

console.log(`The most expensive product costs ${mostExpensivePrice}`);

const mostExpensiveProduct = products.find(p => p.price === mostExpensivePrice);

console.log(`And it is called ${mostExpensiveProduct.name}`);

This fails, because the most expensive price calculated by Math.max is 3 (a number) and prices are strings, so the === comparison finds nothing. Which raises the question: should we use == instead? And the answer to that is, I'd argue, a strong no.

  • Another con is that cast-by-default makes typings weird - if you have Flow or TypeScript, why would you want to have a string | number type when the type system is able to identify mismatched parameters?
  • And another minor con is that, for hot code, casting adds a potentially tiny, but still present, performance cost, even for users who are providing the correct, numeric values.

QED

Neither is strictly better. mbostock, who is undeniably more accomplished than me, uses casting a lot in d3, and that has made lots of people happy. I don't cast in any of my code, unless I do in some project that I forgot about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment