2
\$\begingroup\$

I defined a function that multiplies its argument by 3:

coffee> foo = (x) -> x * 3
[Function]

We can call it with an input of 1:

coffee> foo(1)
3

However, when I tried with a non-Number type:

coffee> foo("3")
9

This behavior appears wrong to me since a String cannot be multiplied by a Number.

To fix it, I'd add something like this:

foo = (x) ->
  if typeof x is 'number'
    x * 3
  else
    throw new Error("not a number:"  + x)

Is there a cleaner way to do this in CoffeeScript?

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

CoffeeScript is, in the end, just JavaScript with a nicer syntax. So the string-interpreted-as-number behavior is expected and normal. You write:

This behavior appears wrong to me since a String cannot be multiplied by a Number.

Yet, due to type coercion in JavaScript, a string can be multiplied by a number just fine. And, importantly, the result is correct!

Your options with regard to strict type-checking are:

  1. Using typeof and throwing an exception (or handling it some other way), as you do now.
  2. Doing nothing.

You're still writing JavaScript, so option #2 is a valid solution to most type concerns. For instance, most (all?) the functions in the Math object accept numerical strings just as well as numbers.

For other types, the GIGO principle usually applies: Garbage In, Garbage Out. I.e. if the user of your code passes something completely incompatible, like an object, they'll get NaN back. And that's their problem, really. Same as if you do Math.floor({}) or something.

However, if type-checking is absolutely necessary, you can at least slim your code down a bit:

foo = (x) ->
  throw new TypeError unless typeof x is 'number'
  x * 3

Note that the code throws a TypeError, which is more appropriate for this situation, and that, since it throws, execution of the function is halted just as effectively as writing return. So there's no need for if...else branches.

You can generalize it a bit with a function, if you need to do this more often:

assertNumber = (value) ->
  unless typeof value is 'number'
    throw new TypeError("Expected a Number")

Or, better yet, check whether the value is numeric:

assertNumeric = (value) ->
  if isNaN Number(value)
    throw new TypeError("Expected a numeric value")

And use it like so:

foo = (x) ->
  assertNumeric x
  x * 3
\$\endgroup\$
7
  • \$\begingroup\$ This is a very good answer. \$\endgroup\$
    – nrw
    Commented May 24, 2014 at 21:49
  • \$\begingroup\$ I appreciate your thoughtful, solid answer. However, I don't like this statement - if the user of your code passes something completely incompatible, like an object, they'll get NaN back. And that's their problem, really. I understand your GIGO point, but a well-built function shouldn't just blow up upon bad input, in my opinion. Although perhaps this state-of-mind is commonplace in JavaScript? I primarily work in the back-end (Java/Scala). \$\endgroup\$ Commented May 25, 2014 at 0:02
  • \$\begingroup\$ @KevinMeredith Well, that's sort of the point of GIGO: The function doesn't blow up; it executes and returns. Throwing an exception would be more akin to "blowing up". Of course, you can catch exceptions, but if you fail to catch one, script execution is just halted. Meanwhile, checking for NaN is pretty simple and doesn't require a couple of lines of try...catch code. JavaScript does use exceptions from time to time, yes, but for the most part it's GIGO and loose typing. Again, look at something like Math.floor \$\endgroup\$
    – Flambino
    Commented May 25, 2014 at 0:12
  • \$\begingroup\$ thanks, Flambino. I didn't intend to say "state-of-mind" in a rude way. I had meant, "perhaps GIGO is standard in JavaScript libraries". But your follow-up response helped me out in your recommendation to throw an Exception or return NaN, thus avoiding the try/catch. \$\endgroup\$ Commented May 25, 2014 at 0:14
  • \$\begingroup\$ @KevinMeredith Oh, no offense taken (and thanks for the checkmark). JS libraries vary in how they handle bad input. Judging by how rarely I write try...catch statements, I'd say GIGO is somewhat more common than throw statements (or maybe I should be writing try...catch more often, but it rarely seems necessary). Of course, if you're in a situation where it's impossible to even return garbage then by all means throw an exception. \$\endgroup\$
    – Flambino
    Commented May 25, 2014 at 0:42

Not the answer you're looking for? Browse other questions tagged or ask your own question.