Other than that, functions should be defined by the keyword.
> Other than that, functions should be defined by the keyword.
Says who?
Serious arguments would be:
- readability
- greppability
const foo = () => {}
This function is not anonymous, it's called foo.function foo(){} is also callable if bar is defined before foo.
const foo = function (){}
without its own name before (). These behave like expressions and cannot be hoisted.I haven't figured out if people consider this a best practice, but I love doing it. To me the list of called functions is a high-level explanation of the code, and listing all the definitions first just buries the high-level logic "below the fold". Immediately diving into function contents outside of their broader context is confusing to me.
Hoisting also enables cross-imports without helper unit extraction headaches. Many hate js/ts at the “kids hate == and null” level but in reality these languages have a very practical design that wins so many rounds irl.
const foo = () => {}
console.log( foo.name );
actually outputs 'foo', and not the empty string that I was expecting. const test = () => ( () => {} );
const foo = test();
console.log( foo.name );
outputs the empty string.Is this behavior required by the standard ?
var foo = function() {};
Except nowadays it too does have the name "foo".The bad examples of arrow functions I saw initially were of:
1. Devs trying to mix them in with OOP code as a bandaid over OOP headahes (e.g. bind/this) instead of just not using OOP in the first place.
2. Devs trying to stick functional programming everywhere because they had seen a trivial example where a `.map()` made more semantic sense than a for/for-in/for-of loop. Despite the fact that for/for-in/for-of loops were easier to read for anything non-trivial and also had better performance because you had access to the `break`, `continue` and `return` keywords.
But many teams will have it as a rule to always use array fns.
let results;
try {
results = await Promise.all(vals.map(someAsyncOp))
} catch (err) {
console.error(err)
}
While you could pull that promises mapping into a variable and keep it thenable, 99% of the time I see the above instead. Promises have some rough edges because they are stateful, so I think it might be easier to recommend swapping that Promise.all for an Promise.allSettled, and using a shared utility for parsing the promise result.I consider this issue akin to the relationship between `sort`, `reverse`, `splice`, the mutating operation APIs, and their non mutating counterparts `toSorted`, `toReversed`, `toSpliced`. Promise.all is kind of the mutating version of allSettled.
> also had better performance because you had access to the `break`, `continue` and `return` keywords.
This is a great point.One more: Debugging `.map()` is also much harder than a for loop.
Should be a judgment call, and the author needs to be used to doing both looping and mapping constructs, so that they are unafraid of the bit of extra typing needed for the loop.
One reason is exactly what the subject of discussion is here, it's easier to string-search with that keyword in front of the name, but I don't need that for trivial inline functions (whenever I do I make it an actual function that I declare normally and not inline).
Then there's the different handling of "this", depending on how you write your code this may be an important reason to use an arrow function in some places.
Arrow functions are also far more concise and ergonomic when working with higher order functions or simple expressions
The main thing to be wary of with arrow functions is when they are used anonymously inline without it being clear what the function is doing at a glance. That and Error stack traces but the latter is exacerbated by there being no actual standard regarding Error.prototype.stack
To me arrow functions mostly just decrease readability and makes them blend in too much, when it should be important distinction what is a function and what is not.
My experience is that newcomers are often thrown off and confused by higher order functions. I think partly because, well let's be honest they just are more confusing than normal functions, but I think it's also because languages often bind functions differently from everything else.
`const cool = () => 5`
Makes it obvious and transparent, that `cool' is just a variable where as:
`function cool() {return 5}`
looks very different from other variable bindings.
const arrow = (a) => (b) => `${a}-${b}`
function verbose(a) {
return function (b) {
return `${a}-${b}`
}
}
function uncurried(a, b) {
return `${a}-${b}`
}
const values = ['foo', 'bar', 'baz']
values.map(arrow('qux'))
values.map(verbose('qux'))
values.map(uncurried.bind(null, 'qux'))
values.map((b) => uncurried('qux', b))code is to express logic clearly to the reader. We should assess it for that purpose, before assess for any derivative, secondary concern such as whether categories of things in code (function etc) visually pops out when you use some specific tool like vim, or grep. There are syntax highlighters for a reason. And maybe if grep sucks with code then build the proper tool for code searching, instead of writing code after the tool.
But it really pains me when I see
export const foo = () => {}
instead of
export function foo() {}
But everywhere else they reduce readability of the code with no tangible benefit I am aware of.
One that could enforce these styles. Because not only is the export const foo = () {}
painful on itself, it will quite certainly get intermixed with the
function foo() {}
and then in the next library a
const foo = function() {}
and so on. I'd rather have a consistently irritating style, than this willy-nilly yolo style that the JS community seems to embrace.
[1] https://eslint.org/docs/latest/rules/func-style
[2] https://eslint.org/docs/latest/rules/prefer-arrow-callback
It's not opinionated, but requiring you to form your own opinion or at least choose from a palette of opinions.
It requires effort to opt-in rather than effort to opt-out.
The community doesn't frown on code that's not adhering to the common standard or code that doesn't pass the "out of the box" linter.
So, if I have a typescript project with a tree of some 20 dependencies (which is, unfortunately, a tiny project), I'll have at least five styles of code when it browse through it. Some JS, some TS, some strictly linted with "no-bikeshedding", some linted with configs that are bigger than the codebase itself. Some linted with outdated. Many not linted at all. It's really a mess. Even if each of the 20 dependencies themselves are clean, beauties, the whole is an inconsistent mess.
> Some JS, some TS
I think the JS community has done remarkably well amongst dynamically typed languages in settling on one form of gradual typing and adopting it fervently (Flow no longer has any market share at all). Whereas the last time I checked Python still had the Mypy/Pywright divide, and Ruby had the Sorbet/RBS dichotomy.
Ultimately though, most of your critique boils down to the fact that JS (unlike Rust and Go) isn't maintained by a single monolithic entity, and therefore there's no one to dictate the standards you're looking for. If Deno were the sole caretaker of JS for example, we'd have a standard linter and formatter devoid of complex configuration, but Deno doesn't control JS.
This is a consequence of JS being a collaborative product of the various browser vendors, TC39, and the server side JS runtimes that have adapted JS to run on servers. The advantage of this of course though is that JS can run natively in the browser. I think that's a decent tradeoff to make in exchange for having to wade through dependencies with different ideas about when it's appropriate to use arrow functions.
myArray.sort(function(a,b){return a-b})
People for some reason treat this syntactic sugar like it gives them some new fundamental ability.`function(a,b){return a-b;}` is different from `(a,b) => a - b`
And `function diff(a,b) {return a-b;}` is different from `const diff(a,b) => a - b;`.
You can search for both: "function" and "=>" to find all function expressions and arrow function expressions.
All named functions are easily searchable.
All anonymous functions are throw away functions that are only called in one place so you don't need to search for them in the first place.
As soon as an anonymous function becomes important enough to receive a label (i.e. assigning it to a variable, being assigned to a parameter, converting to function expression), it has also become searchable by that label too.
Are you searching through every function, or functions that have a very specific parameter?
And whatever you picked, why?
---------------------------------------------------------------
- If you're searching for every function, then there's no need to search for foo.=>, you only need to search for function and =>.
- If you're searching for a specific parameter, then just search for the parameter. Searching for functions is redundant.
---------------------------------------------------------------
Arrow function expressions and function expressions can both be named or anonymous.
Introducing arrow functions didn't suddenly make JavaScript unsearchable.
JavaScript supported anonymous functions before arrow function expressions were introduced.
Anonymous functions can only ever be:
- run on the spot
- thrown away
- or passed around after they've been given a label
Which means, whenever you actually want to search for something, it's going to be labelled.
So search for the label.
Really, all you need is `<keyword>` and if the first result is a call to that function, just jump to its definition.
Just search the definition.
Any time that a function doesn't have a definition, it's never the target of a search anyway.
It's 2024 and HN still suggests using regular expressions to search through a code base.
Your special tool might not work on plattform X, fails for edge case - and you generally don't know how it works. With regex or simple string search - I am in control. And can understand why results show up, or investigate when they don't, but should.
As always, people come out with the weirdest of excuses to not use actual tools in the 99.9999% of the cases when they are available, and work.
When that tools doesn't work, or isn't sufficient, use another one like fuzzy text search or regexps.
> and you generally don't know how it works.
Do you know how your stove works? Or do you truly understand what the device you're typing this comment on truly works?
Only in programming I see people deliberately avoid useful tools because <some fringe edge case that comes up once in a millenium in their daily work>
But I prefer tools, that I can use wherever I go. To not be dependant and chained to that environment.
"Do you know how your stove works? Or do you truly understand what the device you're typing this comment on truly works?"
Also yes, I do.
" people deliberately avoid useful tools because <some fringe edge case that comes up once in a millenium in their daily work>"
Well, or I did already changed tools often enough, to be fed up with it and rather invest in tech that does not loose its value in the next iteration of the innovation cycle.
I specialize in one thing only: programming
> But I prefer tools, that I can use wherever I go.
Do you always walk everywhere, or do you use a tool available at the time, like cars, planes, bycicles, public transport?
> rather invest in tech that does not loose its value in the next iteration of the innovation cycle.
Things like "fund symbol", "find usages", "find implementation" have been available in actual tools for close to two decades now.
With search you end up grepping the code twice:
- first grepping for the name
We're literally in a thread where people invent regexes for how to search the same thing (a function) defined in two different ways (as a function or as a const)
- secondly, manually grepping through search results deducing if it's relevant to what you're looking for
It becomes significantly worse if you want to include third-party libs in your search.
There are countless times when I would just Cmd+B/Cmd+Click a symbol in IDEA and continue my exploration down to Java's own libraries. There are next to zero cases when IDEA would fail to recognise a function and find its usages if it was defined as a const, not as a function. Why would I willingly deny myself these tools as so many in this thread do?
gr<bs><bs>ion name<cr>
vs
grname<cr>
or for the current identifier, simply gr<m-w><cr>
I could even make my own useful tools like “\[fvm]gr” for function, variable or field search and brag about it watching miserable ide guys from the high balcony, but ain’t that unnecessary as well.And then you proceed to... invent several pale imitations of a symbol/usages search.
More here: https://news.ycombinator.com/item?id=41435862 so as not to repeat myself
I'm mostly ranting against this weird "we will never use great tools because full-text search" obsession
And yet people are obsessed with never using useful tools in the first place because they can invent scenarios when this tool doesn't work. Even if these scenarios might never actually come up in their daily work.
By using regexps I have an experience that opens many doors, and the fact that they aren’t automatic could make me sad, if only these doors weren’t completely shut without that experience.
And you somehow manage to undersell the rename functionality in an IDE. And I've used move/extract functionality multiple times.
I do however agree that applicable transformations (like upgrading to new syntaxes, or ways of doing stuff as languages evolve) could be applied wholesale to large chunks of code.
I was very careful about how I phrased my comment. Languages can gain points by being greppable, and they can gain points by having a well–implemented mode for my favorite editor, and they can also gain points by having a well–implemented language server. They can do all three to gain the most points (and of course there are many other ways to gain or lose points; this is just one tiny aspect of language design), but they don’t have to. And nobody should try to force every language designer to shave the yak of writing an Emacs mode or a Language Server.
And sometimes you just want to improve some random piece of software written in a language that you’ve never used before, without having to shave a bunch of yaks to get the language server installed. Sometimes all you have is grep and you’ll want to be able to use it on this weird new language.