9 ms·
Rust and Javascript and Lisp all get extra points because they put a keyword in front of every function definition. Searching for “fn doTheThing” or “defun do-t
by db48x 2y ago
Rust and Javascript and Lisp all get extra points because they put a keyword in front of every function definition. Searching for “fn doTheThing” or “defun do-the-thing” ensures that you find the actual definition. Meanwhile C lacks any such keyword, so the best you can do is search for the name. That gets you a sea of callers with the declarations and definitions mixed in. Some C coding conventions have you split the definition into two lines, first the return type on a line followed by a second line that starts with the function name. It looks ugly, but at least you can search for “^doTheThing” to find just the definition(s).
- CGamesPlay 2y agoNot JavaScript. Cool kids never write “function” any more, it’s all arrow functions. You can search for const, which will typically work, but not always (could be a let, var, or multi-const intializer).
- lispisok 2y agoAm I the only one who hates arrow functions?
- spartanatreyu 2y agoI did, until I used them enough where I saw where they were useful. 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.
- mewpmewp2 2y agoAnother benefit of using for instead of array fns is that it is easy to add await keyword should the fn become async. But many teams will have it as a rule to always use array fns.
- jappgar 2y agoThat gives you have the option of making it serially async but not parallel, which can be achieved easily using Promise.all in either scenario.
- adregan 2y agoAs an aside: It’s way less ergonomic, but you likely want `Promise.allSettled` rather than `Promise.all` as the first promise that throws aborts the rest.
- wruza 2y agoIt doesn’t really abort the rest, it just prioritizes the selection of a first catch-path as a current continuation. The rest is still thenable, and there’s no “abort promise” operation in general. There are abort signals, but it’s up to an async process to accept a signal and decide when/whether to check it.
- adregan 2y agoAdmittedly, I was being a bit hand-wavy and describing a bit more of how it feels rather than the way it is (I'm perpetually annoyed that promises can't be cancelled), but I was thinking of the code I've seen many times across many code bases: 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.
- throwaway2037 2y ago> 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.
- medstrom 2y agoI feel there are a few ways to invoke .map() in a readable way and many ways that make the code flow needlessly indirect. 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.
- ndnxncjdj 2y agoI very much prefer the way scoping is handled in arrow functions.
- nosianu 2y agoA simple heuristic I use is to use arrow functions for inline function arguments, and named "function" functions for all others. 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.
- turboponyy 2y agoI like them because it reinforces the idea that functions are just values like any other - having a separate keyword feels like it is inconsistent.
- 0xfffafaCrash 2y agoMoreover the binding and lexical scope aspects supported by classic functions are amongst the worst aspects of the language. 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
- mewpmewp2 2y agoWhy do you want to reinforce that idea? 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.
- nsonha 2y ago> should be important distinction what is a function and what is not 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.
- turboponyy 2y agoNot to be dismissive, but because I like it - it just sits right with me.
- benrutter 2y agoI'm not a javascript programmer, but I really like the arrow pattern from a distance exactly because it enforces that idea. 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.
- crabmusket 2y agoI don't like using them everywhere, but they're very handy for inline anonymous functions. But it really pains me when I see export const foo = () => {} instead of export function foo() {}
- creesch 2y agoThank you, that's something I also never have understood myself. For inline anonymous functions like callbacks they make perfect sense. As long as you don't need `this`. But everywhere else they reduce readability of the code with no tangible benefit I am aware of.
- berkes 2y agoI wish javascript had a built-in or at least (defacto) default linter. Like go-fmt or rust fmt. Or clippy even. 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.
- throwitaway1123 2y agoESLint and Prettier are the de facto default linter/formatter combo in JS. There are rules you can enable to enforce your preferred style of function [1][2]. [1] https://eslint.org/docs/latest/rules/func-style https://eslint.org/docs/latest/rules/func-style [2] https://eslint.org/docs/latest/rules/prefer-arrow-callback https://eslint.org/docs/latest/rules/prefer-arrow-callback
- berkes 2y agoThey are miles away from `gofmt` or `rust fmt` or `cargo clippy` and so on. 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.
- nsonha 2y agowhy the need to pronounce arbitrary preferences, who cares?
- ajuc 2y agoI'm of the opinion that giving a global name to an anonymous function should result in a compilation error.
- supriyo-biswas 2y agoYou can still search for `<keyword> = \(.*\) => `, albeit it's a bit cumbersome.
- post-it 2y agoAll you need is `<keyword> =` Really, all you need is `<keyword>` and if the first result is a call to that function, just jump to its definition.
- spartanatreyu 2y agoExactly. Just search the definition. Any time that a function doesn't have a definition, it's never the target of a search anyway.
- troupo 2y agoAll you need is a tool that actually understands the language. It's 2024 and HN still suggests using regular expressions to search through a code base.
- db48x 2y agoI both agree and disagree with this. 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.
- lukan 2y agoRegex is a universal tool. 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.
- spartanatreyu 2y agoYes JavaScript. 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.
- CGamesPlay 2y agoThe => is after the param spec, so you’re searching for foo.*=> or something more complex, but then still missing multiline signatures. This is very easy to get caught by in TypeScript, and also happens when dealing with higher-order functions (quite common in React).
- spartanatreyu 2y agoWhy are you searching for foo.=> 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.
- pjerem 2y agoYes but that’s an anti pattern. Arrow functions aren’t there to look cool, they’re how you define lambdas / anonymous functions. Other than that, functions should be defined by the keyword.
- bobbylarrybobby 2y agoFunctions and arrow functions have an important difference: arrow functions do not create their own `this`. If you're in a context where a nested function needs to maintain access to the outer function’s `this`, and you don't want to muck with `bind` or `call`, then you need an arrow function.
- wiseowise 2y agoHow is that an anti-pattern? > Other than that, functions should be defined by the keyword. Says who?
- lukan 2y agoAll the wise ones. Well, except for you maybe. Serious arguments would be: - readability - greppability
- lukan 2y ago(It wasn't an insult, but a joke on the username)
- tylerhou 2y agoAs of a few years ago (not sure about now) the backtrace frame info for anonymous functions were far worse than ones defined via the function keyword with a name.
- hansworst 2y agoAnonymous functions don't have names. This makes it much harder to do things like profiling (just try to find that one specific arrow function in your performance profile flame graph) and tracing. Tools like Sentry that automatically log stack traces when errors occur become much less useful if every function is anonymous.
- albedoa 2y agoI want to talk to the developer who considers greppability when deciding whether to use the "function" keyword but requires his definitions to be greppable by distancing them from their call locations. I just have a few questions for him.
- Pxtl 2y agoYou can still look for `(funcname)\s*=` can't you? I mean it's not like functions get re-declared a lot.
- bionsystem 2y agoDoesn't cscope fit this usecase ?
- eddieh 2y agoI used to define functions as `funcname (arglist)` And always call the function as `funcname(args)` So definitions have a space between the name and arg parentheses, while calls do not. Seemed to work well, even in languages with extraneous keywords before definitions since space + paren is shorter than most keywords. Now days I don’t bother since it really isn’t that useful especially with tags or LSP. I still put the return type on a line of its own, not for search/grep, but because it is cleaner and looks nice to me—overly long lines are the ugliest of coding IMO. Well that and excessive nesting.
- semiinfinitely 2y agopython also!
- jsjohnst 2y agoPython is the only one mentioned that “actually works” without endless exceptions to the rule in the normal case. The ones mentioned (Rust/Javascript/Lisp/Go) all have specific syntax that is commonly enough used which makes it harder to search. Possible, absolutely, but still harder.
- zbentley 2y agoI'd say Python works well at greppability because community conventions generally discourage concealing certain kinds of definitions (e.g. function definitions are usually "def whatever"). However, that's just convention. Lots of modules do metaprogramming tricks that obscure greppability, which can be a pain. This is particularly acute when searching for code that is "import-time polymorphic"--that is, code which picks one of several implementations for a piece of functionality at import time at the module scope. That frequently ends up with some hanky-panky a la "exported_function_name = _implementation1 if platform_supported else _implementation2" at the module scope. While sometimes annoying, that type of thing is usually done for understandable reasons (picking an optimized/platform-supported implementation of an interface--think select or selectors in the stdlib, or any pypi implementation of filesystem monitoring using fsnotify/fanotify/kqueue/fsevents/ReadDirectoryChangesW). Additionally, good type annotations help with greppability, though they can't fully mitigate this issue. Much less defensible in Python is code that abuses locals/globals to indirect symbol access, or code that abuses star imports to provide interfaces/implementation switching. Those, fortunately, are rare, but the elephant in the "no greppability ever" room is not: getattr bullshit in OO code is so often utterly obscure, unnecessary and terrible. And it's distressingly common on PyPi. At first I thought this was Ruby's encouragement of method_missing in the bad old days bleeding into the Python community, but the number of programmers for whom getattr magic is catnip seems to be disproportionate to the number of folks with Ruby experience, and, more concerningly, seems to me to be growing over time.
- koito17 2y agoGolang has a similar property as a side-effect of the following design decision. ... the language has been designed to be easy to analyze and can be parsed without a symbol table Taken from https://go.dev/doc/faq https://go.dev/doc/faq The "top-level declarations" in source files are exactly: package, import, const, var, type, func. Nothing else. If you're searching for a function, it's always going to start with "func", even if it's an anonymous function. Searching for methods implemented by a struct similarly only needs one to know the "func" keyword and the name of the struct. Coming from a background of mostly Clojure, Common Lisp, and TypeScript, the "greppability" of Go code is by far the best I have seen. Of course, in any language, Go included, it's always better to rely on static analysis tools (like the IDE or LSP server) to find references, definitions, etc. But when searching code of some open source library, I always resort to ripgrep rather than setting up a development environment, unless I found something that I want to patch (which in case I set up the devlopment environment and rely on LSP instead of grep to discover definitions and references).
- remram 2y agoIn golang you get `func (someName someType) funcname`, so it's much less greppable than languages using `func funcname`
- tuetuopay 2y agoGo is horrible due to the absence of specific "interface implementation" markers. Gets pretty hard to find where or how a type implements an interface.
- kiitos 2y agoInterfaces are consumer contracts, they say "I need this" not "I provide this". Regardless, any reasonable LSP will let you "Find implementations" of any interface definition.
- tuetuopay 2y agoYup, but we're talking about grep, not lsp. And that's precisely why I don't write go without an lsp (the rare times I do write go).
- bryanrasmussen 2y agoJavaScript has multiple ways to define a function so you sort of lose that getting the actual definition benefit. on edit: I see someone discussed that you can grep for both arrow functions and named function at the same time and I suppose you can also construct a query that handles a function constructor as well - but this does not really handle curried functions or similar patterns - I guess at that point one is letting the perfect become the enemy of the good. Most people grepping know the code base and the patterns in use, so they probably only need to grep for one type of function declaration.
- veltas 2y agoFor most functions ^\S.*name( will find declarations and definitions. Most of us use exuberant ctags to allow jumping to definitions.
- skywal_l 2y agoYet you reply to an article that defines functions as variables, which I've seen a lot of developers do usually for no good reason at all. To me, that's a much common and worse practice with regards to greppability than splitting identifiers using string which I haven't seen much in the wild.
- kazinator 2y agoC has "classical" tooling like Cscope and Exuberant Ctags. The stuff works very well, except on the odd weird code that does idiotic things that should not be done with preprocessing. Even for Lisp, you don't want to be grepping, or at least not all the time for basic things. For TXR Lisp, I provide a program that will scan code and build (or add to) your tags file (either a Vim or Emacs compatible one). Given (defstruct point () x y) it will let your editor jump to the definition of point, x and y.
- dan-robertson 2y agoNot sure this is very true for Common Lisp. Classic example are accessor functions where the generic function is created by whichever class is defined first and the method where the class is defined. Other macros will construct new symbols for function names (or take them from the macro arguments).
- f1shy 2y agoStill you can extend the concept without a lot of work, couldn't you?
- db48x 2y agoThat’s true, but I regard it as fairly minor. Accessor functions don't have any logic in them, so in practice you don’t have to grep for them. But it can be confusing for new players, since they don't know ahead of time which ones are accessors and which are not.
- ChoHag 2y ago[dead]
- hgomersall 2y agoThough glob imports in rust can hide a source, so those should be avoided.
- mre 2y agoExactly. I wrote an entire blog post about that: https://corrode.dev/blog/dont-use-preludes-and-globs/ https://corrode.dev/blog/dont-use-preludes-and-globs/
- zarzavat 2y agoC is so much worse than that. Many people declare symbols using macros for various reasons, so you end up with things like DEFINE_FUNCTION(foo) {. In order to get a complete list of symbols you need to preprocess it, this requires knowing what the compiler flags are. Nobody really knows what their compiler flags are because they are hidden between multiple levels of indirection and a variety of build systems.
- skissane 2y ago> C is so much worse than that. Many people declare symbols using macros for various reasons, so you end up with things like DEFINE_FUNCTION(foo) {. That’s not really C; that’s a C-based DSL. The same problem exists with Lisp, except even worse, since its preprocessor is much more powerful, and hence encourages DSL-creation much more than C does. But in fact, it can happen with any language - even if a language lacks any built-in processor or macro facility, you can always build a custom one, or use a general purpose macro processor such as M4. If you are creating a DSL, you need to create custom tooling to go along with it - ideal scenario, your tools are so customisable that supporting a DSL is more about configuration than coding something from scratch.
- kragen 2y agothe issue is that the c preprocessor is always available and usually used
- skissane 2y agoOther languages have preprocessors or macro facilities too. C's is very weak. Languages with more powerful preprocessors/macros than C's include many Lisp dialects, Rust, and PL/I. If you think everyone using a weak preprocessor is bad, wait until you see what people will do when you give them a powerful one. Microfocus COBOL has an API for writing custom COBOL preprocessors in COBOL (the Integrated Preprocessor Interface). (Or some other language, if you insist.) I bet there are some bizarre abominations hidden in the bowels of various enterprises based on that ("our business doesn't just run on COBOL, it runs on our own custom dialect of COBOL!")
- akritid 2y agoLooks fine (subjective) and there is also ctags
- sva_ 2y agoPeople don't use LSP?
- gregjor 2y agoThat’s right, not everyone uses an LSP. Nothing wrong with LSPs, very useful tools. I use ripgrep, or plain grep if I have to, far more often than an LSP. Working with legacy code — the scenario the author describes — I often can’t install anything on the server.
- giancarlostoro 2y agoFun fact: VS Code uses ripgrep by default. https://github.com/microsoft/vscode-ripgrep https://github.com/microsoft/vscode-ripgrep
- menaerus 2y agoLSP doesn't always work without issues with large C and C++ codebases which is why one needs to fallback to grep techniques.
- gregjor 2y agoctags.
- mav3ri3k 2y agoAlthough in rust, function like macros make it super hard to trace code. I like them when I am writing the code and hate then when I have to read others macros.
- throwawayffffas 2y ago> Meanwhile C lacks any such keyword It's a hassle. But not the end of the world. I usually search for "doTheThing\(.+?\) \{" first. If I don't get a hit, or too many hits I move to "doTheThing\([^\)]*?\) \{" and so on.
- jampekka 2y agoRust though does lose some of those points by more or less forcing[1] snake_case. It's really annoying to navigate bindings which are converted from camelCase. I don't care which case is used. It's a trivial superficial thing, and tribal zealotry about such doesn't reflect well on the language and community. [1] The warnings can be turned off, but in some cases it requires ugly hacks, and the community seems to be actively hostile to making it easier)
- kibwen 2y agoThe Rust community is no more zealous about naming conventions than any other language which has naming conventions. Perhaps you're arguing against the concept of naming conventions in general, but that's not a Rust thing, every language of the past 20 years suggests naming conventions if for no other reason than every language provides a standard library which needs to follow some sort of naming conventions itself. Turning off the warnings emitted by the Rust compiler takes two lines of code, either at the root of the crate or in the crate manifest.
- jampekka 2y agoI've yet to encounter another compiler that warns about naming conventions, by default at least. So at least it's most enforced zealotry I've encountered. Yes, it can be turned off. But for e.g. bindgen generated code it was not trivial to find out.
- kibwen 2y agoThe Rust compiler doesn't produce warnings out of zealotry, but rather as a consequence of pre-1.0 historical decisions. Note that Rust doesn't use any syntax in pattern matching contexts to distinguish between bindings and enum variants. In pre-1.0 versions of Rust, this created footguns where an author might think they were matching on an enum, but the compiler was actually parsing it as a catch-all binding that would cause any following match arms to never be executed. This was exacerbated by the prevailing naming conventions of the time (which you can see in this 2012 blog post: https://pcwalton.github.io/_posts/2012-06-03-maximally-minimal-classes-for-rust.html https://pcwalton.github.io/_posts/2012-06-03-maximally-minim... (note the lower-cased enum variants)). So at some point the naming conventions were changed in an attempt to prevent this footgun, and the lint was implemented to nudge people over to the new conventions. However, as time went on the footgun was otherwise fixed by instead causing the compiler to prioritize parsing enum variants rather than bindings, in conjunction with other errors and warnings about non-exhaustive patterns and dead code (which are all desirable in their own right). At this point it's mostly just vestigial, and I highly doubt that anybody really cares about it beyond "our users are accustomed to this warning-by-default, so they might be surprised if we stopped doing this".
- wruza 2y agoMeanwhile C lacks any such keyword, so the best you can do is search for the name. That gets you a sea of callers with the declarations and definitions mixed in That’s why in my personal projects I follow classic “type\nname” and grep with “^name\>”. looks ugly Single line definitions with long, irregular type names and unaligned function names look ugly. Col 1 names are not only greppable but skimmable. I can speedscroll through code and still see where I am.
- akira2501 2y ago> so the best you can do is search for the name This is why in C projects libs go in "lib/" and sources go in "src/". If your header files have the same directory structure as libs, then "include/" is a also a decent way to find definitions.
- suprjami 2y ago> Meanwhile C lacks any such keyword, so the best you can do is... ...use source code tagging or LSP.
- marcosdumay 2y agoThose also make your language easier to parse, and to read. Many people insist that IDEs make the entire point moot, but that's the kind of thing that make IDEs easier to write and debug, so I disagree.
- johannes1234321 2y agoOne thing which works for C is to search something like `[a-z] foo\(.+\) \{` assuming that spacing matches the coding style, often the shorter form `[a-z] foo\(` works well, which tries to ensure there is a type definition and bin assignment or something before name. Then there is only a handful false positives.
- andersa 2y agoDo people really use text search for this rather than an IDE that parses all of the code and knows exactly where each declaration is, able to instantly jump to them from a key press on any usage...? Wild.
- iamwil 2y agoYes. Not everyone uses or likes an IDE. Also, when you lean on an IDE for navigation, there is a tendency to write more complicated code, since it feels easy to navigate, you don't feel the pain.
- fsckboy 2y agoC, starting with K&R, has all declarations and definitions on lines at the left margin, and little else. this is easy to grep for.
- wpollock 2y agoIn the bygone days of ctags, C function definitions included a space before opening parenthesis, while function calls never had that space. I have a hard time remembering that modern coding styles never have that space and my IDE complains about it. (AFAIK, the modern gtags doesn't rely on that space to determine definitions.) Even without *tags, the convention made it easy to grep for definitions.
- mzs 2y agospace after builtin was recommended instead: if (x == 0) { ... sizeof (buf); return (-1); exit(0);
- drewg123 2y agoIn terms of C, that's one reason I prefer the BSD coding style: int foo(void) { } vs the Linux coding style: int foo(void) { } The BSD style allows me to find function definitions using git grep ^foo.
- leogout 2y agoJavascript is a bit trickier i think nowadays with the fat arrow notation : const myFunc = () => console. log("can't find me :p");
- darepublic 2y agoThere is arrow syntax with js