[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)