[HN Gopher] Don't use functions as callbacks unless they're desi...
___________________________________________________________________
Don't use functions as callbacks unless they're designed for it
Author : pavel_lishin
Score : 147 points
Date : 2021-02-04 13:02 UTC (2 days ago)
(HTM) web link (jakearchibald.com)
(TXT) w3m dump (jakearchibald.com)
| mpawelski wrote:
| Well, typescript doesn't solve this because "passing callback
| function that doesn't use all provided parameters" is so common
| in JS world that it would cause much hurdle when migrating JS
| project or when writing idiomatic JS code.
|
| But I definitely can image typescript could provide another
| "strict" (or even something outside of "strict" group, like
| recent "pedantic" option for index access [1]) option that would
| check against that potential errors.
|
| [1] https://github.com/microsoft/TypeScript/pull/39560
| deckard1 wrote:
| First-class functions considered harmful. Of course it's
| JavaScript.
|
| The inmates are not only running the asylum, they built it too.
| mattmanser wrote:
| Haha, someone was blaming 'typescript' after they'd made this
| exact mistake in Ask HN :
|
| https://news.ycombinator.com/item?id=26039826
|
| Strange coincidence.
|
| Edit: I always wondered if jquery's .each deliberately had a
| signature of function(i, ele) to discourage people from mistakes
| like this, or if it was a happy accident.
| brailsafe wrote:
| You could derive a decent interview question out of that. Not
| the specific output of the bad call to parseInt, but simply
| "What arguments can you pass into parseInt?", and then simply
| ask about why it would be bad to pass as a callback to map. I'd
| probably have tripped up on the latter having not read this
| post, because I'd have never thought to not explicitly pass the
| radix to parseInt and rarely use anything but anonymous
| functions in a map call.
|
| Though on second thought, maybe not a great question, hard to
| say. I know I've tripped up on the std sort function having not
| had used it in a while.
| joantune wrote:
| Hmm.. haven't coded in it for a while, but wouldn't typescript
| solve this?! Why are people still using vanilla JS?!
| throw14082020 wrote:
| Perhaps you should read the article
| [deleted]
| weego wrote:
| I think a more important lesson before you get anywhere near this
| one in that logic chain is:
|
| Don't attempt to code for every possible future.
| spinningarrow wrote:
| > The developers of toReadableNumber felt they were making a
| backwards-compatible change.
|
| To me that's the real point. In JavaScript where a function's
| signature is not changed by the number of parameters, adding even
| an optional argument probably constitutes a breaking change.
| tpoacher wrote:
| I'm a bit uncomfortable with the overtly broad use of the term
| "callback" in the article ... most examples aren't callbacks per
| se, just higher order functions expecting function expressions as
| inputs (which may or may not act as "callbacks").
|
| Is this a "in the javascript world everything is a callback"
| thing, or is the author just using the term loosely?
| z3t4 wrote:
| It's not exactly wrong as map does call the function passed to
| it. Maybe the topic was named such as to get up-votes as many
| people do not like callbacks (which is only a convention to
| higher order functions used extensively in Node.js for async,
| as JavaScript by itself does not have any async functions!)...
| The proper title would be something like: "Unexpected
| parameters when using Array.map and new Promise"
| Noumenon72 wrote:
| I agree. I started my notes on this under the heading
| "callbacks" but ended up under "functions". The article could
| be titled "Pass arrow functions and partials, not objects you
| don't control."
| kall wrote:
| Yep that's it. Most of the javascript world is very open to
| beginners and not that worried about being strictly technically
| correct, when it doesn't really matter. A callback is pretty
| much any function that you pass as an argument. It's still not
| as much a programming language community as it is a website
| making community.
| [deleted]
| spion wrote:
| What would be the strict definition of "callback" ?
| interblag wrote:
| The term "callback" is usually used to refer to a function
| that you want called after the completion of an async
| operation (like a database read, a file read, a network call,
| etc).
|
| This article uses the term callback but then uses functions
| passed to "map" as its examples; "map" is synchronous and so
| this usage of the term "callback" is atypical, probably
| atypical to the point that we can just say it's incorrect :).
| Chris_Newton wrote:
| _The term "callback" is usually used to refer to a function
| that you want called after the completion of an async
| operation_
|
| I'm not aware of any authoritative definition, but the term
| "callback" has been used for functions passed in as
| parameters to higher-order functions since long before the
| modern idioms for asynchronous code were around. The first
| use of the term I can remember personally was for the
| comparator function passed to qsort in C. That was probably
| sometime in the 1980s. Another common usage that goes way
| back is for event handlers in event-driven systems.
| bryik wrote:
| FWIW, Wikipedia's defines "callback" as "any executable
| code that is passed as an argument to other code; that
| other code is expected to call back (execute) the argument
| at a given time" and synchronous callbacks are mentioned
| explicitly [0]. I don't think Jake's usage of the word is
| atypical.
|
| 0 - https://en.wikipedia.org/wiki/Callback_(computer_progra
| mming...
| z3t4 wrote:
| A function that will be called... a continuation. eg. "call
| me back when you are ready". or "call me when you are
| outside", "call me when you have arrived", "call me later".
| Twisell wrote:
| Thanks for pointing out this confusion i'm a backend guy and
| anytime i have to deal with JS i keep wondering wtf people are
| talking about and if I'm dumb. Apparently they just switch
| words for free.
|
| This is not helping beginners and it make documentation and qa
| especially hard to search.
| paul_f wrote:
| It is articles and comment threads like this that make me glad
| I am not a Javascript developer :-)
| spion wrote:
| Positional optional arguments seems to be the bad idea here.
|
| If you have to have optional arguments, its probably best to make
| them named.
|
| This would this make an unintended clash a lot less likely. The
| name has to match - and if you have the types, both the type AND
| the name would need to match.
|
| Not only that, it also it lets you have any number of optional
| arguments with a lot less fuss, and pass any subset of them.
| speedgoose wrote:
| I would recommend using eslint and the following rule :
| https://github.com/sindresorhus/eslint-plugin-unicorn/blob/m...
|
| eslint-plugin-unicorn has a lot of great rules, some are
| opinionated but you don't have to use all the rules.
| moron4hire wrote:
| Can't have a rando library break on you if you don't import any
| rando libraries.
| wruza wrote:
| The core issue is that now you're calling a _different_ function.
|
| A type system may help, but the fact persists. Type systems only
| catch changes in (args.length ++ args.map(primitive_typeof)). I
| think that this issue is not with types or arguments, but with a
| loose naming and handling of dependencies and backwards
| compatibility. A theoretical author of toReadableNumber() simply
| ditched one function and introduced another one, in-place.
| Apparently they did that because there is no way in their project
| to fork, retain and maintain both. You may say that types solve
| 95% of this, but dynamic languages exist for a reason, and when
| you feel like using one or see an advantage in it, other
| techniques may be applied. We could resolve that by using semver
| on functions instead of modules (renaming them at import for
| convenience), but nobody does that. Functions are fundamental
| building blocks, they take your args, do their job, return
| results and may have a separate environment (not in js), but
| somehow they are not autonomous entities. In contrast, in a real
| world we use explicit versions of things: gtx 1060 6gb, iphone
| se, cat 6020b, and the same for their part numbers. Nobody
| specifies just "gtx" or "cat" in their package.xls.
| uallo wrote:
| Yes, that annoys me every time I see it. One other example that
| is often used is `someArray.filter(Boolean)`. While that will
| likely never break, it absolutely could.
|
| Thanks for writing this. It's bookmarked and will share it with
| my team when necessary.
| ragnese wrote:
| How could it break? If `Boolean` (which is basically just a
| casting function, right?) changed its signature?
|
| That _does_ sound very unlikely.
| dep_b wrote:
| Hopefully I can avoid stacks tainted by this kind of unnecessary
| "flexibility" until the end of my programming career. Typescript
| was considered the "fix all problems with JavaScript" language
| but I poked in its type system within the first 10 minutes of
| using it.
| notapenny wrote:
| The thing is, most of that flexibility or any of the things
| that could be considered quirks/flaws of the language have been
| around for a long time and probably will be due to browser
| compatibility issues. It isn't great, but it is what it is.
|
| I enjoy TS and for web applications I wouldn't want to do
| without it, but it for sure isn't the "end all" since it still
| needs to work with those issues. Ultimately though in the case
| of some of the examples, a type system in a language with
| optional parameters doesn't excuse you from having to use your
| brain. Especially if you're the kind of developer who thinks
| testing your code is just unnecessary.
| ragnese wrote:
| Yeah, TypeScript sounds great, but I quickly became a skeptic.
| JavaScript is a lost cause. There cannot be a JavaScript++ that
| will be reasonably safe.
|
| I'm not a frontend guy, but I truly don't know what I'd do with
| a frontend project. ClojureScript? Elm? OCaml? I would even do
| JavaScript with JSDoc comments before I'd bother doing
| TypeScript.
| teddyh wrote:
| ...in JavaScript.
| leafario2 wrote:
| I am not sure this really has to do with callbacks - isn't this
| more due to the dangers and hassle of allowing a function with 3
| arguments to be called with just one?
| brailsafe wrote:
| I gathered that it's the danger of allowing a function with 1
| parameter to be called with 3 arguments, but I think you're
| right too.
| Twisell wrote:
| It's actually quite the opposite, in this case JS is allowing a
| function with one argument to be called with two extra argument
| without complaining. In my view it's ways worse.
|
| Optionals arguments (ideally named and with default values) are
| quite easily dealt with and the developer is aware he'll break
| compatibility if he touch them. No error on extra argument is
| another flavor of madness.
| dsego wrote:
| You can still have a signature mismatch if you're not sending
| the correct args. The point is to not pass the function to map
| directly but use the more explicit arrow function.
| choeger wrote:
| Absolutely.
| Aldipower wrote:
| The article content is great and a good advice, but let me nit-
| pick on the title a little bit.
|
| It should be "Don't use functions as _arguments_ unless they're
| desdigned for it".
|
| Function passed as arguments to higher-order function are just
| that, arguments.
|
| A function callback on the other hand is just that, a call back,
| after a longer asynchronous run.
| philipyoungg wrote:
| I've been bitten by this multiple times.
| craigds wrote:
| *in Javascript.
|
| In most other languages where unwanted extra arguments raise an
| error instead of being silently ignored, this mostly isn't a
| problem
| chadlavi wrote:
| I was about to say, just use typescript and it'll throw you an
| error that the function expected one argument but received
| three.
| pfdzm wrote:
| This won't prevent all errors - the author explicitly
| addresses this towards the end.
| chadlavi wrote:
| Yes. I meant specifically just the "too many args" issue
| lloeki wrote:
| 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.
| Toutouxc wrote:
| 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.
| lloeki wrote:
| 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.
| lliamander wrote:
| 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.
| Geminidog wrote:
| 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.
| foldr wrote:
| 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.
| Chris_Newton wrote:
| 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.
| keyle wrote:
| Exactly, Typescript still remains a, transpiler.
| fulafel wrote:
| 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.
| searchableguy wrote:
| Unsound type system was a sacrifice made for easy
| migration from javascript.
| fulafel wrote:
| That may be for the static type system but you could
| still check and signal an error at runtime instead of
| silently ignoring it.
| Aeolun wrote:
| And wildly successful at that. At least I can use
| typescript with my existing codebase. Rewriting
| everything in haxe isn't exactly appealing.
| Geminidog wrote:
| It is very possible for a type checker to catch this mistake.
| I illustrate the intuition here:
|
| https://news.ycombinator.com/item?id=26046808
| Chris_Newton wrote:
| 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.
| sfvisser wrote:
| TypeScript trivially catches the mistake:
| const func = (i: number, x: boolean) => i * 2 const
| nope = [1, 2, 3].map(func) // type error!
| Chris_Newton wrote:
| 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.
| giantDinosaur wrote:
| It doesn't if the x parameter in func is :number.
| sfvisser wrote:
| Because that's no longer a type error. TS is not going to
| help you calling the right function. :)
| dsego wrote:
| 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".
| culturedsystems wrote:
| 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).
| Chris_Newton wrote:
| 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.
| MaxBarraclough wrote:
| Does Dart handle it better?
| spion wrote:
| 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.
| Blikkentrekker wrote:
| This would have been one of the things one would expect to be
| fixed in _strict mode_.
| z3t4 wrote:
| 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.");
| kayodelycaon wrote:
| 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.
| z3t4 wrote:
| 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.
| franciscop wrote:
| 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.
| masklinn wrote:
| > 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
| franciscop wrote:
| 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')
| username90 wrote:
| 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.
| mechEpleb wrote:
| 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.
| [deleted]
| choeger wrote:
| 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.
| schwartzworld wrote:
| 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.
| sfvisser wrote:
| > 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.
| tom_mellior wrote:
| The article has a whole section called "TypeScript
| doesn't solve this", with examples and stuff. Is it
| mistaken?
| sfvisser wrote:
| 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.
| macintux wrote:
| > 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.
| tom_mellior wrote:
| 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.
| mhh__ wrote:
| 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.
| giantDinosaur wrote:
| 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!
| dsego wrote:
| How would a type check prevent this?
| choeger wrote:
| 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.
| username90 wrote:
| So basically any language that does any form of type
| checking at all.
| MaxBarraclough wrote:
| Not including TypeScript.
| wruza wrote:
| 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'.
| kuschku wrote:
| 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.
| username90 wrote:
| 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.
| btinker wrote:
| 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.
| dsego wrote:
| What if toReadable also takes 3 args of the same type but
| different meaning?
| flohofwoe wrote:
| 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).
| magicalhippo wrote:
| 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...
| leni536 wrote:
| The problem in general is a language feature silently increasing
| the API surface.
|
| In this specific case the function was already callable with two
| parameters, without the library author's intent.
|
| Another example to the general rule is the fragile base problem:
| every method is a customization point by default.
|
| These "convenience" features can easily turn into headaches like
| this for library authors.
| franciscop wrote:
| Agreed 100%. As a library author I'd also recommend if you create
| functions to make way for this specific case. It's just very
| useful to be able to `.map()` so allowing people to do it is a
| net gain, I've done so in my library `files`
| (https://documentation.page/github/franciscop/files):
|
| > Ignores the second parameter if it's not an object so you can
| work with arrays better like .map(read)
|
| It's also pretty simple to implement: //
| Assuming we are taking an options object {} export const
| myFunc(arg1, arg2 = {}) => { if (typeof arg2 ===
| 'number') arg2 = {}; // ... rest of the code as
| usual };
|
| So, library authors, do a favour to users and in those functions
| that could be used as a callback, add this option.
|
| Some other tips/niceties I've learned over the years:
|
| - For highly async libraries, allow to accept an unresolved
| promise. It's pretty safe and easy to do on the library-side, and
| will probably remove a bug or two on your users' side.
|
| - Export default and named so the users don't need to worry about
| whether to `import * as files from` or `import files from`
| (assuming the library is small and there's no concern about tree-
| shaking).
|
| - I also use a higher-order promise abstraction I created, Swear
| (https://documentation.page/github/franciscop/swear), but that's
| totally optional and you can treat any of my async libraries as
| normal Promises.
| magicalhippo wrote:
| For me the core issue seems to be that the map function has, in
| essence, three overloads which does very different things.
|
| A strongly typed compiler would not catch the error in the
| article with a similarly overloaded map function, so type
| checking can't really rescue you from this situation.
|
| Sure the chances of it happening silently might be slightly less,
| as the second and third parameter would have to match in type
| (integers in this case), but it could still absolutely happen
| silently.
|
| So to me the core take-away is that overloading a function in
| such a way is a very poor design choice, regardless of language.
| joduplessis wrote:
| Good article, but like - is this really a problem or even a
| pattern people use?
| spinningarrow wrote:
| Yes it is (JavaScript is so widely used that probably every
| pattern is in use somewhere. I myself have done this many times
| and seen it being done as well!)
| joshxyz wrote:
| Yeh, first thought when I saw it was "who does this lol"
| ufo wrote:
| The parseInt bug that's mentioned in thd article is very likely
| to catch you eventually if you use Javascript for long enough.
|
| The habit that I developed as a response to that is to always
| pass a lambda to the callback. That is, write this:
| foo(x => f(x))
|
| instead of foo(f)
| ragnese wrote:
| I've been bitten by it.
|
| I'm used to statically typed languages that allow calling HOF's
| with a function reference. If the signature matches, there's no
| reason to wrap it in a lambda.
|
| So I did the same, reflexively in a Node project recently.
|
| I've learned my lesson and now I just know to always use a
| lambda forJS function arguments.
| notapenny wrote:
| In my experience yes and I use it myself as well, but with
| care. Optional parameters are a thing so when you're doing
| something like the author describe, you need to consider that
| your function is quietly receiving these parameters but just
| discarding them.
|
| I see people using .filter(Boolean) a lot for example. If the
| signature of that were to change, for sure it wouldn't be
| something that quietly gets implemented, but I wouldn't pass
| formatting functions to these operations carelessly, especially
| in a codebase where there may not be tests. Some of the safer
| ways I've seen are use of unary helper functions to wrap the
| callback, or having the callback actually take the arguments
| but discarding them like (element, _index, _array). At least
| that way you communicate some intent.
| dsego wrote:
| I don't think so, maybe before JS had arrow functions. Now you
| would usually have an anonymous arrow function as a callback to
| explicitly state the calling contract. Then inside it apply the
| original function.
| tengbretson wrote:
| Point-free function passing is only valuable from a stylistic
| point of view. In any dynamic language I don't think the
| downsides make it worthwhile.
| hansvm wrote:
| I'll admit to not being fully ready for all of JavaScript's
| footguns when I first switched from other languages, and most of
| the function related ones can be (as this article points out)
| mitigated by blindly wrapping everything in lambdas.
|
| As one fun example suppose you're working with the snippet:
|
| const f = foo.predicate; return arr.filter(f);
|
| If foo is a class instance and references any instance variables
| via `this`, then just storing the method before you attempt to
| use it will cause the whole house of cards to blow up.
| So...adding a reference to `this` is a breaking change for even
| moderately sane code. That problem is also easily mitigated with
| lambdas: const foo = (x) => foo.predicate(x);
| valenterry wrote:
| Let's talk straight about Javascript. Repeat with me:
|
| Literally any kind of change to a function in javascript is a
| breaking change.
|
| Don't believe it? Give me an example of a function and how you
| change it and I will show you the code that works with the
| original function but breaks with the changed one.
| kall wrote:
| How do you feel about adding arguments to a "single object as
| named arguments that is destructured in function declaration"?
| I can also always come up with something that breaks, but seems
| pretty safe.
|
| add({a:2, b:4})
|
| function add({a, b}) { return a + b }
|
| function _add({a, b, multiplier = 1}) { return a+b*multiplier }
|
| challenge: come up with a breaking use that does not involve
| something having a property named "multiplier"
| valenterry wrote:
| First of all, I _can_ demonstrate code that will work when
| using "add" but explodes when using the changed version -
| even with your restriction of not having a "multiplier"
| property.
|
| However, let's keep the tension for while. The fact that
| having a working function using "multiplier" is already a bit
| broken - it shouldn't work from the beginning, but javascript
| is designed so that it does. Hence you have to give me this
| restriction, because otherwise it is obvious how your example
| can be "broken".
|
| Before I expose my (very simple) solution to still break your
| example even without using "multiplier", I would like to ask
| you to try to come up with another example first, where you
| don't require restrictions. I think it's a good exercise. :)
|
| If no one comes up with one, I'll show it in, say, a day from
| now.
| kall wrote:
| Yeah ok, it's a saturday in lockdown, why not. I have a
| hunch that the only way I can do this safely is with a
| bunch of safety checks. I have to think about wether your
| breaking has something to do with the intricacies of
| function arguments in js or if you consider addition of a
| property to any type of "options" object that is passed
| around always breaking.
|
| For day to day, I feel this pattern is good enough, as
| typescript works nicely with it.
| kall wrote:
| Allright, I didn't really come up with anything clever. I
| may have been to sloppy in the original
| function/restriction. Obviously once you start passing non-
| numbers, all bets are off. E.g. a simple add({a: "hello",
| b: "world"}) breaks. Add may well have been intended as a
| string concatination function so yeah, fair enough.
|
| A tricky thing I could see without ever explicitly defining
| "multiplier" (e.g. on the object prototype) is passing a
| Proxy that e.g. has a fallback for all missing properties.
| Detecting a proxy is only kind of possible (?) but we can
| copy all the original target properties from it, which
| should make it safe.
|
| So here goes, my safe solution for modifying function
| signature in a non breaking way: function
| add({ ...args }) { const { a, b, ...rest } = args;
| if (typeof a !== "number" || typeof b !== "number") {
| throw "all arguments must be numbers"; } if
| (Object.keys(rest).length > 0) { throw "You may
| only pass arguments a and b"; } return a +
| b; } function _add({ ...args }) {
| const { a, b, multiplier = 1, ...rest } = args; if
| ( typeof a !== "number" || typeof b !==
| "number" || typeof multiplier !== "number"
| ) { throw "all arguments must be numbers";
| } if (Object.keys(rest).length > 0) {
| throw "You may only pass arguments a, b and multiplier";
| } return a + b * multiplier; }
|
| Most of these issues (not the proxy one) should be solved
| by typescript.
| valenterry wrote:
| Okay, you definitely deserve praise for that this
| prevents problems in a real world scenario. Now I feel a
| bit bad for having made you write so much code.
|
| In the evil world, I can break your code like that:
| try { add(1, 2, 3, 4) } catch (e) {
| if(e !== "You may only pass arguments a and b")
| throw "boom"; }
|
| However, you can of course make your exception string
| generic.
|
| Then I'll have no choice to use one of my jokers: calling
| "add.toString()" and inspect your function in detail.
| Before you scream that this is stupid, please mind that
| this is actually used out there (looking for example at
| you, angular).
| kall wrote:
| Oh you wanna have a go? Let's have a go
| function add({ ...args }) { const { a, b, ...rest
| } = args; if (typeof a !== "number" || typeof b
| !== "number") { throw "no"; }
| if (Object.keys(rest).length > 0) { throw "no";
| } return a + b; } function
| _add({ ...args }) { const { a, b, multiplier = 1,
| ...rest } = args; if ( typeof a !==
| "number" || typeof b !== "number" ||
| typeof multiplier !== "number" ) {
| throw "no"; } if
| (Object.keys(rest).length > 0) { throw "no";
| } return a + b * multiplier; }
| add.toString = () => "nice try"; _add.toString
| = () => "nice try";
|
| Edit: OK I think we are stretching HN comment ettiquete
| to far with this much code. This was fun though. Thanks.
| valenterry wrote:
| Sure, let's do that :) if( Function.pro
| totype.toString.call(add).includes("multiplier") ) throw
| "boom!";
|
| > Edit: OK I think we are stretching HN comment ettiquete
| to far with this much code. This was fun though. Thanks.
|
| Huh? Would you mind to educate me about what part of the
| ettiquete we are not following?
| kall wrote:
| IDK not a real thing, you just never see it. I feel like
| it's a forum not a chat room and it doesn't collapse deep
| threads by default so the long code makes the page very
| long. Probably doesn't matter though.
|
| At this point we can go ahead and break the world:
| add.toString = () => `function add({ ...args }) { const {
| a, b, ...rest } = args; if (typeof a !== "number" ||
| typeof b !== "number") { throw "no"; } if
| (Object.keys(rest).length > 0) { throw "no";} return a +
| b;}`; _add.toString = () => `function add({ ...args
| }) { const { a, b, ...rest } = args; if (typeof a !==
| "number" || typeof b !== "number") { throw "no"; } if
| (Object.keys(rest).length > 0) { throw "no";} return a +
| b;}`; Function.prototype.toString = () => `function
| add({ ...args }) { const { a, b, ...rest } = args; if
| (typeof a !== "number" || typeof b !== "number") { throw
| "no"; } if (Object.keys(rest).length > 0) { throw "no";}
| return a + b;}`;
| valenterry wrote:
| I think it's very interesting for others to follow this.
|
| Now, we are leaving the original scope (not just changing
| a function, but modifying globals). read-only globals
| even. But prepare for my counter: let
| frame = document.createElement('x');
| document.body.appendChild(frame); if( frame.conte
| ntWindow.Function.toString.call(add).includes("multiplier
| ") ) throw "boom!";
|
| You might go to also kill "document.createElement", but
| there are many ways for me to get a new frame. I think
| when we come to the point where all these are disabled, I
| would say only a small fraction of the websites that use
| javascript would still properly operate. It would be your
| victory though. ;)
| bryik wrote:
| > Before you scream that this is stupid, please mind that
| this is actually used out there
|
| That _is_ stupid though. It 's like saying changing a
| private field in Java is a breaking change because
| someone might have used reflection to access it.
|
| Taken to the moronic extreme: any detectable change is a
| breaking change because someone could write a function
| that pulls your latest release and depends on every bit
| being identical with the previous release.
| benburton wrote:
| const add = ({ a, b }) => a + b; const _add = ({ a, b }) =>
| b + a;
| valenterry wrote:
| That one is rather simple to break.
| console.log(add({a:"1", b:2})) //12
| console.log(_add({a:"1", b:2})) //21
|
| So to make the code break with the change to _add, I can
| just do: if(add({a:"1", b:2}) != 12)
| boom()
| benburton wrote:
| Ah of course!
| ric2b wrote:
| id = x => x;
|
| logging_id = x => { log(x); return x };
| valenterry wrote:
| What's log? console.log?
| thomasikzelf wrote:
| How about this? (I am not a 100% about this one)
| f = Object.defineProperties(x => x, {toString:{value:()=>'a'},
| [Symbol.toStringTag]:{value:'a'}})
|
| and we will change it to: f =
| Object.defineProperties(y => y, {toString:{value:()=>'a'},
| [Symbol.toStringTag]:{value:'a'}})
|
| Edit: ah you -can- break it actually. A challenge for others to
| figure out how to break this one.
| valenterry wrote:
| Hehe :) smart one!
| ragnese wrote:
| To be clear, you mean "literally and kind of change to a
| function _signature_ [...] ", right?
| valenterry wrote:
| I wished so, but no. It's even true for changes in a
| function's body. Maybe that's a good hint, no? :)
| [deleted]
| hoppla wrote:
| const readableNumbers = someNumbers.map((item, _, _) =>
| toReadableNumber(item));
| franciscop wrote:
| Why not just:
|
| const readableNumbers = someNumbers.map(item =>
| toReadableNumber(item));
| presentation wrote:
| I understand the point, and also wish typescript were a bit
| stricter about this, but the main reason I still do this
| sometimes is for the filter function when the function I pass in
| is a type guard - wrapping it in an anonymous function requires
| also duplicating the type guard logic, which is enough repetition
| for me to sidestep it. That said it's easy enough to just disable
| the ESLint rule that enforced this limitation for those lines.
| kleiba wrote:
| I don't quite buy it. If a library gets a update that is not
| backward-compatible, it does not matter whether you previously
| called that function as a callback or as direct invocation: your
| code may break one way or the other.
| dsego wrote:
| Yes, but here the argument is to not blindly apply just any
| function to a map since the signatures won't necessarily match.
| It's harder to spot the errors and is just technically wrong,
| even if it does work in that particular situation.
| XelNika wrote:
| That much is obvious. The blog post has an example of something
| that appears to be non-breaking (at least it would be in many
| other languages), but actually does break things. Both the
| library creator and user messed up in the example given, but if
| you're not intimately familiar with the language, both mistakes
| seem reasonable.
| kleiba wrote:
| The keyword here is _appear_ , but in reality it _wasn 't_ a
| non-breaking change.
| XelNika wrote:
| But if you write defensively, like the post suggests, you
| won't be surprised by bad library writers.
| hvdijk wrote:
| Any update is going to be backwards incompatible to some
| extent, even a change to the version number can cause and has
| caused breakage in software that has buggy version checks.
| Because of that, taking backwards compatibility as an absolute
| is not useful, and instead we generally limit it to where the
| old version was being used correctly in supported ways. Whether
| calling a function with more arguments than it is specified to
| take is supported will depend on who is maintaining that
| function, but unless documented I would assume it isn't.
| timdaub wrote:
| > but I still got folks on Twitter telling me to "just use
| TypeScript",
|
| This type of behavior is exactly the reason why I think the
| existence of Typescript adds downsides to being a JS dev.
|
| Saying "just use TS" is of equal value as saying: "just use
| Assembly" or "just use Dart". It has no value.
|
| First an foremost this type of logical behavior is a problem that
| needs to be adressed in JavaScript. The dynamically typped
| language that is embedded into every major browser. A heuristic
| will have to be discovered by JS devs. Or TC39 will have to
| extend the standard.
|
| Ultimately, it then comes down to a contexual personal choice of
| using TS over JS.
|
| When it comes to separation of concerns, I can recommend reading
| this essay by Dijkstra:
| https://www.cs.utexas.edu/users/EWD/transcriptions/EWD04xx/E...
| ragnese wrote:
| Personally, I'm not a fan of TypeScript anyway.
|
| JavaScript is a lost cause to me. I'm not a full time JS Dev,
| so every time I have to touch one of our Node projects at work,
| I mentally prepare myself for very slow dev speed (ironic,
| considering the arguments that dynamically typed, loosey
| goosey, language speed you up) and frustrating bugs around
| `this`, mixed up function arguments, forgetting to await
| promises returned from functions, etc.
|
| But TypeScript really doesn't actually help that much compared
| to an IDE that understands JSDoc. Its type system is unsound
| and it's too accommodating of JavaScript's nonsense.
|
| If I ever start a new project that just _has_ to run on Node, I
| 'd probably try one of these languages that transpiles to JS,
| but is totally different, like Clojure or OCaml.
| ulucs wrote:
| Another way to frame this for library developers: adding
| parameters is a breaking change in javascript, and it should be
| noted in changelogs as such.
| globular-toast wrote:
| Yeah. Pretty horrible design flaw. Being able to add parameters
| in a backwards compatible way is essential for software
| development. I guess the only way to do it is via passing an
| object, which is like passing keyword parameters in other
| languages.
| tetha wrote:
| Or you end up with java-like names once you forgot the option
| object. toReabableNumber, toReadableNumberWithBase,
| toReadableNumberWithBaseAndPrecision, .... or I guess
| toReabableNumberWithOptions.
| simongray wrote:
| Isn't that more Objective-C style?
| wruza wrote:
| Aaand in objc, it would traditionally be:
| [array
| mapSelector:@selector(toReadableNumber:withOptions:)
| fromObject:[NSFormattingManager
| localizedFormattingManager]
| withArguments:@[@{kCFNumberFormattingBaseKey:@(10)}]];
|
| Thank god they invented @-syntax for core type literals.
| hawk_ wrote:
| uhm java has method overloading..
| vorticalbox wrote:
| So does javascript.
| Toutouxc wrote:
| I believe that JS doesn't have function overloading.
| Function overloading, the way I understand it, is the
| ability to declare multiple functions with the same name
| but a different signature (different tuple of arguments)
| and let the runtime/compiler decide upon the
| implementation used.
| dsego wrote:
| I don't understand this argument. Let's say you remove a
| typed param and replace it with a new one of the same type,
| how will your typed language protect you then? Is that a case
| that should be handled in a backwards compatible way?
| Changing a function signature is a breaking change. In some
| cases like function arity or different types your compiler
| could catch it and throw errors (and break your program,
| hence breaking change).
|
| A better language might have a special syntax or specific
| types for mapping functions but that's not the argument you
| were making.
| valenterry wrote:
| > Changing a function signature is a breaking change
|
| Not if your language has default parameters and does not
| allow callers to pass "extra" parameters. Then you can
| easily add a new parameter with a default value and older
| callers will work as expected.
| reeeeee wrote:
| If you remove a typed param and replace it with a new one
| of the same type, typing will indeed not save you. But
| there should be other checks to warn you:
|
| - Version should be bumped - Dependencies should be
| informed via the changelog - Existing tests of dependencies
| should fail
| globular-toast wrote:
| I forgot to say I meant adding an _optional_ new parameter
| to a function. Obviously adding a new parameter to a
| function is a breaking change for any API regardless of
| language.
| masklinn wrote:
| > adding parameters is a breaking change in javascript
|
| TBF adding parameters is a breaking change in most languages
| unless they have defaults, or even then.
|
| In javascript it's a _corrupting_ change, it may _silently_
| break all callers.
| valenterry wrote:
| I would not say that - maybe you meant only dynamically typed
| languages? Even then I'm not sure...
| masklinn wrote:
| > I would not say that - maybe you meant only dynamically
| typed languages?
|
| No I don't? A breaking change means working code doesn't
| work anymore. In a statically typed language, if a
| dependency adds a parameter to a function your code stops
| compiling. That's _very much_ a breaking change.
| valenterry wrote:
| Sorry, for some reason I must have over-read your
| "defaults". Without those you are certainly right.
| whiddershins wrote:
| The way I've seen this dealt with in objective C, and I use
| this pattern in JavaScript, is to make a new function with the
| additional arguments, and then refactor the old function to be
| a convenience function that calls the new function and passes
| defaults to the new parameters.
|
| Original function: doSomething(foo){
| *body* }
|
| Refactored functions: doNewSomething(foo, bar){
| *body* }
|
| //now a convenience function doSomething(foo){
| bar = defaultValue; doNewSomething(foo, bar); }
|
| Doesn't this solve this when updating a library?
| Noumenon72 wrote:
| So when I look up `toReadableNumber(num, base = 10)` in the
| current library documentation and pass it binary 11001010, in
| your codebase I get '11,001,010' instead of 202.
| whiddershins wrote:
| so you would have a new function called
|
| toReadableNumberWithBase(num, base)
| whiddershins wrote:
| Wait, what?
|
| Like if you only pass one variable, you get the old
| behavior, and to pass two variables you have to call a new
| function name.
|
| What am I misunderstanding about what you are writing.
| mhh__ wrote:
| This is partly why I think sacrificing speed and type safety in
| the name of ease-of-writing is pointless in the long run - any
| productivity you gain gets lost in the test suite or in
| heisenbugs hidden down the stack that blow up where you can't
| easily fix them.
| z3t4 wrote:
| If you did not use map, Promise nor object destructuring you
| would not have these problems. Code would not look as cool
| though.
| ragnese wrote:
| I agree and will take it a step further: if you didn't use
| JavaScript, you would not have these problems.
| gorjusborg wrote:
| 1. Not every function invocation is a callback. 2. A JS library
| that changes function arity as if it doesn't break API isn't
| being responsible. 3. This sort of paranoia defensiveness in API
| usage is a direct result of language (not developer)
| shortcomings.
___________________________________________________________________
(page generated 2021-02-06 23:02 UTC)