I think map passes the value AND index of the array element to the mapper function, so you get the result of “10” in base 0 (defaults to base 10), base 1 (error because 1 is not a valid digit in base 1), and base 2 (which is 2).
In general in JS, if you see [].map(funcName), it’s going to bite you in the ass, and you should change it to [].map(x=>funcName(x)) as soon as possible. And do it even faster if it involves this.
I understand why, but this shouldn’t be necessary though because it’s verbose and doesn’t meet expectations coming from many languages–introducing a point and needing arguments to get around what sometimes feels like a bug. This is why you’ll see separate functions like indexedMap, mapWithIndex, mapi, and the like in other languages so you’re not overloading the function arity.
Is there are Clippy-like linter for JS/TS that could warn against mistakes like this one? Deno itself has lint, but that doesn’t seem to catch this one. Nor does eslint apparently.
It’s unfortunate but there’s not much to be done here. You’d have to craft a very strict, one-off eslint rule to prevent this error in your codebase at the cost of some performance and convenience (eg prevent passing anything but an arrow function to Array.map)
Linters cannot rule out cases when the argument function is actually supposed to take an element and its index. But at least they could warn about a possible gotcha.
See I disagree here - it’s very much a “know the APIs you’re using”.
It is reasonable - and can be useful - for map() to pass things like the index as argument to the mapped function.
It is reasonable - and absolutely useful - for parseInt to take a base argument.
What we’re seeing here is when you try to use these two together.
Imagine a statically typed language:
int parseInt(string, base = 10);
class Array<A> {
Array<B> map<B>(B(*f)(A)) {
Array<B> result = [];
for (int i = 0; i < length; i++) {
result.append(f(this[i]))
}
return result
}
Array<B> map<B>(B(*f)(A, int))
Array<B> result = [];
for (int i = 0; i < length; i++) {
result.append(f(this[i], i))
}
return result
}
};
here if you did [“10”, “10”, “10”].map(parseInt) you would call the second map, and so would get [parseInt(“10”, 0), parseInt(“10”, 1), parseInt(“10”, 2)]
I get that it’s not the common map api in other languages, but coming in with “this is the API in some other unrelated language, therefore this is WAT” is simply not reasonable.
For the latter: not needing exact parameter count is a fairly fundamental part of the language. Saying it’s surprising that map(parseInt) invokes the same behavior as anywhere else in the language is “surprising” is again not reasonable.
JS has quirks, but labeling basic language semantics as WAT is tiresome.
I get that it’s not the common map api in other languages, but coming in with “this is the API in some other unrelated language, therefore this is WAT” is simply not reasonable.
It is still a reasonable criticism because map exists in a broader social context with widely known conventions. The criticism is that JS doesn’t obey what people would expect. And the resulting confusion is the empirical proof of the problem.
In some cases, you might say, “Well ok, we know people will expect such and such, but such and such is actually a poor design because of X, so we’re going to fix it even if it’s going to cause some confusion”. That is, you can break with convention if you have a good reason. In the case of JS, this defense doesn’t hold up imo.
Ramda’s solution to the problem, which is to add indexing only on explicit request, results in a cleaner, more composable API. Generously, you could argue that “just add i to every function” has practical convenience, despite wtfs like map(parseInt). But that is a debatable counterargument to the criticism. The criticism is at minimum reasonable, even if you don’t think it’s correct.
Also, the fact that map() takes a function that expects 3 args (IIRC) but is commonly passed a function with one argument and things don’t blow up (neither at runtime nor at compile time in TypeScript) is pretty strange and probably the cause of a lot of these kinds of bugs. In general, JS/TS’s approach to “undefined” is difficult for me to intuit about and I’ve been using JS on and off for about 15 years.
It’s vaguely reasonable to not know this, but also the signature of map’s functor is on the first line or two of any documentation.
it’s not an error to call a function with the wrong number of arguments
Because variadic arguments (and the arguments array) are first class language features. This is where we get into “this is a core JS language feature, you really need to know it exists”
Variadic functions are fine, the confusing thing is that all functions can receive 0-N args instead of only those that opt in. Yes this has been in JS from the start, but it’s unusual in casual JS use and in other popular languages.
It is reasonable - and can be useful - for map() to pass things like the index as argument to the mapped function.
It is not. You shouldn’t concern yourself with indices at all if you’re using map et al, and if you need that index, just zip the collection with its indices before mapping.
Or you can remember that other languages have other idioms, and that the index can be useful in principle. There’s also an additional parameter which is the array being enumerated. Together these can be useful.
You my have a functional language puritanical belief about what the API should be, but that’s a subjective opinion, hence it is reasonable that the functor receive additional information about the iteration, especially in a language like JS where variadic functions are first class.
I have the utmost respect for different languages having different idioms (I’ve used quite a lot of OSes and languages throughout the years and am myself bothered when people raised on a blend of Windows, Linux and C-style languages complain that things are just different to what they are used to.) but in this case, it’s just a fractal of bad design.
It’s not just because the map API is misleading, it’s because the extra arguments are thoroughly superfluous and tries to fit imperative idioms into a functional one.
But first and foremost, it’s a bad design because it never took into account how it would match the default parameter convention of JS.
One could also remark that parseInt is a strange global function with a strange name in an object-oriented language with some FP features.
You are right. These are totally reasonable APIs. parseInt in particular is totally fine. For map, it’s a little quirk that’s different than maps in other languages, but it’s forgivable because it is useful.
It really bothers me because of how Javascript functions are called. As roryokane says, you can get around the API by defining an extra function that only takes one argument:
[“10”, “10”, “10”].map(n => parseInt(n))
And that works because Javascript functions will ignore extra arguments instead of throwing an error. Ultimately, it’s that looseness that bothers me.
You’re complaining about dynamic typing then, not JS.
Plenty of languages have varargs, plenty of languages have default parameters. The reason map(parseInt) works is because there is no type checking beyond “can be called”.
If you do want “arguments must match exactly”, then all your common calls to map would need function wrappers, because they’d trigger errors from not handling all three arguments.
You’re complaining about dynamic typing then, not JS.
No, I’m not. Python is dynamically typed, but it will throw an error if you provide an extra argument. Python also has varargs, but it’s opt in which I find reasonable. I want dynamic checks at run time, which this behavior doesn’t give me. (Or checks at compile time. Mostly I want to know if I screwed up.)
Again, parseInt is fine. Default arguments are fine. I don’t like how you can pass extra arguments to a function and they get ignored. I want them to throw an error.
There are a bunch of problems. First variadic functions are a first class and relatively fundamental concept in JS, so you’ve got a different language style. That’s just a basic language choice - you can disagree with it, but given that fact you can’t then say it’s unreasonable when variadic nature of calls comes into play.
Second, I could make a JS style map() function, and pass python’s int() function to it, and it would be called just as JS’s is, because the additional parameter is optional, and hence would be fine. The only difference is that python bounds checks the base and throws an exception.
The issue here is that variadic functions are core to JS from day 1. We’ve generally decided that that is “bad” nowadays, but given the functions have always been variadic in JS, large swathes of APIs are designed around it.
Yes, that’s why I try to avoid JavaScript. There are lots of decisions that were made that seemed reasonable in the past, but turned out to have negative consequences that persist. I’ve been burned by some of them and have a negative opinion of the language.
This is just not a WAT. What it is, is a classic example of people pretending reasonable behaviour and acting as though it is WAT. If you pass a function that takes multiple parameters (parseInt is in modern syntax: function parseInt(string, base=10)), and passing it to another function that will call it with multiple parameters, and then acting like the result is a weird behavior in the language, rather than a normal outcome that would occur in any language.
Start with:
function parseInt(string, base=10) -> int; // which is a fairly standard idiom across many languages for defining this function
and then say “map passes the value and the index to the mapping function”, and ask what the outcome would be.
What we’re seeing here is a person saying “I am blindly assuming the same map API as other languages”, and then acting like the API not being identical is somehow a language WAT. It’s right up with the WATs claiming “’{}’ is undefined and ‘+{}’ is NaN”, or “typeof NaN == ‘number’”, etc
Then again, your reply is a classic example of a language afficiando taking a clear WAT and explaining it as reasonable behaviour – even though it quite clearly is a bit of a problem for users of the standard map function coming from practically every other language.
Granted, like almost every other WAT, it’s a rather minor one for anyone who’s been writing JS/TS for more than a week. Still, I’d very much like a linter to automatically catch these for me. Even experienced people make dumb mistakes sometimes, and it’s just a complete waste of time and energy. Rust, a much stronger typed language (whose type checker already wouldn’t allow this), contains a linter that does catch problems similar to this one.
Then again, your reply is a classic example of a language afficiando taking a clear WAT and explaining it as reasonable behaviour – even though it quite clearly is a bit of a problem for users of the standard map function coming from practically every other language.
I’m unsure what to say here. map in JS passes the index of the iteration, and the array being iterated. That is what it does. If you pass any function to map it will get those parameters. If the function you pass has default parameters, those will be filled out.
Similarly, parseInt takes an optional base that defaults to 10.
I get that people are quite reasonably tempted to do map(parseInt), but I cannot work out what JS is meant to do here different, unless people are saying map should explicitly check for parseInt being passed in?
I suppose the simplest way to fix this would for map’s type to be like it is in Haskell, i.e.
(a -> b) -> [a] -> [b]
So the function map takes as argument takes just one argument, and map does not pass an index of the iteration.
I don’t doubt that there are valid reasons why this would be cumbersome in Javascript/Typescript, but I’m not familiar with them. Index of the iteration is not typically needed in functional style, so it’s kind of redundant and as shown here, potentially harmful.
function parseInt(string, base=10) -> int;
// which is a fairly standard idiom across many languages for defining this function
and then say “map passes the value and the index to the mapping function”, and ask what the outcome would be.
Alas, that’s backwards. Things are surprising not when they are inexplicable in hindsight, but when they are not expected in advance. You have to start from what the user sees, then follow the network of common associations and heuristics to identify likely conclusions.
If the common associations lead to the correct conclusion, the interface is probably fine. If common associations lead to the wrong conclusion, there will be many mistakes — even by users who also know the correct rules.
So in our case, the user starts by seeing ths:
[10, 10, 10].map(parseInt)
Now the user has to remember relevant facts about map and parseInt and come to the correct conclusion. Here’s a list of common associations that lead to the wrong answer.
I can use parseInt(mystring) to mean parseInt(mystring, 10)
I can use parseInt(mystring) without needing to remember parseInt(mystring, base) exists
f is simply another way to write x => f(x)
If the inputs to a .map() are all the same, the outputs are all the same
[10, 10, 10].map(f) is normal code that does not require special attention.
Alas, that’s backwards. Things are surprising not when they are inexplicable in hindsight, but when they are not expected in advance. You have to start from what the user sees, then follow the network of common associations and heuristics to identify likely conclusions.
parseInt takes a base argument. map provides the base. the semantics of both are reasonable. The same thing would happen in any other language.
Now the user has to remember relevant facts about map and parseInt and come to the correct conclusion.
Yes. the user should know the APIs that they’re using. The problem here is that many modern languages have a version of map that has a different API, and end users blindly assume that is the case.
Array.map() provides a third argument. Other languages would blow up when parseInt() was called with two arguments. You seem to argue elsewhere that JavaScript’s parseInt() accepts more than 2 arguments (because all functions in JavaScript accept ~arbitrarily large numbers of parameters), but that’s precisely what’s bizarre (and as many contend, error prone) about JavaScript–that we can call a function whose signature is function(a, b) with 3 arguments and it doesn’t blow up.
The issue isn’t that people don’t understand map()‘s signature, it’s that JavaScript is unusually permissive about function calls which makes it easy (even for people who understand that JavaScript is unusually permissive about function calls) to make mistakes where they wouldn’t in other languages.
No, you want the over application to work, because that’s how JS works, hence how APIs are designed. The alternative is that you would have to do (for example)
someArray.map((value,a,b)=>value*2)
I get that people don’t like JS functions being variadic, but they are, and that means APIs are designed around that variadic nature.
I mean, the obvious alternative is to make map()’s functor accept one argument, but that’s beside the point. We were debating about whether Javascript was uniquely confusing and the answer is “yes, Javascript functions are (uniquely) variadic by default, and this seems to result in many bugs and WATs”. To add to the confusion, it also has a syntax for expressing that the programmer intends for the function to be variadic: function(…args).
If you pass a function that takes multiple parameters (parseInt is in modern syntax: function parseInt(string, base=10)), and passing it to another function that will call it with multiple parameters, and then acting like the result is a weird behavior in the language, rather than a normal outcome that would occur in any language.
It seems like the Go compiler disagrees with you:
func CallWithThreeArgs(f func(int, int, int)) {
f(0, 0, 0)
}
func TakesTwoArgs(int, int) {}
func main() {
// cannot use TakesTwoArgs (value of type func(int, int)) as func(int, int, int) value in argument to CallWithThreeArgs
CallWithThreeArgs(TakesTwoArgs)
}
You’re right, calling a function with an incorrect number of parameters does fail. So let’s try what I said: calling a function assuming one API, when it has another. It’s a bit harder in statically typed languages because (A)->X and (A,B=default value)->X are actually different types. In C++
The problem is you’ve misunderstood the claim. The claim isn’t “I can pass a function that takes N and expect it to be called with M arguments” it is “I can pass a function that takes a variable number of arguments, and that will work”. Referencing rust isn’t relevant as it has no concept of variadic functions or pseudo variadic with default parameters. C++ does, and I can make that happen with [](std::string, int = 10){...} which could be passed to a map the passes an index or one that does not, and both will work.
Your example of python however is simply wrong. If python’s map passed the index:
def map(array, fn):
result = []
for i in range(0, len(array)):
result.append(fn(array[i], i))
return result
you could call that map function, passing int() as the functor, and it would be called. The only reason it would fail is int() bounds checks the base. The behaviour and language semantics are the same as in JS.
Now you can argue about details of the map() API, but the reality is the JS is a language that allows variadic calling, so it is a reasonable choice to provide those additional parameters, You can argue much more reasonably that parseInt should be bounds checking the base and throwing an exception, but again the language semantics and behaviour are not weird.
Your claim was that any language would behave as JavaScript does when passing multiple parameters to a multiple parameter function.
I can pass a function that takes a variable number of arguments, and that will work
Ok, so you’re arguing that unlike other languages, all functions in JavaScript take a variable number of arguments which is just rephrasing the criticism that JavaScript is unusual (thus more programmer errors) and contradicting your claim that it’s behaving as any other language would do (e.g., “but again the language semantics and behaviour are not weird”).
Your example of python however is simply wrong. If python’s map passed the index:
Respectfully, you’re mistaken. Array.map() passes a third parameter to fn. If we update your Python example accordingly, it blows up as previously discussed:
def map(array, fn):
result = []
for i in range(0, len(array)):
result.append(fn(array[i], i, array)) // TypeError: int() takes at most 2 arguments (3 given)
return result
I’ve never heard the acronym WAT before, but I would call JS’s “map” function a footgun for sure. It’s named after a classic/ubiquitous functional, uh, function dating back to early LISP, and has the same purpose, but passes different parameters. Yow.
(But I have no problem with parseInt, or JS’s parameter passing in this instance. It’s all on map.)
Thus, the incorrect version was calling parseInt like this:
> parseInt('10', 0, strs) // parse with explicitly assumed radix due to `0`
10
> parseInt('10', 1, strs) // parse with invalid radix of 1
NaN
> parseInt('10', 1, strs) // parse with radix 2. That is, '10' in base 2 is…
2
The first one is guaranteed to invoke the pip matching the interpreter. With the second one, it can be the case that Python and pip in the PATH are from different installations.
Well, python3 -m pip is definitely using the right interpreter, but the tradeoff is that it’s no longer guaranteed to be the right pip, since a pip.py in the current directory would mess it up, for example.
Sometimes I really feel like the py3k compatibility break was wasted.
Could someone explain this to me? What the holy hell happens to make it produce the crazy result
[10, NaN, 2]
⁉️I had never seen this before and just had to try typing it in to see what happened … I guess I don’t know JS as well as I thought I did.
I think map passes the value AND index of the array element to the mapper function, so you get the result of “10” in base 0 (defaults to base 10), base 1 (error because 1 is not a valid digit in base 1), and base 2 (which is 2).
In general in JS, if you see
[].map(funcName)
, it’s going to bite you in the ass, and you should change it to[].map(x=>funcName(x))
as soon as possible. And do it even faster if it involvesthis
.I understand why, but this shouldn’t be necessary though because it’s verbose and doesn’t meet expectations coming from many languages–introducing a point and needing arguments to get around what sometimes feels like a bug. This is why you’ll see separate functions like
indexedMap
,mapWithIndex
,mapi
, and the like in other languages so you’re not overloading the function arity.Oh yeah, it’s inarguably a bug in javascript, it’s just many decades too late to fix it.
Is there are Clippy-like linter for JS/TS that could warn against mistakes like this one? Deno itself has
lint
, but that doesn’t seem to catch this one. Nor doeseslint
apparently.Neither do TS/flow. And there’s a reason they don’t: https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-functions-with-fewer-parameters-assignable-to-functions-that-take-more-parameters
It’s unfortunate but there’s not much to be done here. You’d have to craft a very strict, one-off eslint rule to prevent this error in your codebase at the cost of some performance and convenience (eg prevent passing anything but an arrow function to Array.map)
An in-depth description of the problem is here, for anyone having trouble following along: https://jakearchibald.com/2021/function-callback-risks/
Linters cannot rule out cases when the argument function is actually supposed to take an element and its index. But at least they could warn about a possible gotcha.
this is typically why I favour lodash/fp versions as they are strictly limited to one argument for easy composition!
Love letters like this make me think I should give JS another shot. Warnings like that remind me that I don’t want to. It’s an abusive relationship.
See I disagree here - it’s very much a “know the APIs you’re using”.
It is reasonable - and can be useful - for map() to pass things like the index as argument to the mapped function.
It is reasonable - and absolutely useful - for parseInt to take a base argument.
What we’re seeing here is when you try to use these two together.
Imagine a statically typed language:
here if you did [“10”, “10”, “10”].map(parseInt) you would call the second map, and so would get [parseInt(“10”, 0), parseInt(“10”, 1), parseInt(“10”, 2)]
Of course, if you know you know, but for non-JS programmers this is very surprising because:
map
passes multiple argumentsI get that it’s not the common map api in other languages, but coming in with “this is the API in some other unrelated language, therefore this is WAT” is simply not reasonable.
For the latter: not needing exact parameter count is a fairly fundamental part of the language. Saying it’s surprising that map(parseInt) invokes the same behavior as anywhere else in the language is “surprising” is again not reasonable.
JS has quirks, but labeling basic language semantics as WAT is tiresome.
It is still a reasonable criticism because
map
exists in a broader social context with widely known conventions. The criticism is that JS doesn’t obey what people would expect. And the resulting confusion is the empirical proof of the problem.In some cases, you might say, “Well ok, we know people will expect such and such, but such and such is actually a poor design because of X, so we’re going to fix it even if it’s going to cause some confusion”. That is, you can break with convention if you have a good reason. In the case of JS, this defense doesn’t hold up imo.
Ramda’s solution to the problem, which is to add indexing only on explicit request, results in a cleaner, more composable API. Generously, you could argue that “just add
i
to every function” has practical convenience, despite wtfs likemap(parseInt)
. But that is a debatable counterargument to the criticism. The criticism is at minimum reasonable, even if you don’t think it’s correct.Also, the fact that
map()
takes a function that expects 3 args (IIRC) but is commonly passed a function with one argument and things don’t blow up (neither at runtime nor at compile time in TypeScript) is pretty strange and probably the cause of a lot of these kinds of bugs. In general, JS/TS’s approach to “undefined” is difficult for me to intuit about and I’ve been using JS on and off for about 15 years.It’s vaguely reasonable to not know this, but also the signature of map’s functor is on the first line or two of any documentation.
Because variadic arguments (and the arguments array) are first class language features. This is where we get into “this is a core JS language feature, you really need to know it exists”
Variadic functions are fine, the confusing thing is that all functions can receive 0-N args instead of only those that opt in. Yes this has been in JS from the start, but it’s unusual in casual JS use and in other popular languages.
It is not. You shouldn’t concern yourself with indices at all if you’re using
map
et al, and if you need that index, just zip the collection with its indices before mapping.Or you can remember that other languages have other idioms, and that the index can be useful in principle. There’s also an additional parameter which is the array being enumerated. Together these can be useful.
You my have a functional language puritanical belief about what the API should be, but that’s a subjective opinion, hence it is reasonable that the functor receive additional information about the iteration, especially in a language like JS where variadic functions are first class.
I have the utmost respect for different languages having different idioms (I’ve used quite a lot of OSes and languages throughout the years and am myself bothered when people raised on a blend of Windows, Linux and C-style languages complain that things are just different to what they are used to.) but in this case, it’s just a fractal of bad design.
It’s not just because the
map
API is misleading, it’s because the extra arguments are thoroughly superfluous and tries to fit imperative idioms into a functional one.But first and foremost, it’s a bad design because it never took into account how it would match the default parameter convention of JS.
One could also remark that
parseInt
is a strange global function with a strange name in an object-oriented language with some FP features.You are right. These are totally reasonable APIs. parseInt in particular is totally fine. For map, it’s a little quirk that’s different than maps in other languages, but it’s forgivable because it is useful.
It really bothers me because of how Javascript functions are called. As roryokane says, you can get around the API by defining an extra function that only takes one argument:
And that works because Javascript functions will ignore extra arguments instead of throwing an error. Ultimately, it’s that looseness that bothers me.
You’re complaining about dynamic typing then, not JS.
Plenty of languages have varargs, plenty of languages have default parameters. The reason map(parseInt) works is because there is no type checking beyond “can be called”.
If you do want “arguments must match exactly”, then all your common calls to map would need function wrappers, because they’d trigger errors from not handling all three arguments.
No, I’m not. Python is dynamically typed, but it will throw an error if you provide an extra argument. Python also has varargs, but it’s opt in which I find reasonable. I want dynamic checks at run time, which this behavior doesn’t give me. (Or checks at compile time. Mostly I want to know if I screwed up.)
Right, but parseInt taking an optional base argument is reasonable, and that makes the arity match; python wouldn’t raise an error there.
Again, parseInt is fine. Default arguments are fine. I don’t like how you can pass extra arguments to a function and they get ignored. I want them to throw an error.
parseInt takes 2 args, map provides 3, so it would error in python.
There are a bunch of problems. First variadic functions are a first class and relatively fundamental concept in JS, so you’ve got a different language style. That’s just a basic language choice - you can disagree with it, but given that fact you can’t then say it’s unreasonable when variadic nature of calls comes into play.
Second, I could make a JS style map() function, and pass python’s int() function to it, and it would be called just as JS’s is, because the additional parameter is optional, and hence would be fine. The only difference is that python bounds checks the base and throws an exception.
The issue here is that variadic functions are core to JS from day 1. We’ve generally decided that that is “bad” nowadays, but given the functions have always been variadic in JS, large swathes of APIs are designed around it.
Yes, that’s why I try to avoid JavaScript. There are lots of decisions that were made that seemed reasonable in the past, but turned out to have negative consequences that persist. I’ve been burned by some of them and have a negative opinion of the language.
map passes the index as a second argument; parseInt accepts a second argument.
This is just not a WAT. What it is, is a classic example of people pretending reasonable behaviour and acting as though it is WAT. If you pass a function that takes multiple parameters (parseInt is in modern syntax: function parseInt(string, base=10)), and passing it to another function that will call it with multiple parameters, and then acting like the result is a weird behavior in the language, rather than a normal outcome that would occur in any language.
Start with:
and then say “map passes the value and the index to the mapping function”, and ask what the outcome would be.
What we’re seeing here is a person saying “I am blindly assuming the same map API as other languages”, and then acting like the API not being identical is somehow a language WAT. It’s right up with the WATs claiming “’{}’ is undefined and ‘+{}’ is NaN”, or “typeof NaN == ‘number’”, etc
Then again, your reply is a classic example of a language afficiando taking a clear WAT and explaining it as reasonable behaviour – even though it quite clearly is a bit of a problem for users of the standard map function coming from practically every other language.
Granted, like almost every other WAT, it’s a rather minor one for anyone who’s been writing JS/TS for more than a week. Still, I’d very much like a linter to automatically catch these for me. Even experienced people make dumb mistakes sometimes, and it’s just a complete waste of time and energy. Rust, a much stronger typed language (whose type checker already wouldn’t allow this), contains a linter that does catch problems similar to this one.
I’m unsure what to say here. map in JS passes the index of the iteration, and the array being iterated. That is what it does. If you pass any function to map it will get those parameters. If the function you pass has default parameters, those will be filled out.
Similarly, parseInt takes an optional base that defaults to 10.
I get that people are quite reasonably tempted to do map(parseInt), but I cannot work out what JS is meant to do here different, unless people are saying map should explicitly check for parseInt being passed in?
I suppose the simplest way to fix this would for map’s type to be like it is in Haskell, i.e.
(a -> b) -> [a] -> [b]
So the function map takes as argument takes just one argument, and map does not pass an index of the iteration.
I don’t doubt that there are valid reasons why this would be cumbersome in Javascript/Typescript, but I’m not familiar with them. Index of the iteration is not typically needed in functional style, so it’s kind of redundant and as shown here, potentially harmful.
Alas, that’s backwards. Things are surprising not when they are inexplicable in hindsight, but when they are not expected in advance. You have to start from what the user sees, then follow the network of common associations and heuristics to identify likely conclusions.
If the common associations lead to the correct conclusion, the interface is probably fine. If common associations lead to the wrong conclusion, there will be many mistakes — even by users who also know the correct rules.
So in our case, the user starts by seeing ths:
Now the user has to remember relevant facts about
map
andparseInt
and come to the correct conclusion. Here’s a list of common associations that lead to the wrong answer.parseInt(mystring)
to meanparseInt(mystring, 10)
parseInt(mystring)
without needing to rememberparseInt(mystring, base)
existsf
is simply another way to writex => f(x)
parseInt takes a base argument. map provides the base. the semantics of both are reasonable. The same thing would happen in any other language.
Yes. the user should know the APIs that they’re using. The problem here is that many modern languages have a version of map that has a different API, and end users blindly assume that is the case.
Array.map() provides a third argument. Other languages would blow up when
parseInt()
was called with two arguments. You seem to argue elsewhere that JavaScript’s parseInt() accepts more than 2 arguments (because all functions in JavaScript accept ~arbitrarily large numbers of parameters), but that’s precisely what’s bizarre (and as many contend, error prone) about JavaScript–that we can call a function whose signature isfunction(a, b)
with 3 arguments and it doesn’t blow up.The issue isn’t that people don’t understand
map()
‘s signature, it’s that JavaScript is unusually permissive about function calls which makes it easy (even for people who understand that JavaScript is unusually permissive about function calls) to make mistakes where they wouldn’t in other languages.No, you want the over application to work, because that’s how JS works, hence how APIs are designed. The alternative is that you would have to do (for example)
I get that people don’t like JS functions being variadic, but they are, and that means APIs are designed around that variadic nature.
I mean, the obvious alternative is to make map()’s functor accept one argument, but that’s beside the point. We were debating about whether Javascript was uniquely confusing and the answer is “yes, Javascript functions are (uniquely) variadic by default, and this seems to result in many bugs and WATs”. To add to the confusion, it also has a syntax for expressing that the programmer intends for the function to be variadic: function(…args).
It seems like the Go compiler disagrees with you:
https://go.dev/play/p/BY7rRRqRMwt
As does the Rust compiler:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=93d4aead97995330669c6e48cf8c657a
Here’s Python:
https://trinket.io/python/63d966b40e
You’re right, calling a function with an incorrect number of parameters does fail. So let’s try what I said: calling a function assuming one API, when it has another. It’s a bit harder in statically typed languages because (A)->X and (A,B=default value)->X are actually different types. In C++
The problem is you’ve misunderstood the claim. The claim isn’t “I can pass a function that takes N and expect it to be called with M arguments” it is “I can pass a function that takes a variable number of arguments, and that will work”. Referencing rust isn’t relevant as it has no concept of variadic functions or pseudo variadic with default parameters. C++ does, and I can make that happen with
[](std::string, int = 10){...}
which could be passed to a map the passes an index or one that does not, and both will work.Your example of python however is simply wrong. If python’s map passed the index:
you could call that map function, passing int() as the functor, and it would be called. The only reason it would fail is int() bounds checks the base. The behaviour and language semantics are the same as in JS.
Now you can argue about details of the map() API, but the reality is the JS is a language that allows variadic calling, so it is a reasonable choice to provide those additional parameters, You can argue much more reasonably that parseInt should be bounds checking the base and throwing an exception, but again the language semantics and behaviour are not weird.
Your claim was that any language would behave as JavaScript does when passing multiple parameters to a multiple parameter function.
Ok, so you’re arguing that unlike other languages, all functions in JavaScript take a variable number of arguments which is just rephrasing the criticism that JavaScript is unusual (thus more programmer errors) and contradicting your claim that it’s behaving as any other language would do (e.g., “but again the language semantics and behaviour are not weird”).
Respectfully, you’re mistaken.
Array.map()
passes a third parameter tofn
. If we update your Python example accordingly, it blows up as previously discussed:I’ve never heard the acronym WAT before, but I would call JS’s “map” function a footgun for sure. It’s named after a classic/ubiquitous functional, uh, function dating back to early LISP, and has the same purpose, but passes different parameters. Yow.
(But I have no problem with parseInt, or JS’s parameter passing in this instance. It’s all on map.)
Here’s a detailed answer adapted from a chat message I once sent to a coworker.
Starting from this JavaScript console transcript, in which I’ve split out a
strs
variable for clarity:The correct way to write this is to wrap
parseInt
in a single-argument anonymous function:strs.map(parseInt)
gives unexpected results because of three factors:Array.prototype.map
calls the passed function with multiple arguments:The arguments are
element, index, array
.The
parseInt
function takes up to two arguments –string, radix
:When calling a function in JavaScript, any extra arguments are ignored.
Thus, the incorrect version was calling
parseInt
like this:Yeah, uh, much as I love JavaScript,
parseInt()
is probably the worst place to look for elegance in the design of its equivalent of a standard library. It doesn’t even necessarily default to a radix of 10.Uh, I didn’t know about that. What exactly is the difference ? Maybe that the first one is “your” python, and the second may be another ones pip3 ?
The first one is guaranteed to invoke the pip matching the interpreter. With the second one, it can be the case that Python and pip in the PATH are from different installations.
Well,
python3 -m pip
is definitely using the right interpreter, but the tradeoff is that it’s no longer guaranteed to be the right pip, since apip.py
in the current directory would mess it up, for example.Sometimes I really feel like the py3k compatibility break was wasted.
AFAIK
pip3 install --upgrade pip
fails on Windows most times, because Windows disallows the running executable to replace (or delete) itself.Does Deno have its own typescript compiler or does it bundle the original? How exactly does this work?
It bundles tsc for
deno check
anddeno lsp
. When youdeno run
, it doesn’t by default check the types and usesswc
to quickly strip them.