12 ms·
Loopless Code (2006)
- johan_felisaz 5y agoGenuine question, what would be the idiomatic way of doing an impure operation multiple times in J/APL ? (e.g. if writing an interpreter loop) I was wondering if it's doable without the while. and other constructs (which honestly feel like plugged artificially in J, even syntax wise)
- mlochbaum 5y agoThe idiomatic way would be to exit the array paradigm and use an imperative (while.) or functional (recursion) method. APL and J both have "repeat until convergence" functionality, which stops when the same result is returned twice in a row, but to use this you'd have to artificially create a result that changes each time. When designing BQN I embraced the limited nature of array primitives, so that most primitives can only implement efficiently parallelizable programs and none of them can perform infinite loops. Flip this around and you get guarantees: if you create a function by composing primitives you know it will halt, and if you avoid using modifiers in complicated ways it's easy to prove good sequential and parallel bounds on the runtime.[0] Although BQN has no tail recursion (J also doesn't; Dyalog does), it's possible to implement loop functionality that uses only logarithmic stack space in the number of iterations, with low overhead (I just measured 30ns/iteration in CBQN for a simple incrementing loop).[1] [0] https://mlochbaum.github.io/BQN/doc/primitive.html https://mlochbaum.github.io/BQN/doc/primitive.html [1] https://mlochbaum.github.io/BQN/doc/control.html#low-stack-version https://mlochbaum.github.io/BQN/doc/control.html#low-stack-v...
- RodgerTheGreat 5y agoin K, there is an adverb form which is essentially equivalent to a while loop: apply a function or composition to a value repeatedly as long as a second function or composition of that value yields true. It's basically an "escape hatch" when an algorithm cannot be cast into any more specific pattern, like iteration, a fixed-point, a reduction, etc. In practice it is needed very rarely. There's a complete list of adverb forms for k6 here: https://github.com/JohnEarnest/ok/blob/gh-pages/docs/Manual.md#adverb-reference https://github.com/JohnEarnest/ok/blob/gh-pages/docs/Manual....
- the_optimist 5y agoThere's ^: DoWhile https://wiki.jsoftware.com/wiki/Vocabulary/hatco#DoWhile https://wiki.jsoftware.com/wiki/Vocabulary/hatco#DoWhile
- sharmin123 5y agoGuide on The Mental Health Effects Of Extramarital Affairs: https://www.hackerslist.co/guide-on-the-mental-health-effects-of-extramarital-affairs/ https://www.hackerslist.co/guide-on-the-mental-health-effect...
- mlajtos 5y agoI will always upvote APL/J.
- userbinator 5y agoThe loops are still there, they're just implicit. That's how APL-family languages can be parallelised easily. It's notable that the first J interpreter, while written in C, has a similar style --- it defines a macro to run a loop "implicitly", and then uses that throughout: https://code.jsoftware.com/wiki/Essays/Incunabulum https://code.jsoftware.com/wiki/Essays/Incunabulum
- air7 5y ago> The loops are still there, they're just implicit. Obviously. I think the point is that loops should be considered "implemention details" at the compiler level, and us higher beings should be able to say what we want without troubling ourselves with them.
- dkersten 5y agoThe article does have a heading "Examples of Implicit Loops", so the author acknowledges that the loops are there, but implicit. I guess "implicit-only-loops code" or "explicit-loop-less code" or whatever isn't as catchy a title, but I don't think anyone reading it expects it to be literally loopless code.
- xelxebar 5y agoThe current interpreter even has this! In fact, the meat of many (most?) functions is implemented using the (functions underlying the) primitives. It's kind of fascinating to skim through, if you're already familiar with array programming. The code goes way out of its way to reduce the impedance mismatch between J and C semantics.
- matthewaveryusa 5y agoSean Parent has a good talk about no loops in C++. His primary argument is that typically a loop is an algorithm you’re applying (map, filter, reduce…) and the raw loop masks the algorithm. https://m.youtube.com/watch?v=qH6sSOr-yk8 https://m.youtube.com/watch?v=qH6sSOr-yk8
- magicalhippo 5y agoWhile I agree that a lot of loops could be better implemented as a map, filter or similar construct, there's still many loops where writing it as a loop makes it more clear what's going on. For example, in our system we have orders. Each order has some order items as well as one or more invoice. Due to reasons, some customers want to consolidate orders before processing them in our system. In that case all the items should simply be copied to the consolidated order, however for invoices we should accumulate values for the same invoice number and currency combo. In addition, order items references the invoice they belong to, so we need to keep track of the new invoice id's so we can remap that reference. Doing all this in a few nested loops makes the overall process very clear I think, each step in the loop being clear and logical. In that case, the loops highlight the algorithm I think. I'm not sure how to implement the consolidation only in terms of map, filter and friends in a way which would be more clear.
- valenterry 5y ago> there's still many loops where writing it as a loop makes it more clear what's going on I think that really depends on your language and what you are used to. For me, I would disagree. > I'm not sure how to implement the consolidation only in terms of map, filter and friends in a way which would be more clear. Tbh, when I read this, I'm thinking: is the data-structure wrong? Maybe you should have something like a "bundle" that contains orderItems and their invoice. It doesn't seem to make so much sense to keep an invoice ID in each orderItem. That makes sense in a relational db, but not when using objects. I would model it like that (for lack of a better name than "bundle"): Order(bundles: Map[InvoiceId, Bundle(invoice: Invoice, orderItems: List[OrderItem])) Now orderItems and the invoice-amount are logically bundled together and identified by the invoice-id. Then, combining two orders becomes really simple: combinedOrder = order1 + order2 No loops needed, simply treating the data as monoids. Here's an executable example: https://scastie.scala-lang.org/DDIRbiC5TTuOxmkzEFxTqA https://scastie.scala-lang.org/DDIRbiC5TTuOxmkzEFxTqA You might argue that it is not clear how that works and you are right for everyone who is not used to how monoids work. But everyone who is, independent of the language, will understand exactly what happens. On the other hand, someone who is not used to loops would probably argue that your loops are hard to understand. It really boils down to what techniques one is familiar with.
- bennybob 5y agoMap reduce and filter often are much more readable, but then sometimes a loop is clearer. I often see this when I use resharper's (a c# tool) auto refactor a loop into a linq statement , it can become unreadable.
- Cthulhu_ 5y agoPlus they may have hidden performance costs - or benefits. They may have function invocation and memory layout costs, but they may also be parallelised and optimized transparently. A regular for-loop is super efficient in terms of memory layout / access but it's by definition singlethreaded.
- gnufx 5y agoYes, as always, it depends. The typical problem with array operations, e.g. in Fortran, is how well they're "scalarized", and the possible cost of not doing copy elimination over subroutine calls, for instance. Presumably the exact semantics of a for-type loop depends on the language, but surely the C standard doesn't prevent auto-parallelization (including with SIMD vectorization, modulo arithmetic rules). Loops might also have OpenMP or other annotation.
- mlochbaum 5y agoReadability depends on both the underlying algorithm and its expression in code. Because LINQ has to work in existing languages, it can't express loopless algorithms as well as languages like J that have syntax designed to fit the style. You're probably also putting it at a disadvantage with the automatic translation, as you'll write different and cleaner array code if you approach the problem with array operations in mind. The author is claiming (and I and many more practical-minded programmers agree) that in J, explicit loops are rarely needed, and usually not even helpful. As the J notation is designed for loopless programming, it has the same kind of bias, against loops. But knowing how J approaches things can be valuable. Most likely, many problems you think are unapproachable with map and reduce can be solved easily by knowing the right techniques.
- sys_64738 5y agoWhen I saw the title the only method for repetitive code I could think of to replace loops was recursion.
- nmz 5y agoI think this is what ATS does, it does not have loops, only recursion.
- ThePhysicist 5y agoBPF didn't have loops in the beginning either, but since Linux 5.3 it supports bounded loops as that seems to make programming a lot easier in many cases.
- MaxBarraclough 5y agoFrom the title I was sure this was going to be about the branch forward only pattern used in the Gripen, that John Carmack has briefly written about. In that pattern, there's a top-level loop, but other than that, no looping constructs or other backward-branches are permitted anywhere in the codebase. * http://lambda-the-ultimate.org/node/5362 http://lambda-the-ultimate.org/node/5362 * https://news.ycombinator.com/item?id=22192656 https://news.ycombinator.com/item?id=22192656
- jhgb 5y agoIt seems to me that this would be best ensured by writing the codebase in a custom language the compiler of which would put a loop around the whole code but the language itself wouldn't have loops. This could be yet another instance of "patterns mean 'I've run out of language'".
- cloogshicer 5y agoThis sounds very interesting. What exactly would be a backward-branch? Are function calls allowed (I feel like they have to be)? Are there any code snippets for this style?
- kwhitefoot 5y agoAccording to Carmack subroutine calls are also disallowed. See https://web.archive.org/web/20210226152857/http://lambda-the-ultimate.org/node/5362 https://web.archive.org/web/20210226152857/http://lambda-the... This sounds very much like programming a traditional Programmable Logic Controller (PLC).
- aaaaaaaaaaab 5y agoWell, a flight control computer is not too different from a PLC.
- MaxBarraclough 5y agoSeems curious to ban functions. If you ban function-pointers then you can statically ensure there's no recursion (including mutual recursion). At that point, all function calls are in principle able to be inlined, so any backtracking is merely a compiler-level implementation detail. iirc, OpenCL C does something similar, banning function pointers and recursion (including mutual recursion), although it does so for different reasons than this pattern.
- dahart 5y ago> If the rank of the verb's operand is smaller than the rank of the verb, the verb is applied to the entire operand and it is up to the author of the verb to ensure that it produces a meaningful result in that case. This instantly brings back all my frustration with getting broadcasting in numpy to work like I want / expect. I love getting the right dot products to work between an array of matrices and an array of vectors, but I don’t do it often enough to remember how, I have to slowly re-derive the incantation every damn time. > J does contain while. and for. constructs, but they carry a performance penalty Question - what is the state of the art of functional programming for performance? My experience in JavaScript, Python, and C++ is that using loop-hiding pure functional constructs is difficult to impossible to optimize, often much slower than explicit loops, and worse that it’s harder to refactor when you realize your nested loops are inside-out from what they need to be. I want to use functional more often, but I feel like I hit roadblocks in practice.
- rak1507 5y agoI find rank in APL/J to be much easier to understand (and much more powerful) than broadcasting in numpy.
- maest 5y agoPart of the reason for that is because APL-family languages support this feature as a first class citizen and are designed with them in mind. Numpy has to, for better or worse, work within the confines of the Python grammar.
- fifilura 5y agoIsn't all this popularized in SQL? And (at least for me) written with a much clearer syntax.
- avmich 5y agoI recently was wondering how to write a program, in SQL, which generates consecutive integers - 0, 1, 2, 3... - up to an arbitrary input value. Only standard SQL is allowed, and familiar constructs are preferred - the code should be understandable to the maintainer. In J it's i. <n>, like this - i. 5 produces 0 1 2 3 4 .
- 5e92cb50239222b 5y agoWITH RECURSIVE num AS ( SELECT 1 AS id UNION ALL SELECT id + 1 FROM num ) SELECT * FROM num LIMIT 5; SQL:1999 IIRC.
- lodi 5y agoAdmittedly though, recursive CTE's like that are a bit of a minefield in practice. It's easy to confuse the query optimizer after chaining a few of those together, or to outright hit a recursion limit (32,767 in SQL Server).
- fifilura 5y agoSQL is not turing complete, at least not without recursions. But you will still get very far when it comes to wrangling data. So far that you may not even need another tool for that purpose. What do you want to use that list for?
- moeris 5y agoI think their point was that J and SQL target different domains, and each will be stronger in its respective domain. If I want to join two tables, filtering on some value, sorting, and viewing the first ten results, SQL will likely be the cleaner syntax. If I want to apply a polynomial function to a list of values, and calculate the standard deviation of the result, J will be much cleaner. There's some similarity, at a high level, with how they work. But it doesn't really make sense to say one is, overall, better than the other, because they don't solve the same problem. It's like saying a hammer is better than a screwdriver.
- enriquto 5y agoWhy so much hate for loops? Loops are just a nice notation for some computational constructs. Sometimes they are the clearer way to write an algorithm. Very often an algorithm becomes clearer when written in explicit loop form than in "vectorial" notation. Loops do not need to be artificially slow to discourage them. Any modern programming language should be able to recognize the construct and compile it in the most efficient way. Saying that you must avoid loops because they are slow is a failure in a particular programming language, not in the concept of loop. After all, all languages managed to implement loops efficiently many decades ago.
- GerbilWithALisp 5y agoI don't use loops because they force you to use side effects in your code.
- mekkkkkk 5y agoI think the idea is that a loop in many cases doesn't express your intent. What you might want to do is to "set the property 'foo' to 'bar' on all items in an array". What you might write is "create a variable and set it to zero, then iterate that variable as long as it's smaller than the number of items in the array, and do this between each iteration: take the item at the variables index and set its property 'foo' to 'bar'". Of course any programmer will instantly see what's going on, but that's not from clarity but from prior experience and familiarity.
- rand_r 5y agoThis seems perfectly clear. for item in array: item.foo = bar
- maest 5y agoarray.'foo = bar Is clearer. Or, at least, would be, if one were accustomed to the (fictitious) adverb '. There's no need for all the ceremony around specifying "for x in y: <some function describing what to do with x>"
- odipar 5y agoNo Stinking Loops! http://www.nsl.com http://www.nsl.com
- oh_my_goodness 5y agoHN's top-voted comment at this moment begins: "Why so much hate for loops?" The item cited as 'hate for loops' is an introduction to 'loopless' programming in J, a universally admired language developed by Iverson. Whether LINQ or SQL etc. are also examples of 'hate for loops' or not ... I'm done. For every web site there's a moment when user comments need to be declared 'not worth the pain of sifting through.' HN has crossed that threshold for me. [Edit: My point is that we are crossing a sort of event horizon. Short-sighted micro-kvetching is becoming the largest single focus of user comments, upvotes, etc. on this site. Perennial examples: one-indexing vs zero-indexing; OO 'vs' FP; whitespace. You can list more yourself.]
- jedimastert 5y ago> For every web site there's a moment when user comments need to be declared 'not worth reading.' HN has crossed that threshold for me. So what you're doing is the exact same thing, but for HM ITSELF? /j I don't think think the comment was unwarranted in this case, because the article makes no attempt to explain why what it says is a it admits is a building block of traditional programming is a bad idea. It just says "j does loops bad, do this instead"
- throwawaygh 5y ago> HN's top-voted comment... ...spawned a really interesting conversation on the PL design-tradeoffs of various special-purpose iteration/recursion schemes. > J, a universally admired language developed by Iverson. Even in hard-core PL communities that seems like a reach. Lots of PL courses exclude APL/J, which is a pretty strong empirical proof-point against universal (or even strong majority) admiration. Outside of PL enthusiast communities, array PLs sit somewhere between "niche" and "esoteric". I think there is a lot to admire in APL, J, et al. They clearly had far-reaching influence. But there's also a reason that the dominant reaction is closer to "um... heh" than admiration. There's no need to over-state the case here.
- deleted 5y ago[deleted]
- 5y ago
- brundolf 5y agoI've found this tends to happen in my Rust code too, and even in my JavaScript lately (though sadly it has performance costs in JS). It's true that 90% of loops in practice are just for processing collections, and that can be better served in most languages by using a harder-to-mess-up construct that's designed for the purpose. It really does almost feel like an extension of the goto trajectory, since loops themselves were one of the purpose-built constructs designed to cover specific, common uses of goto. I wonder if we'll see a "control-flow considered harmful" one day (this is a joke... mostly)
- awinter-py 5y ago'and then he discovered loops' classic early apple interviewing story https://www.folklore.org/StoryView.py?project=Macintosh&story=Discovered_Loops.txt https://www.folklore.org/StoryView.py?project=Macintosh&stor...
- dang 5y agoA past related thread: Loopless Programming - https://news.ycombinator.com/item?id=21278790 https://news.ycombinator.com/item?id=21278790 - Oct 2019 (122 comments)
- orcasushi 5y agoI usually avoid loops. Guess I got sorta hooked by this new age functional school. But recently realized even layman brains on drugs can understand some loop and goto statements: "Eat Sleep rave Repeat" Sometimes they are just the best to write logic.