News for hackers. Stuff that matters.
Re:Don't use functions as callbacks unless they're designed ... by craigds on Saturday February 06, 2021 @04:19AM (#26045351)
*in Javascript.

In most other languages where unwanted extra arguments raise an error instead of being silently ignored, this mostly isn't a problem

Re:Don't use functions as callbacks unless they're designed ... by lliamander on Saturday February 06, 2021 @04:55AM (#26045498)
I'm not terribly impressed with claims that some programming language will hinder a programmer's development of their skills, but a language that teaches its users to reflexively avoid passing functions as arguments in this way is definitely concerning.
Re:Don't use functions as callbacks unless they're designed ... by archi42 on Sunday February 07, 2021 @06:44AM (#26054362)
What language does teach avoiding passing functions? C++ is typed, and passing a function just involves telling the compiler "yo, listen up, this parameter foo is a function taking two ints and returns a boolean. Let me know if I messed this up". Okay, actually it's " std::function<bool(int, int)> foo". The whole class of errors described on the article just disappeared.

Sometimes a dynamically typed language is the right tool for a job, but IMHO this mostly holds for auxiliary stuff. Typed languages are more effort during programming/learning, but the benefit is gigantic.

But maybe I'm just too biased because I'm usually involved with "is this fails, people may [literally] die".

Re:Don't use functions as callbacks unless they're designed ... by foldr on Saturday February 06, 2021 @07:24AM (#26046084)
You mean to avoid passing named functions? The suggested alternative still passes a function. Although unnecessarily wrapping a named function in a lambda is bad code style (in most languages), it doesn’t seem that disastrous of a habit to get into.
Re:Don't use functions as callbacks unless they're designed ... by Geminidog on Saturday February 06, 2021 @09:46AM (#26046808)
You should be impressed by it.

The true nature of type checking is basically a method of hindering you. The set of all correct programs is much smaller then the set of all programs that exist so anything that hinders a programmer from operating in the bigger parent set outside the set of correct programs is a good and impressive thing.

What’s going on here is a type checking issue. JavaScript and typescript is a little too loose. The map method takes a function of <Arity 3, 2, or 1> So if a library changes a function from <arity 1> to <arity 2 or 1> you should get a type error, but the type checker is too loose. It’s subtle.

Basically a type of <arity 3, 2, or 1> should only type check with <arity 3> or <arity 2> or <arity 1> it should not allow <arity 2 or 1>. You see what’s going on here? Subtle.

This is indeed as much of a type checker problem as it is defining what type correctness is. The definition above is simply a way of defining type correctness that fits with our intuition of what is correct for this given situation, so take what I wrote with a grain of salt. There could be situations where the current definition of type correctness in typescript is more correct then the definition I provided.

Our intuition is complex and if you think long and hard enough you may be able to come up with a formal definition of type correctness that perfectly fits our intuition and therefore elegantly unionized typescripts looser definition of correctness and my own stricter definition.

Beware though, often human intuition can be contradictory. This means that a formalization of our intuitive notions of type correctness will also be contradictory and therefore unusable. In other words there may not be a way to type check for this issue while maintaining the convenience of the status quo.

Intuitively I think it’s possible, you just need special syntax to tell the type checker whether to use my stricter definition or the original looser definition that’s in use now.

Also I’m not sure if there’s any type checker in existence that handles that case (don’t know). So I believe this is more than just a JavaScript issue.

Re:Don't use functions as callbacks unless they're designed ... by joshspankit on Sunday February 07, 2021 @03:12PM (#26057801)
> Beware though, often human intuition can be contradictory. This means that a formalization of our intuitive notions of type correctness will also be contradictory and therefore unusable.

It is my very strong suspicion that in almost all (if not all) of the similar cases in programming methodology, there have been not just arguments on both sides, but implementations and real-world lessons on both sides.

A veeeery minor example: I’m equally as sure that someone designed and deployed systems that specifically created errors when you tried to pass less than the required number of params as I am that other people (or even the same people) specifically designed and implemented systems where params were optional (most likely because they hated having to specify empty params every time).

Re:Don't use functions as callbacks unless they're designed ... by choeger on Saturday February 06, 2021 @04:41AM (#26045433)
In a language that actually tries to help you creating error-free code, a type check would prevent this.

This is really just a wonderful example how javascript is a mental burden to the programmers instead of a useful tool.

Re:Don't use functions as callbacks unless they're designed ... by dsego on Saturday February 06, 2021 @05:43AM (#26045673)
How would a type check prevent this?
Re:Don't use functions as callbacks unless they're designed ... by magicalhippo on Saturday February 06, 2021 @06:03AM (#26045760)
Well the issue mentioned is that map calls the callback function with up to three arguments[1].

A type check could prevent this because it would require map to take a reference to a function with three parameters, or the compiler would complain inside the map implementation.

Similarly, passing it a function with only one parameter would be a type violation and the compiler would complain.

Now in a language with type checking, you could still potentially run afoul.

Say the map function was overloaded with one variant for one-parameter callbacks, one variant for two-parameter callbacks etc. Then the compiler might figure out it could use the second overload if the "toReadableNumber" function got changed to take the extra "base" parameter.

So again you end up with the numbers getting converted with a variable base.

Though, IMHO, having such an overloaded map function is inviting trouble and is a very poor design.

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Re:Don't use functions as callbacks unless they're designed ... by mr_toad on Saturday February 06, 2021 @07:48PM (#26051616)
> or the compiler

Functions can be assigned at runtime. It would have to be a runtime error.

Re:Don't use functions as callbacks unless they're designed ... by dirkt on Sunday February 07, 2021 @03:06AM (#26053466)
A proper typesystem for functional types will check at compile-time that in all places where you would assign such a function, the function will always have the right type.

So it's NOT a runtime error, it's a compile-time error, even though you can assign different functions at runtime.

Re:Don't use functions as callbacks unless they're designed ... by mr_toad on Sunday February 07, 2021 @09:04PM (#26060045)
You can assign a reference to a function and pass that to a function that expects a callback, and then change that assignment based on incoming data. And if that’s not enough you can create and modify functions at runtime. And you’d have to content with JavaScript’s spread syntax and rest parameters.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Re:Don't use functions as callbacks unless they're designed ... by magicalhippo on Monday February 08, 2021 @07:02AM (#26063386)
But it would only have to be a runtime error (your words) for the code which is dynamically loaded.

Static code will get checked at compile-time, even though the exact function passed as callback depends on the incoming data via a series of if/case statements say, from a dictionary or whatever.

Re:Don't use functions as callbacks unless they're designed ... by btinker on Saturday February 06, 2021 @05:58AM (#26045742)
It would check the arguments and return type of the function. Map takes a function with three arguments, toReadableNumber only takes one, therefore the functions are of a different type. So someNumbers.map(toReadableNumber) would be an error and not execute at all, instead of being a "bad practice" / potential mistake.
Re:Don't use functions as callbacks unless they're designed ... by dsego on Saturday February 06, 2021 @08:30AM (#26046358)
What if toReadable also takes 3 args of the same type but different meaning?
Re:Don't use functions as callbacks unless they're designed ... by flohofwoe on Saturday February 06, 2021 @09:18AM (#26046626)
You can create custom types to communicate semantics to the type checker. Whether that's always a good idea is arguable though, but the tools are there (e.g. there's a wide field between weak and strong typing, and an overly strong type checker can be quite a hassle to work with while an overly weak type checker isn't much better than duck typing).
Re:Don't use functions as callbacks unless they're designed ... by choeger on Saturday February 06, 2021 @05:50AM (#26045705)
A type check would give map a type, say [a] -> (int -> a -> b) -> [b]. If you try to apply it to any function that does not have the type (int -> a -> b), the check would fail.
Re:Don't use functions as callbacks unless they're designed ... by username90 on Saturday February 06, 2021 @05:57AM (#26045732)
So basically any language that does any form of type checking at all.
Re:Don't use functions as callbacks unless they're designed ... by wruza on Saturday February 06, 2021 @06:27AM (#26045857)
See also magicalhippo’s comment on overloading. We could introduce real types instead of primitives, so that ‘number_formatting_base_t’ would conflict with ‘array_index_t’, but nobody in the world bothers beyond bare ‘int’.
Re:Don't use functions as callbacks unless they're designed ... by username90 on Saturday February 06, 2021 @07:24AM (#26046083)
Yeah, I see the point, in for example C# if you have overloaded methods it tries it best to map it to the accepted type. However you'd have to work really hard to invent a case where a library updates a signature to accept another type, and then you in your code have 2 overloads of a method that you pass and it now choses the wrong version, all while the library change doesn't break any other code.

Scenario: Library changes signature so you always pass functions with more parameters.

Result: This will break almost every codebase, they probably wouldn't do this in a minor patch.

Scenario: Library adds a new signature where you can pass functions with more parameters and keep them overloaded between each other.

Result: Compiler can't identify which of the two signatures to pass your overloaded function and throws a compilation error.

Re:Don't use functions as callbacks unless they're designed ... by kuschku on Saturday February 06, 2021 @07:15AM (#26046046)
Actually, that's what I use inline types in kotlin for.

So username, password are distinct from string and objectid might be backed by an int, but is distinct from int.

Obviously, once compiled, there's no overhead, it's a zero cost abstraction. But an incredibly useful one.

Re:Don't use functions as callbacks unless they're designed ... by Akronymus on Saturday February 06, 2021 @06:30PM (#26051154)
On my personal projects with F#, never got a chance to use it professionally, I actually use them. Makes modeling the data much easier.
Re:Don't use functions as callbacks unless they're designed ... by MaxBarraclough on Saturday February 06, 2021 @06:15AM (#26045799)
Not including TypeScript.
Re:Don't use functions as callbacks unless they're designed ... by xsmasher on Saturday February 06, 2021 @09:03PM (#26051962)
Just to clarify, if you update toReadableNumber to take a second parameter and that parameter is not a number, typescript will complain.*

TypeScript won't catch the original landmine because it ignores the extra parameters; maybe some linter would? Is there a rule that enforces "functions used by map must spell out all the parameters?"

* Example of typescript giving an error on toReadableNumber_v2: https://www.typescriptlang.org/play?#code/FAYw9gdgzgLgBAQwE5...

Re:Don't use functions as callbacks unless they're designed ... by schwartzworld on Saturday February 06, 2021 @06:43AM (#26045919)
If your code has tests, an API change like the one described in the article would get caught immediately.

And it only works because `Array.prototype.map` callback has one required arguments and two optional ones. What language with optional function arguments protects you from this sort of behavior? Do people in that language not test this sort of behavior? Or at least run through the app?

More than that, the whole hurrdurr-javascript-bad thing is so tired. Lots of work is being done in JavaScript. Sure, it has its quirks, but those quirks come with expressiveness. You can use functional or imperative style, throw lambdas around, and it runs on pretty much all the phones and computers in the world.

Re:Don't use functions as callbacks unless they're designed ... by mhh__ on Saturday February 06, 2021 @07:45AM (#26046157)
If you have a good type system you don't need to write tests like this. I test my algorithms not my function calls.

Also, > A lot of work is being done in JavaScript

Is that it? It's one of the best funded languages on Earth, what do you expect - with D for example we can do all the things you mentioned and catch errors like this, and we're basically just some guys working on the language not the combined might of the entire Internet sector.

Re:Don't use functions as callbacks unless they're designed ... by sfvisser on Saturday February 06, 2021 @07:27AM (#26046092)
> What language with optional function arguments protects you from this sort of behavior?

Languages with a sane type system.

TypeScript has no issues catching such mistakes. Writing tests to catch simple type errors is such an incredible waste of time.

Re:Don't use functions as callbacks unless they're designed ... by tom_mellior on Saturday February 06, 2021 @08:07AM (#26046250)
The article has a whole section called "TypeScript doesn't solve this", with examples and stuff. Is it mistaken?
Re:Don't use functions as callbacks unless they're designed ... by sfvisser on Saturday February 06, 2021 @09:30AM (#26046709)
Partially.

TypeScript doesn't complain when you pass in a function that ignores some of its arguments. Which is totally fine and safe. If you upgrade your function from no second argument to a numeric second argument TypeScript will not complain and your program might break.

It will not crash, because it still perfectly type-safe, but it might not behave like you want to. So in that sense the article has a point.

However, this is just one instance of a larger issue with changing the behavior of a function, while keeping the types compatible.

  - Using a function as a callback to .map() with two numeric arguments and then swapping the arguments.
  - Returning a tuple of two of the same type and swapping the order.
  - Returning a string in a new encoding.
  - ...
Basic rule: if it's a type error and the program might crash, TypeScript will complain. If the types are fine and only the behavior changes, TypeScript will (obviously) not complain. Callback functions and optional arguments are not special in this regard.
Re:Don't use functions as callbacks unless they're designed ... by tom_mellior on Saturday February 06, 2021 @10:27AM (#26047151)
Thanks for the explanation.

> If you upgrade your function from no second argument to a numeric second argument [...] It will not crash, because it still perfectly type-safe

It's type-safe using a definition of "type-safe" that is defined relative to the underlying JavaScript model of "corrupt the user's data rather than crash". It wouldn't be type-safe using most other languages' (including Python, for example) model of "functions have a type that includes the number of arguments, and applying them to the wrong number of arguments is meaningless and hence not considered type-safe".

It's fair for TypeScript to use this approach. But it is surprising to many of us, who view type systems as tools for ruling out some dumb functional bugs, not just crashes.

Re:Don't use functions as callbacks unless they're designed ... by macintux on Saturday February 06, 2021 @09:35AM (#26046742)
> It will not crash, because it still perfectly type-safe, but it might not behave like you want to.

Which is generally worse than crashing, because silent data corruption can have far-reaching impacts.

Re:Don't use functions as callbacks unless they're designed ... by giantDinosaur on Saturday February 06, 2021 @07:56AM (#26046207)
Here you go:

  Array.prototype.map = function (func) {
    if (func.length !== 3) {
        throw new Error(
            "bad! this function you've passed must take THREE arguments. Grr!"
        );
    }
    for (let i = 0; i < this.length; i++) {
        this[i] = func(this[i], i, this);
    }
    return this;
  };
;)

(making this workable and bug free is left to the reader or multi-billion dollar corporations)

There can be a non-indexed map as well.

I'm most curious as to who uses the last argument in the JS map function!

Re:Don't use functions as callbacks unless they're designed ... by dragonwriter on Sunday February 07, 2021 @06:50AM (#26054399)
> I'm most curious as to who uses the last argument in the JS map function!

It's convenient for some things that would otherwise require a reduce (but where reduce isn't particularly more efficient, because you just need lookahead/lookbehind) or an imperative loop, like transforming a list to a set of moving averages over the list.

It's a little more expressive than reduce our imperative lots loops in those cases, too.

Re:Don't use functions as callbacks unless they're designed ... by kuschku on Sunday February 07, 2021 @06:37AM (#26054327)
> I'm most curious as to who uses the last argument in the JS map function!

array.map((val, index, {length}) => if (index + 1 == length) { alert("last element") } else { alert("element #"+index) })

Re:Don't use functions as callbacks unless they're designed ... by Chris_Newton on Saturday February 06, 2021 @04:58AM (#26045504)
It also provides a useful example of the limitations of TypeScript’s type checking. TS is an improvement on JS in this area, but sometimes people overestimate the safety guarantees it offers and forget that its type system is still unsound.
Re:Don't use functions as callbacks unless they're designed ... by sfvisser on Saturday February 06, 2021 @07:22AM (#26046072)
TypeScript trivially catches the mistake:

    const func = (i: number, x: boolean) => i * 2
    const nope = [1, 2, 3].map(func) // type error!
Re:Don't use functions as callbacks unless they're designed ... by Chris_Newton on Saturday February 06, 2021 @09:00AM (#26046519)
That case really is a type mismatch, so you would hope a decent static type system would catch it.

However, you don’t see the same warning in the case of functions that can be called with variable numbers of arguments if the types of the arguments being unintentionally supplied do match, because within the rules of TS, this is working as designed.

Combined with the perhaps unfortunate decision to provide a standard `map` function that doesn’t use its callback as most languages do, there is still the potential for an unexpected change of behaviour that the type checker can’t warn you about here.

Re:Don't use functions as callbacks unless they're designed ... by giantDinosaur on Saturday February 06, 2021 @07:34AM (#26046115)
It doesn't if the x parameter in func is :number.
Re:Don't use functions as callbacks unless they're designed ... by sfvisser on Saturday February 06, 2021 @07:43AM (#26046148)
Because that's no longer a type error. TS is not going to help you calling the right function. :)
Re:Don't use functions as callbacks unless they're designed ... by culturedsystems on Saturday February 06, 2021 @09:27AM (#26046690)
It kind of is a type error, in that the error comes from the programmer incorrectly thinking that the function has type t => r, when it actually has type (t, s) => r. It's not a type error from TypeScript's point of view, because according to TypeScript's rules, both t => r and (t, s) => r are subtypes of (t, s, u) => r. The point of type systems is to detect cases where the programmer is wrong about the types of some value they're using, so it is a limitation of TypeScript that it wasn't able to detect the error in this case (I'm not sure if there's a good way to allow it to detect this particular error while also maintaining compatibility with JavaScript, but it is a cost that's being paid for that compatibility).
Re:Don't use functions as callbacks unless they're designed ... by Chris_Newton on Saturday February 06, 2021 @10:02AM (#26046951)
TS is taking on an impossible challenge in trying to add a robust type system on top of JS without harming compatibility. Its designers have chosen to favour compatibility where the two can’t be reconciled, and that is a reasonable, pragmatic choice. Better a new language that offers some improvements and lots of people actually use than a new language that offers somewhat more improvements that hardly anyone uses?

Unfortunately, this does mean TypeScript’s type system can’t be entirely sound. A classic situation that is also legal according to the rules of TS but “ought” to fail type checking is something like this:

    let arr_num: Array<number> = [1, 2, 3]
    let arr_opt: Array<number | null> = arr_num  // Erm...
    arr_opt[0] = null  // ERM!!!
Now arr_num[0] is null, clearly violating the intended type constraint.

This problem could be fixed by making it an error to alias arr_opt to arr_num. However, that might also cause a lot of extra work for anyone trying to migrate an existing JS code base, particularly if the types involved are not of their choosing but instead determined by code written elsewhere.

For example, if you called a library function that returned an Array<number> and you passed that into another library function that required an Array<number | null> and wasn’t going to modify that array, enforcing the constraint could mean that working code was broken for no real benefit.

Then you get into deeper questions about enforcing immutability using the type system, and finding that again you’re building on sand because you still have JS underneath. IMHO, it’s hard to blame the TS designers for not wanting to go down these kinds of rabbit holes.

Re:Don't use functions as callbacks unless they're designed ... by dsego on Saturday February 06, 2021 @08:57AM (#26046499)
In the end isn't that the same argument for JS? The programmer is responsible for passing a correct callback that accepts the 3 params that the map() provides, ie "calling the right function".
Re:Don't use functions as callbacks unless they're designed ... by spion on Saturday February 06, 2021 @07:39AM (#26046132)
I think the author may have gotten their TypeScript example wrong.

Yes, TS is fine with passing more arguments to a callback that takes fewer. The callback cannot possibly use the additional arguments, so it doesn't matter what gets passed as it will not change the outcome.

This is very different from passing the wrong kinds of arguments to functions that do read them and do something with them, like parseInt.

Now, if you decide to pass a function with an optional second argument that matches the second argument that will get passed to the callback and expect that it will not be used because why would anyone pass additional arguments to a map callback - then yes, you will have the problem again.

  function addOneByDefault(num: number, addAmount = 1) {
    return num + addAmount
  }

  [1,2,3,4].map(addOneByDefault) // this typechecks but works poorly
This extra example is missing in the article and might be helpful to add.
Re:Don't use functions as callbacks unless they're designed ... by MaxBarraclough on Saturday February 06, 2021 @06:27AM (#26045856)
Does Dart handle it better?
Re:Don't use functions as callbacks unless they're designed ... by Geminidog on Saturday February 06, 2021 @11:19AM (#26047519)
It is very possible for a type checker to catch this mistake. I illustrate the intuition here:

https://news.ycombinator.com/item?id=26046808

Re:Don't use functions as callbacks unless they're designed ... by Chris_Newton on Saturday February 06, 2021 @01:33PM (#26048676)
I don’t see any disagreement that some type checker could catch this unintended behaviour. Many popular languages have checkers that would. The question here appears to be whether TypeScript’s type checker could do it without other consequences that are considered unacceptable.
Re:Don't use functions as callbacks unless they're designed ... by Geminidog on Saturday February 06, 2021 @06:32PM (#26051166)
Read carefully. The distinction here is that the type checker must allow for intended behavior within JavaScript while checking for an error.

The type checking I am talking about is not a sum type. It is not that the function can take a two different possible types. It's the fact that the parameter function can mutate into two different types depending on the usage. It has (<arity 1 or 2>) not (<arity 1> or <arity 2>) if you catch my meaning.... Or in other words the concrete type is not evaluated when you pass the function as a parameter but only when it is called with a certain amount of parameters... which is not something type checkers I know about look for.

Re:Don't use functions as callbacks unless they're designed ... by Chris_Newton on Saturday February 06, 2021 @07:25PM (#26051476)
The fundamental problem with the example under discussion seems to be that while the behaviour might not be intended by the programmer, it is working as specified as far as the language is concerned and changing that specification to make the unwanted behaviour fail a type check could have additional and unwanted side effects.

Perhaps I’m not correctly understanding your idea around arity as part of the function types, but so far it’s not obvious to me how what I think you’re describing helps to resolve that contradiction. Are you suggesting a way the type system could be changed without causing those additional, unwanted side effects?

Do you by any chance have a more rigorous definition or even a formal semantics for your proposed arity types that you could share, so the rest of us can understand exactly what you’re proposing here?

Re:Don't use functions as callbacks unless they're designed ... by Geminidog on Saturday February 06, 2021 @07:52PM (#26051630)
> The fundamental problem with the example under discussion seems to be that while the behaviour might not be intended by the programmer, it is working as specified as far as the language is concerned and changing that specification to make the unwanted behaviour fail a type check could have additional and unwanted side effects.

You don't need to change the behavior of the program. You can change the type checker to catch the unwanted error.

>Perhaps I’m not correctly understanding your idea around arity as part of the function types, but so far it’s not obvious to me how what I think you’re describing helps to resolve that contradiction. Are you suggesting a way the type system could be changed without causing those additional, unwanted side effects?

It's not formalized anywhere to my knowledge and I'm not willing to go through the rigor to do this in the comments. But it can easily be explained.

Simply put, what is the type signature of a function that can accept either two variables or one variable? I've never seen this specified in any formal language.

To fix this specific issue you want the type signature here to specify only certain functions with a fixed arity.

When some external library is updated with a function that previously had arity 1 to <arity 1 or 2> that could be thought of as type change that should trigger a type error.

Right now type checker recognizes F(a) and F(a, b=c) (where c is a default parameter that can be optionally overridden) as functions with matching types.

  F(a) == F(a, b=c)
  F(a,b) == F(a, b=c) <-----(F(a,b) in this case is a function where b is NOT optional) 
  F(a) != F(a, b) 
From the example above you can see the type checker lacks transitivity (a == c and b == c does not imply a == b), because the type of a function with an optional parameter is not really well defined or thought out.

This is exactly the problem the author is describing. The type checker assumes that when the library changed F(a) to F(a, b=c) that the types are still equivalent, but this breaks transitivity so it's a bad choice and will lead to strange errors because programmers assume transitivity is a given.

You don't see this problem in other type checkers because JavaScript is weird in the sense that you can call a function of arity 1 with 5 parameters.

Re:Don't use functions as callbacks unless they're designed ... by keyle on Saturday February 06, 2021 @05:54AM (#26045721)
Exactly, Typescript still remains a, transpiler.
Re:Don't use functions as callbacks unless they're designed ... by fulafel on Saturday February 06, 2021 @06:01AM (#26045751)
I don't think we can excuse it by citing the nature of source-to-source compilers. Other languages like Haxe, Elm, ReasonML, ClojureScript etc that target JS don't suffer from this.
Re:Don't use functions as callbacks unless they're designed ... by searchableguy on Saturday February 06, 2021 @06:05AM (#26045774)
Unsound type system was a sacrifice made for easy migration from javascript.
Re:Don't use functions as callbacks unless they're designed ... by Aeolun on Saturday February 06, 2021 @06:16AM (#26045803)
And wildly successful at that. At least I can use typescript with my existing codebase. Rewriting everything in haxe isn’t exactly appealing.
Re:Don't use functions as callbacks unless they're designed ... by fulafel on Saturday February 06, 2021 @06:10AM (#26045787)
That may be for the static type system but you could still check and signal an error at runtime instead of silently ignoring it.
Re:Don't use functions as callbacks unless they're designed ... by lloeki on Saturday February 06, 2021 @06:00AM (#26045750)
In Ruby there’s a similar situation.

While blocks, procs, and lambdas all have arity metadata, only lambdas check for the argument count when called. The other two drop excess arguments and fill missing arguments with nil.

Re:Don't use functions as callbacks unless they're designed ... by Toutouxc on Saturday February 06, 2021 @07:03AM (#26045989)
I think this problem is almost nonexistent in Ruby.

If you're inlining your block as a literal do-end block on the call site, it's just a matter of knowing what kind of data you're calling the block-taking method on. So blocks are kinda different.

If you're designing a more intricate piece of code to be used repeatedly by a 'map' or 'reduce' (like in the example), nothing is preventing you from defining a lambda instead of proc. And nothing is preventing you from designing your library so that it exposes only arity-checking lambdas to the outside.

But it's also quite usual to define callbacks as plain old methods (e.g. Rails before and after actions). Methods can be easily used as a block by getting the actual Method object first with the 'method' method, then using the & syntax to automatically convert them to a proc (e.g. map(&method(:foobar)) which again, converts them to arity-checking lambdas.

Re:Don't use functions as callbacks unless they're designed ... by lloeki on Saturday February 06, 2021 @07:41AM (#26046139)
Yep the problem is very much reduced but it still exists: an API/DSL provider that hinges on blocks can change the args under your feet and it would only blow up when the argument values start to receive unexpected methods, instead of at the interface.

As you mentioned, lambdas and methods check for that, but it’s sad to have to give up the syntactic and lexical niceties of blocks.

Re:Don't use functions as callbacks unless they're designed ... by chadlavi on Saturday February 06, 2021 @08:57AM (#26046497)
I was about to say, just use typescript and it'll throw you an error that the function expected one argument but received three.
Re:Don't use functions as callbacks unless they're designed ... by pfdzm on Saturday February 06, 2021 @09:05AM (#26046548)
This won’t prevent all errors - the author explicitly addresses this towards the end.
Re:Don't use functions as callbacks unless they're designed ... by chadlavi on Saturday February 06, 2021 @09:20AM (#26046643)
Yes. I meant specifically just the "too many args" issue
Re:Don't use functions as callbacks unless they're designed ... by Blikkentrekker on Saturday February 06, 2021 @05:17AM (#26045574)
This would have been one of the things one would expect to be fixed in strict mode.
Re:Don't use functions as callbacks unless they're designed ... by z3t4 on Saturday February 06, 2021 @08:26AM (#26046345)
You should check the parameters. For example:

  function toReadableNumber(num, base, trap) {
    if(base == undefined) base = 10;
    if(typeof base != "number") throw new Error("Second argument should be the base! base=" + base + " (" + (typeof base) + ")");
    if(trap != undefined) throw new Error("Did not expect a third argument. Are you using this with map? Then use an intermediate function.");
Re:Don't use functions as callbacks unless they're designed ... by kayodelycaon on Saturday February 06, 2021 @09:34AM (#26046734)
This won't help in most cases, because you're not going to be able to get the people writing libraries to add guards to every single function they make.

This is a problem that should be handled at the language level, not by adding multiple lines of potentially incorrect code for every dozen lines of regular code.

Re:Don't use functions as callbacks unless they're designed ... by z3t4 on Saturday February 06, 2021 @11:14AM (#26047471)
You do not have to add "guards" to every function, just the functions publicly available via API. And you only need to add them when making (breaking) changes, like adding more parameters, but most of the time you can figure out what the caller wants to do and keep your code backwards compatible.

Also you should wait until your API is somewhat stable before adding the guards. So for most code, you do not need guards. But if your code is used by many, that defensive coding/guards, taking only a few minutes to add, will save countless man-hours that would otherwise be spent debugging.

As a general rule I like errors to throw early. So when I found a bug, (I first write a test to automatically reproduce the bug, then) I backtrack and add guards to each step (with helpful debug/data in the error message), so that the bug would be caught at the surface, rather then causing weird issues several layers down.

And guards are much easier to write then complicated type definitions. And the errors will be more informative, helpful and human friendly then errors from a type-checker.

Defensive coding is mostly useful in long living apps that have a lot of state, and which is constantly developed (new features added, breaking changes, etc). You would not need defensive code in programs that are executed once and then thrown away.

Re:Don't use functions as callbacks unless they're designed ... by franciscop on Saturday February 06, 2021 @05:30AM (#26045622)
I personally prefer extra arguments to just be ignored (and allow to use a default like `function fn(a, b='x') {`). I also believe in most other _dynamic_ languages this is allowed, so it's just about what kind of language it is, not just JS vs the rest.
Re:Don't use functions as callbacks unless they're designed ... by masklinn on Saturday February 06, 2021 @06:05AM (#26045770)
> I also believe in most other _dynamic_ languages this is allowed

Most definitely not. It's not allowed in Python, it's not allowed in Ruby, it's not allowed in any Lisp I know of[0], … it is allowed in PHP, which is about what I'd expect from that[1]. In most dynamic languages the arity is not a suggestion[2].

Which is exactly the issue at hand: `Array#map` was (stupidly) defined as calling its callback with 3 parameters. The last 2 are useless 99.99% of the time (and in better language you'd compose them in if and only if you needed them), as a result it's almost universal that you'd pass single-parameter callbacks which works… until it doesn't because the callback now takes 2+ parameters and starts taking in account the previously ignored garbage `Array#map` feeds it. The average JS developer likely doesn't even know Array#map callbacks receive 3 parameters, and usually aren't going to think about it: in 99% of cases it's has no relevance whatsoever.

[0] but most lisps make significant uses of variable-arity functions, which is a very different and much more formal proposition

[1] PHP's one saving grace being that HoFs have historically not been much of a thing, though I have not tracked how it's used these days

[2] as long as it's present at all AFAIK in Perl functions don't have formal parameters lists

Re:Don't use functions as callbacks unless they're designed ... by franciscop on Saturday February 06, 2021 @06:23AM (#26045837)
This seems to work on Python:

  def hello(a, b = 'world'):
      print(a, b)

  hello('hello')
  hello('hello', 'world')
But these don't, so fair point:

  hello('hello', 'world', 'there)
  # nor
  def hello(a):
  ...
  hello('hello', 'world')
Re:Don't use functions as callbacks unless they're designed ... by username90 on Saturday February 06, 2021 @07:33AM (#26046110)
Yeah, problem with JavaScript function signatures is that every function argument is optional, including all the arguments you didn't write.

In python those functions would look like:

    def F(a = None, b = None, *args): ...
And you can't write any other kind of function in javascript. I don't really like that aspect of javascript, it creates so many hard to debug situations.
Re:Don't use functions as callbacks unless they're designed ... by mechEpleb on Saturday February 06, 2021 @07:33AM (#26046112)
The first is an example of default argument syntax, it doesn't mean you can call functions with extra arguments, only that a value will be provided from the declaration if the call doesn't.