[HN Gopher] Loopless Code (2006)
       ___________________________________________________________________
        
       Loopless Code (2006)
        
       Author : xept
       Score  : 117 points
       Date   : 2021-11-25 06:40 UTC (1 days ago)
        
 (HTM) web link (www.jsoftware.com)
 (TXT) w3m dump (www.jsoftware.com)
        
       | dang wrote:
       | A past related thread:
       | 
       |  _Loopless Programming_ -
       | https://news.ycombinator.com/item?id=21278790 - Oct 2019 (122
       | comments)
        
       | userbinator wrote:
       | The 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
        
         | dkersten wrote:
         | The 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.
        
         | air7 wrote:
         | > 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.
        
       | brundolf wrote:
       | I'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)
        
       | bennybob wrote:
       | Map 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.
        
         | mlochbaum wrote:
         | Readability 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.
        
         | Cthulhu_ wrote:
         | Plus 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.
        
       | dahart wrote:
       | > 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 wrote:
         | I find rank in APL/J to be much easier to understand (and much
         | more powerful) than broadcasting in numpy.
        
           | maest wrote:
           | Part 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 wrote:
       | Isn't all this popularized in SQL? And (at least for me) written
       | with a much clearer syntax.
        
         | avmich wrote:
         | I 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 wrote:
           | WITH RECURSIVE num AS (           SELECT 1 AS id
           | UNION ALL           SELECT id + 1 FROM num       )
           | SELECT * FROM num LIMIT 5;
           | 
           | SQL:1999 IIRC.
        
             | lodi wrote:
             | Admittedly 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 wrote:
           | SQL 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 wrote:
             | I 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 wrote:
       | Why 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 wrote:
         | I don't use loops because they force you to use side effects in
         | your code.
        
         | gnufx wrote:
         | Not hate for loops, but if you replace them with, say, array
         | operations, you've removed the need to prove your loops correct
         | -- or removed a possible source of error from getting them
         | wrong. I guess what's clearer is a matter of opinion.
         | 
         | Reconstructing what was meant at high level from arbitrary
         | loops is surely intractable, but could only be a property of a
         | compiler, not a language. Should a compiler like GCC
         | necessarily recognize a matmul loop nest and replace it with a
         | GEMM call? XL always does -- which can make investigating it's
         | optimization annoying -- but then it doesn't actually recognize
         | GEMM.
        
         | mekkkkkk wrote:
         | I 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 wrote:
           | This seems perfectly clear.                 for item in
           | array:           item.foo = bar
        
             | maest wrote:
             | array.'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>"
        
               | rand_r wrote:
               | That does look pretty nice for this example. I think the
               | problem with it is that introduces too much new syntax
               | that you have to memorize. The beauty of a for loop is
               | that you can do _anything_ with it. And it all works with
               | the same syntax.                 for item in array:
               | item.foo = bar            for item in array:
               | item.foo = item.foo * 2            for item in array:
               | my_bar_func(item.foo)
               | 
               | If you special case certain uses of a for loop and add
               | language syntax around it, of course you can make code
               | terser. But it's nice to rely on a small set of very
               | generally useful syntax.
        
               | pasquinelli wrote:
               | depends on how many different types of loops there really
               | are. now _that_ would make an interesting article.
        
               | dragonwriter wrote:
               | > The beauty of a for loop is that you can do _anything_
               | with it.
               | 
               | That's also the curse: a generic loop can be almost
               | anything (looping over an iterable can't quite express
               | _anything_ , but it can do a lot), so seeing it tells you
               | nothing.
               | 
               | C-style loops can do more (literally anything), and loop-
               | over-iterable syntax has been generally preferred where
               | it can be applied because it _tells you more_ (even if
               | only a little more) about what the block of code is doing
               | up-front.
               | 
               | Constructs that are more specific than loop-over-iterable
               | can be correspondingly more expressive.
        
               | [deleted]
        
               | jcelerier wrote:
               | It isn't. "For x in y" is literally what we say when
               | speaking as humans unlike that quote thing.
        
               | maest wrote:
               | I don't find that to be a convincing argument, because it
               | suggests we should write "foo applied to bar" instead of
               | "foo(bar)" in your code. It's just notation, just because
               | it's new, doesn't mean it's bad.
               | 
               | There's maybe an argument to be made about there being
               | _too much_ notation to learn, but notation makes sense
               | for fundamental concepts (which loops clearly are).
               | 
               | Notation is important. It condenses thought and refines
               | the concepts, making them more visible. In fact, some
               | valid mathematical theory came about from figuring out
               | proper notation and then playing around with it,
               | sometimes in non-rigorous (albeit intuitive ways). e.g.
               | consider how physicists wildly move dx and dy around in
               | differential equations (much to mathematicians' dismay)
               | 
               | Ancient greek geometry textbooks were written in long
               | form: "the square of the hypotenuse side is equal to the
               | sum of squares of the other two sides". We can all agree
               | that the "a^2+b^=c^2" notation is better.
        
               | [deleted]
        
               | mekkkkkk wrote:
               | That's an awkward way to speak.
               | 
               | "Take each of my gizmos, then put it in the gizmo box"
               | for gizmo in gizmos: gizmo.put_in_box()
               | 
               | vs
               | 
               | "Put all my gizmos in the gizmo box"
               | gizmos.'put_in_box()
        
               | hinkley wrote:
               | Or "paint all my gizmos red".
        
               | jbluepolarbear wrote:
               | The second code doesn't put all the gizmos in the gizmo
               | box. That's your intent, but not the reality. All that
               | syntax says is apply function to all elements. It is
               | syntactic sugar for the code you put above; it's a for
               | each loop.
        
               | mekkkkkk wrote:
               | Well, the argument is about expressing intent, so I guess
               | we agree.
        
               | RHSeeger wrote:
               | Your first statement is unambiguous.
               | 
               | Your second sentence is ambiguous. It could mean
               | 
               | 1. Put all of my gizmos in a single gizmo box, together,
               | OR
               | 
               | 2. Put each of my gizmos in its own gizmo box
        
               | mekkkkkk wrote:
               | Are you talking about the pseudo code or the readable
               | sentences? I hope my point comes across even if there's
               | some linguistic nuance I'm missing.
        
               | hinkley wrote:
               | I think you will find that those "humans" you've been
               | hanging out with are all programmers or mathematicians.
               | "Humans" don't talk like this. Most of them have never
               | heard of set theory.
        
               | jcelerier wrote:
               | Maybe it's just my mother tongue, but "pour chaque entree
               | dans la base de donnees, prends la moyenne et affiche-la"
               | sounds pretty ok (literally in English, for each entry in
               | the database, take the average and print it)
        
               | photochemsyn wrote:
               | Not necessarily. The later expression might be more
               | common though:
               | 
               | "For every item in this truck, remove that item and put
               | in in the warehouse."
               | 
               | "While this truck is not empty, keep unloading items to
               | the warehouse."
        
               | nickjj wrote:
               | > "For x in y" is literally what we say when speaking as
               | humans unlike that quote thing.
               | 
               | What about:
               | 
               | "Can you unload the truck?"
               | 
               | The current context gives us implied knowledge that we're
               | talking about the items in the truck and where it's
               | getting unloaded to, such as a warehouse if we're near a
               | warehouse door. We only expand on that verbally when we
               | need to such as "Can you throw out the empty boxes in the
               | truck?".
               | 
               | I think this is why programming is nonintuitive, you have
               | to include important steps with very precise words that
               | often go unsaid. Even "throw out" wouldn't convert to the
               | correct intent if a computer executed that statement as
               | is. You would end up literally throwing the empty boxes
               | out of the truck instead of putting them into a dumpster
               | or garbage can (the likely intent based on this example).
        
             | pletnes wrote:
             | With numpy, this would just be item.foo [:] = bar. Much
             | more readable to me - the whole <<for>> thing is
             | superfluous.
        
               | jbluepolarbear wrote:
               | These comments from people who don't write software and
               | only use programming for data science. Numpy is
               | superfluous for anyone that doesn't need it.
        
               | maest wrote:
               | Neat trick. Didn't know about it.
               | 
               | In pandas it would be item.foo = bar, _as long as_ bar
               | isn't something you can iterate over.
        
               | pletnes wrote:
               | Numpy is all about this. E.g y = np.sin(x). X and y are
               | arrays and you can think of them as <<all values of x and
               | y>> or <<the physical variables x and y>>
        
               | kergonath wrote:
               | Indeed. Also, this notation was lifted straight from
               | Fortran 90 (which itself got it from Matlab, Algol, and
               | the primitive slicing that was possible in older
               | FORTRANs). The idea that a concise notation is helpful
               | for the concept of "put a thing in all the buckets of an
               | array" really is quite old. It does make a lot of sense.
        
           | FalconSensei wrote:
           | In python, the way you write this is more similar to how we
           | express the intent, using dict/list comprehension. But the
           | thing is, for anything more complex that your example, the
           | comprehension notation is harder to read and make changes if
           | you are not really used to it
        
             | BeFlatXIII wrote:
             | Pythonic for loops are more or less `for item in list:
             | <block of code>`. Sill no manual management of array
             | indices.
        
           | dagmx wrote:
           | That's only true for languages that don't support looping
           | over an iterable directly. Or if you need the index while
           | iterating too.
           | 
           | Python, Rust, Swift, C++, ObjC and more all support the
           | equivalent of a foreach loop today. That perfectly shows
           | intent
        
             | dragonwriter wrote:
             | > That's only true for languages that don't support looping
             | over an iterable directly.
             | 
             | Even with loop-over-iterable semantics, explicit loops
             | obscure intent compared to comprehensions and
             | map/reduce/filter/etc.; they are better than C-style loops
             | which in turn are better than conditionals + gotos, but
             | that doesn't make them _good_ at expressing intent in many
             | common cases.
        
             | jakear wrote:
             | Agreed, and based on the list here:
             | https://en.m.wikipedia.org/wiki/Foreach_loop the only
             | popular language without for each is C.
        
               | jacoblambda wrote:
               | And even then, a decent number of data structures
               | provided in headers on *nixes provide for each macros.
        
             | tomtomtom777 wrote:
             | True. It is possible though for a language to avoid these
             | iteration constructs as if they are needless plumbing as
             | well. Consider the elegant type system of the XQuery
             | language:
             | 
             | In XQuery, the root type is a flattened sequence. Flattened
             | means not just that [1, [2, 3]] is flattened to [1, 2, 3],
             | but also the a single object 42 is equivalent to a sequence
             | of one object [42].
             | 
             | This means that if I write:                 declare
             | function local:prefixName($name)       {
             | fn:concat("prefix-", $name)       }
             | 
             | I can invoke it with a single name or a sequence of names
             | without any explicit iteration.
             | 
             | It has drawbacks but using XQuery certainly highlights how
             | much code we normally waste dealing with "things" and
             | "sequences of things" separately.
             | 
             | (Than again working with XQuery also highlights how much
             | code one can waste dealing with XML namespaces :)
        
             | mekkkkkk wrote:
             | Admittedly it's a worst case scenario, but the broader
             | point still holds even with a foreach. You are still
             | picking out each item individually instead of operating on
             | the collection which is what you intend to do. You write
             | "do this for every apple: peel it", instead of "peel the
             | apples".
        
               | tshaddox wrote:
               | apples.forEach(peel) is pretty clear, and the distinction
               | is actually important for some operations which could
               | conceivably be applied either to the whole collection or
               | to each item in the collection and thus could be
               | ambiguous in plain English, like "gift wrap the apples."
               | That could mean giftWrap(apples) or
               | apples.forEach(giftWrap).
        
               | mekkkkkk wrote:
               | I love me some forEach methods! It's still one off from
               | completely avoiding the loop-think though. A syntax like
               | "apples.'peel()" (where peel is a method on the
               | individual apples) is what I imagine.
               | 
               | As a PS, I think the parent comment was referring to a
               | (corresponding) "for apple in apples: peel(apple)" syntax
               | though. The main point was that you don't have to bother
               | with the manual index iteration as in my worst case
               | example.
        
               | Kinrany wrote:
               | Here's another comparison: peel and add to mixer fresh
               | apples only.                   let mut mixer =
               | Mixer::empty();         apples.for_each(|apple| {
               | if apple.is_fresh() {
               | mixer.add(apple.peel());             }         });
               | let mixer = apples             .filter(Apple::is_fresh)
               | .map(Apple::peel)             .fold(Mixer::empty(),
               | Mixer::add);
        
               | conradludgate wrote:
               | They're both pretty clear but I'd prefer the second one
               | (if you used collect instead of fold)
        
               | Kinrany wrote:
               | I chose Rust syntax because it allows both chaining and
               | using methods as functions, but avoided Rust-specific
               | features. In addition to `collect` this example is
               | missing `.into_iter()` that both examples would need: I
               | interpreted `apples` as an iterator instead. Otherwise
               | the first example would be clearer with a for loop.
        
               | IggleSniggle wrote:
               | Well said! And when this clarity is lacking in a
               | codebase, damn do I find it incredibly frustrating! Like,
               | can't you just tell me whether you are trying to operate
               | on the items of the collection or the collection as a
               | whole??? Why did you need to adopt this ambiguous
               | formation??
        
           | convolvatron wrote:
           | there is also a pretty big difference in what a compiler can
           | do if we express the computation as a set of operations that
           | transform the sets rather than a little rats nest of
           | mutations and jumps.
           | 
           | if you wanted to vectorize the set/jmp version, you'd be
           | trying to tease apart a problem which is undecidable in the
           | limit. vectorizing the transformer version is trivial.
        
             | kristjansson wrote:
             | There's also the reverse: writing as a loopless operation
             | forces the programmer to write code that can be easily
             | vectorized.
        
         | mirekrusin wrote:
         | Loops are great if your programming languages supports
         | iterables/iterators/generators (also async generators) like in
         | js/ts for example.
         | 
         | Especially generator-to-generator combinators ie. [0] gives
         | terse, transducer expressiveness over computation on
         | finite/infinite streams, arrays, etc. (all iterables). It's
         | easy to compose, jump into/out-of for-loops if needed for
         | arbitrary yielding (ie. conditionally yielding multiple items,
         | skipping some, halting etc); `continue`, `break`, nesting,
         | yield, yield from (yield*), normal code in for-loops is very
         | intuitive and terse, creating pleasant, understandable code.
         | 
         | Recently I had a chance to break down complex pipeline in
         | finance project into small steps as generator-to-generator
         | transducer functions (can you call generator-to-generator
         | functor a trasducer?), higher order for steps that require
         | extra inputs, leaving all pipeline pure and easy to reason
         | about. Typescript was really helpful doing hard work where each
         | step somehow modifies the input - leaving final result
         | typechecked together with spotting bugs in intermediate steps
         | ie. on their dependendency etc. really nice stuff.
         | 
         | [0] https://github.com/preludejs/generator
        
         | pfortuny wrote:
         | Oh but in J loops exist, they are mostly implicit in the
         | operation.
         | 
         | So it is not "loopless" per se, as the sigma notation for
         | summation indicates a loop in a single construct, and it "can
         | be written away" using Einstein's notation.
         | 
         | That is how it should be understood, imo.
        
         | maest wrote:
         | > Why so much hate for loops?
         | 
         | I don't think there's any _hate_ for loops, however, they are a
         | very common pattern, so it's not unreasonable to argue it can
         | be further refined/abstracted.
         | 
         | Most people agree that "for x in y: foo(x)" is an improvement
         | over "for(int i=0; i<=n; i++) {foo(x)}". It's not wild to argue
         | that "foo' x" is an improvement over "for x in y: foo(x)"
        
           | enriquto wrote:
           | > Most people agree that "for x in y: foo(x)" is an
           | improvement over "for(int i=0; i<=n; i++) {foo(x)}"
           | 
           | Well, I don't.
           | 
           | The second version is much clearer to me, for example, if I
           | want to be sure about the running time of the code. Will it
           | take one second or be instantaneous? The first example hides
           | the length of y. I favor a programming style where all loops
           | have clearly identifiable bounds. Using "iterators" hides
           | these lenghts and makes the code much harder to reason about.
        
             | Jtsummers wrote:
             | The analogous for loop would have you compute n from the
             | size of y. You still don't know, statically, the length of
             | the loop in that case unless you statically know the size
             | or length of y.
             | 
             | It has the same problem either way. Similarly, if you
             | always know the size of n in the C style loop, then you
             | would always know the size of y in the Python style loop.
             | 
             | The Python loop just has the benefit of being shorter (by a
             | line and a few tokens).
        
             | monkpit wrote:
             | Both hide the length...
        
             | oh_my_goodness wrote:
             | Folks, folks! Can we not downvote _loops_ at least? All we
             | want, I think, are useful tools. For goodness ' sake, isn't
             | it obvious that loops are a useful tool?
             | 
             | Not a compulsory tool, not a forbidden tool, not a foolish
             | tool, not obviously the correct tool in all situations,
             | possibly not something that needs to be mainstream in every
             | work flow? Sure, all of those. But ... we're downvoting
             | _loops_ now?
        
             | nemetroid wrote:
             | If these two examples are intended as alternative versions
             | of the same idea, the value of n would be something like
             | y.size().
        
             | the_af wrote:
             | > _The second version is much clearer to me_
             | 
             | Clearer? Did you notice the second version contains a bug?
             | 
             | The first version makes it easier to spot said bug, because
             | it's more concise and with fewer parts where you could make
             | a mistake. Note that even in this very short piece of code
             | it's easy to miss the bug; imagine in actual loops!
             | 
             | You have no guarantees about the running time of the code
             | in either case, not without delving into the specifics of
             | your programming language.
             | 
             | > _I favor a programming style where all loops have clearly
             | identifiable bounds. Using "iterators" hides these lenghts
             | and makes the code much harder to reason about._
             | 
             | I find the opposite to be the case. Explicit loops with
             | indexes make off-by-one errors likely, which is one of the
             | most common kinds of error. It's impossible to make that
             | kind of mistake with a foreach or map-over-this operation.
        
               | maest wrote:
               | > Did you notice the second version contains a bug?
               | 
               | Whoops - in my defence, I haven't coded in C in a very
               | long time. I'll leave it there, since I think it makes an
               | (unwittingly) good point.
        
               | enriquto wrote:
               | This is mostly a matter of perspective. An off by one
               | error may go harmlessly unnoticed at worst, or trigger a
               | harmless segmentation fault at best, in which case it's
               | very easy to find and fix the silly problem.
               | 
               | On the other hand, code that hides things tends to be
               | much harder to debug, when it invariably fails. Why is my
               | program suddenly gobbling 10GB of memory? After several
               | hours of profiling and debugging: ah, here's this
               | particular combination of constructors that allocates and
               | frees a stupid amount of memory for no reason.
        
               | RHSeeger wrote:
               | > An off by one error may go harmlessly unnoticed at
               | worst, or trigger a harmless segmentation fault at best
               | 
               | Or be used in a remote code execution attack. So
               | "harmless unnoticed" is FAR from the "worst" case.
        
               | yjftsjthsd-h wrote:
               | Yeah, I had to reread that because I'm pretty sure
               | "silently wrong" is the _worst_ case, and _not_
               | harmlessly so; I would 100% prefer segfaults, which let
               | me actually fix the problem or at least know something 's
               | wrong.
        
               | the_af wrote:
               | > _An off by one error may go harmlessly unnoticed at
               | worst, or trigger a harmless segmentation fault at best,
               | in which case it 's very easy to find and fix the silly
               | problem_
               | 
               | Yes, but even better if you don't write the bug to begin
               | with. It can also fail harmfully, like failing to trigger
               | a segfault but producing the wrong computation, resulting
               | in sending the wrong amount of energy to the X-Ray
               | machine, the wrong amount of money in a bank operation,
               | firing the wrong number of missiles, etc.
               | 
               | > _code that hides things tends to be much harder to
               | debug, when it invariably fails_
               | 
               | In the general case, I agree. In the case of foreach
               | loops (or maps or folds) the good thing is that they are
               | well understood abstractions, not something your fellow
               | coworker wrote and that you must understand from scratch.
               | They are common idioms, just like you don't debug (under
               | normal conditions) how a for-loop with an index works
               | internally.
        
         | 8note wrote:
         | Loops imply that the order matters when it doesnt.
         | 
         | When order does matter, loops are quite enjoyable
        
           | math-dev wrote:
           | Thanks, this was a good post and worth skimming through all
           | the other nonsense to get to
        
         | p2t2p wrote:
         | For me at comes down to telling "how" instead of "what" to do.
        
         | rickbdjvdjb wrote:
         | The PLT position on this is that loops are _too_ expressive. We
         | 'd like to use the least powerful tool that can do the job.
         | Loops can express all sorts of incorrect versions of the
         | intended algorithm. It's preferable to try to identify the
         | reusable structure in the algorithm (it might be a "scan" or a
         | "fold", for example) and implement that once and for all. We
         | then provide a simple interface to that algorithm, without any
         | footguns.
        
           | dgb23 wrote:
           | That's like saying a bag is too expressive because I can put
           | all kinds of things into them.
        
             | the_af wrote:
             | > _That 's like saying a bag is too expressive because I
             | can put all kinds of things into them._
             | 
             | That's entirely accurate. A bag is "too expressive", in
             | that sense. Sometimes you want to constrain the kind of
             | container so you don't put the wrong kind of thing in
             | there.
             | 
             | Another analogy: types. "Any" will let you use anything,
             | but sometimes you want to say "only allow Integers here".
        
               | dgb23 wrote:
               | What I intended to hint at is that a bag being expressive
               | is not the issue. Yeah you sometimes want to label the
               | bags, or make special purpose bags. But bags as they are
               | are not too expressive. Them being expressive allows for
               | all that other stuff. Their core utility is what we're
               | after and the rest is secondary support and should be
               | treated as such or else we get lost.
               | 
               | If we act like we're not actually dealing with bags at a
               | fundamental level, we might mislead ourselves and spread
               | the belief that the rules and labels around the bags are
               | what matters. No, they help you (temporarily) to use the
               | bags in a specific way in some context.
               | 
               | In fact, computers are extremely expressive, (bag like)
               | things. The constraints are auxiliary. I think it's
               | important that we keep reminding us what programming is
               | and that it serves a direct purpose, while the
               | bureaucracy is a support structure at best.
        
               | the_af wrote:
               | Agreed that computers are extremely expressive. I think
               | the constraints are necessary though, and not just
               | bureaucracy. After all, what are bugs (especially harmful
               | bugs) but a failure to constrain choices?
        
               | throwawaygh wrote:
               | _> The constraints are auxiliary._
               | 
               | Constraints enable reasoning, both automatic and manual.
               | 
               | Loops fit the single-core CPU hardware paradigm
               | extraordinarily well. As soon as you start targeting
               | modern hardware, more constrained recursion/iteration
               | schemes start to buy a lot more in terms of both
               | developer time and compiler complexity.
        
             | maest wrote:
             | More practically, a List is too expressive, if all you need
             | is a Set - which is true and generally good advice to use
             | Set instead of List, if possible.
        
               | dgb23 wrote:
               | The "if all you need" part is very important here.
               | Without it the previous sentence makes no sense at all.
               | 
               | In addition to that, the Set is more "expressive" than
               | the list in the right context. Or rather it expressing
               | things more directly.
        
               | enriquto wrote:
               | From what kind of programming hellhole do you come from,
               | where using Set instead of List is good advice? Lists are
               | very natural constructs for computers, sets aren't.
        
               | RHSeeger wrote:
               | A set implies a uniqueness constraint that a list does
               | not.
               | 
               | Generally a list is ordered by contract, whereas whether
               | or not a set is ordered depends on the implementation.
        
               | nkingsy wrote:
               | Sets generally will give you constant time lookup where
               | lists generally will not.
        
               | Jtsummers wrote:
               | Sets specify a particular interface but not a particular
               | implementation or backing data structure. You can
               | substitute different implementations (including ones
               | backed by lists, vectors, hash tables, and others)
               | depending on your particular needs.
        
         | pasquinelli wrote:
         | if you're not writing explicit loops then you must be doing
         | something else. is that something else interesting? you'd have
         | to dig in a little bit to find out.
        
         | dragonwriter wrote:
         | > Why so much hate for loops?
         | 
         | Well, for me, because, compared to other notation, they usually
         | obscure intent.
         | 
         | > Loops are just a nice notation for some computational
         | constructs.
         | 
         | But... they're mostly not.
         | 
         | > Loops are just a nice notation for some computational
         | constructs.
         | 
         | Sometimes, but even then if a language has decent abstraction
         | features that's usually best for the low-level implementation
         | of a generic algorithm, which you should never have to see when
         | actually applying the algorithm to concrete types. True, if you
         | don't have a generic implementation available, YAGNI often
         | argues in favor of a concrete one for the immediate use case,
         | so generic implementations separated from immediate concrete
         | use isn't the only place to use explicit loops.
        
       | johan_felisaz wrote:
       | Genuine 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)
        
         | the_optimist wrote:
         | There's ^: DoWhile
         | 
         | https://wiki.jsoftware.com/wiki/Vocabulary/hatco#DoWhile
        
         | RodgerTheGreat wrote:
         | in 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....
        
         | mlochbaum wrote:
         | The 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
         | 
         | [1] https://mlochbaum.github.io/BQN/doc/control.html#low-
         | stack-v...
        
       | odipar wrote:
       | No Stinking Loops! http://www.nsl.com
        
       | sys_64738 wrote:
       | When I saw the title the only method for repetitive code I could
       | think of to replace loops was recursion.
        
         | nmz wrote:
         | I think this is what ATS does, it does not have loops, only
         | recursion.
        
       | awinter-py wrote:
       | 'and then he discovered loops' classic early apple interviewing
       | story
       | 
       | https://www.folklore.org/StoryView.py?project=Macintosh&stor...
        
       | orcasushi wrote:
       | I 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.
        
       | mlajtos wrote:
       | I will always upvote APL/J.
        
       | matthewaveryusa wrote:
       | Sean 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
        
         | [deleted]
        
         | magicalhippo wrote:
         | While 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.
        
           | thenewwazoo wrote:
           | Obviously I can't see the business logic but I would express
           | this (or what I believe I understand of it) as a fold that
           | accumulates a map of structures which organize the invoice(s)
           | alongside the orders somehow. I suspect that would result in
           | a kind of "inversion" of responsibility that might be
           | unfamiliar at first, but may well result in a clearer
           | relationship between the data elements to someone new to the
           | code (and may make refactoring a lot easier).
        
           | lodi wrote:
           | Can you post a (simplified) sketch of what your looping
           | solution looks like? I'd like to give it a shot with
           | map/filter/etc so we can compare.
        
             | magicalhippo wrote:
             | It's very close to this, in C#-ish pseudocode, and barring
             | errors introduced by my fried Friday brain. The duplicate
             | invoice lookup isn't a linear search but I wanted to keep
             | it simple. In reality order items have various sub-items,
             | but lets ignore those.
             | 
             | It's not super elegant, but in my mind it's straight
             | forward and should be easy for my colleagues to jump in and
             | understand. Though I'd be delighted to be proved wrong.
             | var dstOrder = new Order();         var invoiceMap = new
             | Dictionary<Invoice,Invoice>();                  for
             | srcOrder in SrcOrders do         {           for srcInvoice
             | in srcOrder.Invoices do           {                     //
             | compares using invoice number and currency             var
             | dstInvoice = dstOrder.Invoices.Find(srcInvoice);
             | if (dstInvoice == null) {               dstInvoice =
             | dstOrder.AppendInvoice();
             | dstInvoice.CopyFrom(srcInvoice);
             | invoiceMap[srcInvoice] = dstInvoice;             }
             | else              {               dstInvoice.Amount +=
             | srcInvoice.Amount;             }           }
             | for srcItem in srcOrder.Items do           {
             | var dstItem = dstOrder.AppendItem();
             | dstItem.CopyFrom(srcItem);             // map old invoice
             | reference to new (possibly consolidated) invoice
             | dstItem.Invoice = invoiceMap[dstItem.Invoice];           }
             | }                  dstOrder.Save();              return
             | dstOrder;
        
               | convolvatron wrote:
               | some of this is a little unclear. in particular how the
               | source invoices are identified and mapped and therefore
               | what invoiceMap really looks like. but here is a
               | group/sum in a madeup datalog dialect:
               | 
               | reconcile(dstId, amount) :-
               | Orders(srcOrder)            Invoices(srcInvoice,
               | srcOrder)            unique(dstId, srcInvoice.id)
               | amount = {          sourceInvoice.id = dstId,
               | sum (srcInvoice.Amount)       }
               | 
               | that produces invoice/sum pairs. Orders and Invoices are
               | your source relations. unique produces a set of unique
               | source invoice ids, which drives the cardinality of the
               | aggregate. the block notation is a scoping construct
               | encapsulate the set cardinality changes.
               | 
               | not going to claim that this is inherently more readable
               | or captures all the subtleties. but you can imagine that
               | terse expressions of intent like this are more accessible
               | to the reader and less error prone. if not, I should do a
               | better job.
        
           | valenterry wrote:
           | > 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
           | 
           | 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.
        
             | magicalhippo wrote:
             | I think I get your point, however the real world is
             | muddy...
             | 
             | > I would model it like that
             | 
             | How does your model take into account orders where order
             | lines do not map to an invoice, or which has invoices which
             | no order lines references?
             | 
             | edit: interesting example btw, I do like a lot of aspects
             | and it was fun seeing some "practical" Scala, haven't had a
             | chance to dig in much. Much appreciated!
        
       | ThePhysicist wrote:
       | BPF 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 wrote:
       | From 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
       | 
       | * https://news.ycombinator.com/item?id=22192656
        
         | jhgb wrote:
         | It 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 wrote:
         | This 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 wrote:
           | According to Carmack subroutine calls are also disallowed.
           | See https://web.archive.org/web/20210226152857/http://lambda-
           | the...
           | 
           | This sounds very much like programming a traditional
           | Programmable Logic Controller (PLC).
        
             | aaaaaaaaaaab wrote:
             | Well, a flight control computer is not too different from a
             | PLC.
        
         | mikepurvis wrote:
         | A popular example given for this is often pcap/tcpdump
         | filters-- for perf reasons they have to be executed by the
         | kernel, but because they're an untrusted user "program" being
         | run in kernel space, they can only ever jump forward, ensuring
         | an upper bound on their runtime.
         | 
         | See: https://en.wikipedia.org/wiki/Berkeley_Packet_Filter
         | 
         | That said, it looks like as of Linux 5.3+, there are certain
         | bounded loops that _are_ permissible:
         | https://lwn.net/Articles/794934/
        
       | oh_my_goodness wrote:
       | HN'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 wrote:
         | > 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 wrote:
         | _> 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]
        
       | sharmin123 wrote:
       | Guide on The Mental Health Effects Of Extramarital Affairs:
       | https://www.hackerslist.co/guide-on-the-mental-health-effect...
        
       ___________________________________________________________________
       (page generated 2021-11-26 23:01 UTC)