11 ms·
Loopless Programming
- japanoise 7y agoJ is one of the strangest programming languages I've ever tried. I would reccomend giving it a go, though I wouldn't personally write actual software in it.
- beagle3 7y agoI recommend trying APL or K instead. APL has weird symbols, so forces you to abandon any previous notions. K is super minimalistic so easy to keep in mind. But J is both huge and very different but uses e.g. brackets as individual characters (not in pairs) and other things that break your visual pattern matching. So it’s a lot more effort, IMO After you have grokked it, if you like it - then, by all means do look at J. It is in many ways a purified APL albeit with ascii only characters, and a few decisions that look good on paper but less so in practice (like forks and trains) I don’t write APL or K or J myself, but my C code (and some of my Python) is dramatically changed as a result of working with K in the past — it is dramatically simpler and faster, although less idiomatic for those languages.
- bbanyc 7y agoJ is APL in ASCII. My first PC was a hand-me-down from a family member who had installed APL on it and put a bunch of stickers on the keyboard for typing all the strange characters. Where I work, we have some production code in another APL-family language, q, which is the main interface to kdb - a remarkably fast time-series database in a remarkably tiny binary, with a remarkably expensive price tag. I think APL and its kin disprove the "Blub Paradox." Here's a language that in certain aspects is far more powerful even than Lisp, and in others falls down on tasks that are trivially simple elsewhere. And that's fine. Horses for courses.
- hinkley 7y agoI recall another language that was trying to do something similar. In most languages you have to decide about x:1 versus x:* relationships very early and it’s a painful refactor to go back and fix it. This one tried to fix it as well so that much of the time you only changed the variable declaration and the code just worked. jQuery made a similar observation, and many of its functions are list comprehensions. If they had chosen a different default behavior for empty lists I might still be championing it today. But the silent failures were a bitter pill to swallow. In fact I sometimes still fantasize about building a mini front end framework containing the inverse behavior and some other more modern ideas.
- wavefunction 7y agox^n includes x^1
- hinkley 7y agoThose weren’t exponents and this sounds like a non sequitur. Also variance disagrees with you. A function that takes a single value may be generalized to take multiple values but it’s harder for a caller that expects a single value in return to be faced with multiple answers. If I ask for your shipping address and get three, I can’t send the package to three places. I have to change the workflow quite a bit.
- k__ 7y agoWhat's the inverse behavior?
- hinkley 7y agoAh, yes. I meant fail on an operation on an empty set. The number of times that I’ve had a potentially empty set is a fraction of all situations. Often a select all/select none situation, and adding a flag for silent failure in that case is cheap compared to all of the other bugs I’ve had to fix.
- lsb 7y agoYou see some of this with Numpy, where a +, for instance, has been overridden to mean addition of n dimensional arrays, and Numpy is foundational for a lot of the data science programming these days
- jadbox 7y agoAs far as explicit loops, I probably use write out a loop once a month and maybe not even that often. Using map/filter/reduce [as well as sugar funcs until/any/all] solves virtually all the common cases of working with lists. Granting it's not sufficient if you're writing specialized code like sorting arrays efficiently, but for general development, going higher-order is the way to go.
- banhfun 7y agoI agree, the only time I write a loop out is when I need to break out of it
- agumonkey 7y agoYou can say this now but not long ago map/filter/reduce were almost esoteric.
- whateveracct 7y agoI'd say even just 5 years ago they were weird FP features, with LINQ being the most mainstream version. Now Go is the only mainstream language without them hehe.
- suzuki 7y agoYou can write your own LINQ in Go with a very short program. See https://github.com/nukata/linq-in-go https://github.com/nukata/linq-in-go for example. Here is a self-contained excerpt: type Any = interface{} type Enumerator func(yield func(element Any)) // Select creates an Enumerator which applies f to each of elements. func (loop Enumerator) Select(f func(Any) Any) Enumerator { return func(yield func(Any)) { loop(func(element Any) { value := f(element) yield(value) }) } } // Range creates an Enumerator which counts from start // up to start + count - 1. func Range(start, count int) Enumerator { end := start + count return func(yield func(Any)) { for i := start; i < end; i++ { yield(i) } } } Now you can write the following: squares := Range(1, 10).Select(func(x Any) Any { return x.(int) * x.(int) }) squares(func(num Any) { Println(num) }) // Output: // 1 // 4 // 9 // 16 // 25 // 36 // 49 // 64 // 81 // 100 I'd say it is so elegant in Go!
- defanor 7y agoSee also: array programming [0] (apparently 1970s APL, on which J is based, didn't even have explicit for/while loops). Nowadays even common imperative languages have higher-order functions, so I don't find it very exciting. Though still quite handy in R and similar languages where you deal with array-like structures almost all the time. [0] https://en.wikipedia.org/wiki/Array_programming https://en.wikipedia.org/wiki/Array_programming
- NOGDP 7y ago> Looping - performing a computation repeatedly - is what programs do. In most computer languages, all loops are expressed by one of two statements: > Do While - repeat a code block until a condition is met > For - repeat a code block, with a loop index indicating how many times the block has been repeated. > Programmers trained on scalar languages have spent many years internalizing the Do-While and For paradigms. Discarding these paradigms is the biggest re-think they need to make when they learn J. A lot of languages have these kinds of semantics and arguably in a more streamlined / better organised way - Haskell typeclasses for example. I don't know if it's true that 'most languages' only support for/while loops, but probably not. It also doesn't take 'many years' to internalise for/while loops, these are fairly basic constructs which are learned by most novices in the beginning of an introductory programming course. > (x + y) is an expression rather than a statement. The J programmer can embed (x + y) in a larger expression, perhaps a matrix multiplication (w +/ . * (x + y)) which adds the equivalent of three more nested loops, but is still a single expression. Expressions can be combined; statements cannot. (x + y) is going to be an expression in almost any language... > Many modern languages have iterators, which slightly streamline the For loop, but without addressing its fundamental deficiencies. What are the 'fundamental deficiencies'? This seems like a pretty low-effort write-up.
- esotericn 7y ago> It also doesn't take 'many years' to internalise for/while loops, these are fairly basic constructs which are learned by most novices in the beginning of an introductory programming course. The comment is referring to the patterns becoming ingrained in the mind through years of use, not that learning how to produce a while loop for the first time takes years.
- NOGDP 7y agoThere's not much to be ingrained after you learn how for/while loops work, and there's not much in the way of learning how map/filter, or overloaded functions work. I wouldn't even consider the overloaded '+' operator as a loop, you can think of it as a single operation, it's an implementation detail.
- 7y ago
- joncp 7y agoI've gotten to where I see loops as a code smell. That includes thinly-veiled loops like ruby's Enumerable#each.
- tom_mellior 7y agoAnd instead you use...?
- krferriter 7y agoYeah I'd like to know also. Loops are a foundational operation. Instead of a loop you could use a function/operator overload, which would just call another function which contains the loop. But that isn't doing away with loops, it is just encapsulating them inside a function and calling them in order to avoid polluting up the higher level code context with loop code, and makes that upper context easier to read.
- wffurr 7y agoYou described the GPs point perfectly. This is also covered in the article; it exhaustively covers the use cases for loops and provides higher level functions and operators instead.
- tom_mellior 7y agoIt covers the use cases for loops over arrays/lists, not over more general graph-like structures.
- wffurr 7y agoFor which there exist a slew of higher order graph traversal algorithms.
- riquito 7y ago> Loops are a foundational operation. Instead of a loop you could use a function/operator overload, which would just call another function which contains the loop That's incorrect, you can always replace a loop with a recursive function (e.g. as you do in Erlang where there aren't explicit loops)
- m0zg 7y agoLoops of known size is the one of the best hints you can give to your compiler though. If the computation inside the loop isn't too complicated, the compiler will unroll these, often resulting in amazing speedups. In fact if you know that your array is pretty long but don't know its size, it's often worthwhile to structure the computation as a nested loop, where the inner loop processes chunks of known length (which should be fairly short, and picked to utilize the widest SIMD on your target CPU, e.g. 8 for fp32 on most Intel chips). Then when you run out of chunks you "mop up" at the end. Furthermore, if your chunk size is a power of 2 (which it should be), don't be afraid of the modulo operator (which is normally very slow): the compiler will automatically apply the x % chunk_size == x & (chunk_size - 1) trick for you. All of the above obviously only works if chunk size is known at compile time.
- roelschroeven 7y agoGood modern compilers do all of that for you, even for unknown sizes. For example, see how clang unloops (and vectorizes) a simple loop: https://godbolt.org/z/NPfnhb https://godbolt.org/z/NPfnhb
- m0zg 7y agoYes, in trivial cases like this one, the compiler will generate assembly that's hard to improve upon. But if there were more meat on the bones there (as there is, most of the time, if you aren't just summing ints), this technique could help. As always, benchmark before and after and keep the best variant.
- unnouinceput 7y agoThis reminds me of regex, and the struggle 2 months later to remember wtf I was trying to do there. Easy to write, hard to maintain, so thank you but no thank you, I prefer the loops instead even if it means going down at individual elements in a list/matrix. For any meaningful project maintenance is the threshold that will make it or break it.
- Felk 7y agoThere are cases where I very much prefer a declarative style. Multiplying matrices for example. Depending on the context, I will know what it does conceptually, but don't want to see the implementation, as it probably won't matter. My rule of thumb is: are you doing something unexpected ? show the implementation : show the concept
- krferriter 7y agoHow often do you actually write out matrix multiplication in the main code flow? Generally you extract that into a function and just call it like `matrix_multiply(A, B)`, which in language which supports operator overloading could be attached behind the expression `A * B`. It's not that loops aren't happening, but the loops are just wrapped up in another expression that is more semantically meaningful, which is a good practice in general.
- noobermin 7y agoIt really depends on the situation. For regex, certain things are much more understandable than a long set of loops for it, as long as the regex isn't too complicated. Ditto for implicit for-loops for addition in say numpy or fortran 90. I've seen scientific codes in C that have like 5 to 6 order nested loops that end up being thousand+ of lines, after which the length requires much much more time reinternalizing after coming back months later, or worse, as someone who didn't write it, all due to the fact that the whole damn thing can't fit on a single page of emacs and be still legible.
- coldtea 7y ago>I prefer the loops instead even if it means going down at individual elements in a list/matrix. Actually you have it backwards, probably because you confused J's somewhat cryptic notation as the only way to implement the same concept. It's the for loops, with their sprawl of non-declarative code, that would be equivalent to the regex opaqueness, and a well named operator or function to achieve the same thing that would be more like J. E.g. would you rather use a: sort(myList, order=desc) or write your own sorting with for loops? Similarly, think of operations like: findElement(myList, predicate) filterElements(myList, predicate) keepElements(myList, predicate) forEach(myList, myFunc) forEachParallel(myList, myFunc, chunks=5) In other words, reduce, map, and specialized versions of them. If 10-20 such language provided functions covered all cases, I'd use them over for loops all the time. In fact that's how people use e.g. lodash. What J adds is a succinct syntax to write and compose such primitives at the language level. But such a syntax is not necessary to achieve the same concept (although not the same brevity/expressiveness) and be better/more readable than for loops... P.S And regexes themselves should be compared to the equivalent parsing code -- which could require a FSM, or an ad-hoc buggy implementation of the same checks/captures spanning 10s or 100s of lines depending on the regex. Except if you just use a regex for very simple things where e.g. a "contains" function or some splitting etc will be simpler.
- ncmncm 7y agoTl;dr : APL has implicit loops because almost everything is an array, and mentioning an array implies a loop to process it. In C++, nowadays, people are encouraged to use Standard Library algorithms in preference to most loops; and to make an algorithm out of any loop that can't be cleanly replaced with a Standard one, and call that. The reasoning is similar to that explained in the article, except that saying which you are doing, by naming the algorithm, communicates better what you are trying to achieve than does coding the loop in place. In APL, of course, naming anything with more than two or three letters, or wrapping a one- or two-operator expression in a function, gets you looked at funny, so that reason wouldn't apply.
- 7thaccount 7y agoFrom what I've read, your last paragraph is true, but only because the naming often isn't necessary. The importance of abstraction in a codebase that is 60 pages of code is high, but it fades away if the equivalent J program can be 5 pages of expressions. Instead of defining some numerical operation that is only 5 characters long, just use the 5 characters in the 2 to 3 places where it is needed in your 5 pages of code. This way everything is explicit and you don't have to reference back to the definition. This would be an awful idea in Java, but it works great in J and APL. Aaron Hsu talks about this with his parallel APL compiler and refers to this as working at the macro level. Note that he has plenty of named items that are passed around in a data flow fashion, but he doesn't need to name every little function.
- ncmncm 7y agoThe naming is never necessary, in any language. Even where a function is called, the name may be uninformative. A name is an opportunity to provide useful information about intent to the reader. Open-coding, however concise it may be, fails to communicate intent.
- jancsika 7y ago> In J we do this by creating a Boolean list, one value (0 or 1) per item of z Does the word "creating" here mean you can't do a relatively simple check across array elements in J without allocating memory? E.g., soft realtime scheduling for such a simply operation is essentially just not possible in J?
- jonahx 7y agoI don't recall offhand the answer to this specific question, but many common operations in J are highly optimized under the hood. So even though you are doing "conceptually expensive" things, J will often implement them efficiently under the hood. J is quite fast, shockingly so for an interpreted language.
- jameshart 7y agoMost programmers have worked in a largely loopless programming language: SQL. IT lets you easily build the same sort of ‘Boolean state for every item’ as this discusses, but it requires you to be much more explicit about which items you want to line up next to one another if you’re joining two lists together. In modern languages we usually just use mapreduce like functional approaches to handle the same sort of thing, so I’m not sure this approach is as unusual as the author seems to think.
- 3xblah 7y agoIs there a difference between loopless and "largely loopless". https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/loop_statement.htm https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/loop_... The wiki author, who also wrote J for C Programmers, never uses the word "unusual". He uses the words "different" and "better". Is it only the parent comment that invokes the word "unusual". The comment also implies J is not a "modern language", whatever that means..
- misterdoubt 7y agoPL/SQL is not SQL.
- kqr 7y agoDo recursive CTEs count as SQL, then? Despite the name, they are practically loops.
- kragen 7y agoI wrote this query for Dercuano the other day; it computes the Collatz sequence starting from each of the first 100 integers, running a second Collatz computation twice as fast in parallel to detect any cycles (and of course there are none): with recursive collatz as (select 1 as x, 1 as n, 1 as m, 0 as p, 0=1 as exit union select case when nextx then x + 1 else x end as x, case when nextx then x + 1 else (case n % 2 when 1 then 3*n + 1 else n / 2 end) end as n, case when nextx then x + 1 else (case newm % 2 when 1 then 3*newm + 1 else newm / 2 end) end as m, case when nextx then 0 else p+1 end as p, case when nextx then 0 = 1 else n = newm end as exit from (select (m = 1 or newm = 1) as nextx, x, n, m, p, newm from (select x, n, m, p, case m % 2 when 1 then 3*m + 1 else m / 2 end as newm from collatz where not exit) a) b where x < 100) select * from collatz; It works in Postgres 9.5.14; I'm interested to hear if it works or doesn't in other SQL implementations. I think this definitely qualifies as a loop.
- henrikschroder 7y agoIn C#/.Net there's language constructs like LINQ, and framework methods on generic collections like Select() and Where() and SkipWhile(), etc. For example, if you want to write code that returns the first X items in an array that satisfy a predicate P, it will look a lot neater and readable using code like that, than if you were to write it with for loops and if statements. But it's still just syntactic sugar, the neater code just masks the underlying code that contains the actual loops and conditionals. As always, it's a tool, and can be misused like all tools. It's a balance, the neater code might be more readable, but the for/if code might be easier to change or optimize down the line if conditions change. It all depends. There are no silver bullets, just tools, and trying to minimize the amount of tools in your toolbox is just dumb.
- Gibbon1 7y ago> But it's still just syntactic sugar, the neater code just masks the underlying code that contains the actual loops and conditionals. It seemed to me that was often more performant than using a loop.
- wffurr 7y agoLinq pipelines are lazily evaluated, so you can get linear performance instead of polynomial.
- henrikschroder 7y agoIt depends on what you do, and the underlying code can be pretty smart. But it can also be pretty dumb. The main difference is that if you use .Where(P).Take(X), you're actually generating a new enumerator, a new coroutine, and that code doesn't get called until you actually enumerate it. But if you write your for/if loop, you run the code immediately, you go through the source array, run the predicate on each item, until you have your X items, and then you return a new array with those elements.
- stabbles 7y agoC++ devs are encouraged to do loopless programming by using algorithms in the standard library; plus C++20 ranges should improve the ease of loopless programming. Where J shines is array based programming. The C++ algorithms are concerned with vectors, but in J you can combine matrices, vectors and scalars in concise expressions.
- 7thaccount 7y agoMy favorite example is not the commonly seen average function in the J library (avg:= +/ % $), but the one that Dyalog APL has with their interactive tutorial that looked a little weird to me until I figured out it didn't just work over lists, but arbitrary dimensions like an array of 2x2 matrices. I just laughed to myself...oh what magic the gods have wrought. Unfortunately, my programming needs are high performance scientific computing where APL just doesn't have enough horsepower in most cases (J has some ability to call out to BLAS/LAPACK, but Julia makes it so easy to work with sparse matrices that it has become my go-to Lang for that work) and I also do scripting (Python, Bash, Powershell, and a little Perl6) and J/APL are a little awkward in these areas IMO.
- blt 7y agoThe C++ <algorithm> library has a huge limitation: algorithms return a single iterator that cannot be further consumed by other algorithms. This makes it impossible to chain algorithms in a manner like LINQ. The ranges library will make a dramatic difference for this programming style.
- JesseAldridge 7y ago> add each number in a list x to each number in the corresponding row of a two-dimensional array y I mean, I could write a function `add(x, y)` that does that in any language. You could even inspect the data or type to make it polymorphic. I believe NumPy does this, for example. Skimming the rest of the article, I don't get how this is different from any other language. The primary difference seems to be the function names are all one or two characters long for some reason. I must be missing something...
- plorkyeran 7y agoThe fact that they're built into the language is in fact significant. Yes, you obviously could implement J's set of primitives in nearly any language and then use those. In practice though, you won't, and if you did then many of the people reading the code wouldn't understand it very well. Imagine if lodash was built into JS, and optimized by the implementations to the point where it was faster than not using it. Idiomatic JS would look very different even though it hasn't made anything possible that wasn't previously possible.
- deleted 7y ago[deleted]
- agumonkey 7y agoI find that most programming languages are often wasting time on the notion of plurals .. loopless sounds appealing there.
- theamk 7y agoIf you really want to play with loopless programming, I recommend Python’s numpy. It implements many of the same constructs, but using regular English words instead of punctuation.
- kccqzy 7y agoWell numpy doesn't totally obviate the need for loops though, although it is a good mental exercise to try to write as few loops as possible.
- johnday 7y agoAlmost any functional programming language has the same power and quality of life in programming, except with the added bonus that things have names. If you come up with a new looping construct (which you probably won't), just create a type class for it and you're done; any data structure you want now has that construct, if you give it a sensible implementation. It would be very unusual to write a Haskell program with explicit loops in it. The closest you ever get would be something like `forM_` and even that doesn't really count. I suppose a more direct example would be a recursive function call, but imperative programmers may be surprised at just how few of these actually crop up in production code.
- whateveracct 7y agoAnd even when recursion does show up, the return value tends to "read" better. Like "the sum is the head of the list plus the sum of the tail." Whereas with an imperative loop, I have to either recognize an idiom or manually execute the loop in my brain's VM.
- fake-name 7y agos/loopless/explicit loop/
- pjungwir 7y agoI still remember as a kid back in the 80s when a friend and I were making BASIC games on our Tandy 1000s with just the reference manual that came with the computer, and he was trying to explain to me what a FOR loop was, and I was not getting it. "Why would you want to do the same thing twice?" Just this morning my own 10-year-old was looking over my shoulder while I was debugging some C, and I explained for loops to him. Since he plays violin I said it was like a repeat in music, and he seemed to grok that right away. My other memory of those days is, after years of dismissing GOSUB as useless ("Why would I want to go back to the same place I just left?"), finally having a flash of enlightenment and getting the point. It's a function call! (Not that I knew what those were....) Sorry this has nothing to do with the wiki page. :-) Except maybe that to at least one kid loopy programming was unnatural.
- cdrini 7y agoHaha, you're not alone. I remember when I first learned programming, loops me a while to get. Can't remember why, just remember the confusion :P Now if I'm teaching someone, I like to explain loops like going through a stack of books. You have a stack of books, and you go through each one and do something with it.
- tluyben2 7y agoHa! Yes, I only had the reference manual and some (published by photocopying matrix printed writing) magazines to work with begin 80s as a kid and I remember making goto’s jump forward and then when done backward; some older guy at a meetup (I was the youngest for many years there) told me to check out gosub; that suddenly clicked after that. At first I thought they were just inconvenient goto’s. I guess my relationship between that time and j/k is that I really like the concept of just needing a (printed!) reference manual and being able to do everything you need. It is so liberating (for me anyway); it is also the reason why I like embedded asm/c programming: mostly I need nothing but the reference manual (and after all these years, not that even). To me it makes other types of work (web frontend/backend or native apps) with all their (unstable) libs tedious to work with; I am good at those (esp native apps) but I don’t really enjoy it as much.
- chris_wot 7y agoThat table if alternatives to loops is really interesting. I wonder if there is a similar table for C++ in some form on another website?
- deleted 7y ago[deleted]
- ncmncm 7y agocppreference.com The Standard Library algorithms explicitly implement such a taxonomy.
- jonny383 7y agoThis article doesn't actually list any self-claimed "fundamental deficiencies". There's a reason that C (any many other languages) have been using pre and post conditional loops for over fifty years - because it's what the machine actually does, and they are straight forward to follow. The article mentions "A C programmer would write", and then shows two nested for loops. This is almost correct, but I think any good C programmer would write this once as a method or a macro, and then calling it is as simple x + y, without the snake-oil. In either case, both are going to execute something like .loop CMP r0, r1 JNZ .end_of_loop ADD r2, r1 ; move some memory around JMP .loop .end_of_loop ; And what does that look like... oh wow it's a loop. The J language was written in 1990. It's now 2019. The language is just wrong.
- thenewnewguy 7y agoI have no real comment on C vs J in this instance, but I find it ironic these two statements appear in the same comment: > There's a reason that C (any many other languages) have been using pre and post conditional loops for over fifty years > The J language was written in 1990. It's now 2019. The language is just wrong. Is some practice being old good or bad?
- jonny383 7y agoIt's highlighting that C (and related languages) have been using loops for over fifty years, and that J has been around for 29 years with no marketshare. Therefore, it's safe to assume that A) C is correct, and people use loops for a reason, and B) J is wrong.
- inimino 7y ago> safe to assume Assuming that because something is popular it must be good is an example of assuming that things are as they should be, or deriving ought from is[1]. Assuming that because something is unpopular, it must be "wrong" is worse. Do you make all your technical decisions based on what is most popular? Do you always assume that whatever has not become popular must be "wrong"? [1]: https://en.wikipedia.org/wiki/Is%E2%80%93ought_problem https://en.wikipedia.org/wiki/Is%E2%80%93ought_problem
- stewbrew 7y ago2 questions: - Doesn't "loopless" actually mean "implicit looping"? (unless you assume infinite parallelization) - How do you debug a chain of "loopless" functions when some corner case invalidates your assumptions?
- brudgers 7y ago1. The article describes different types of loops. If you buy that, then "looping" can mean several similar but distinct things. To me, the big distinction is whether or not we "know" how long the input is. Or more broadly, whether or not we can assert that the input terminates. If we can assert that that the input terminates then we can use "for i; while i < input.length" logic. If we can't assert input termination then we have to use "while input.next != EOF" logic and we might wind up calling "input.next" forever (but the system will probably crash first). 2. If the second form of looping -- calling input.next -- is like what you mean by "loopless" then debugging corner cases means something like looking at crash logs. But debugging any crash means looking at crash logs. Or sometimes it means just restarting the system. Which is what a lot of debugging looks like at the interesting scale of widely distributed significantly concurrent computation. 3. For problems of interesting size, assumptions about the data can be made based on statistical analysis of the input. Then corner cases become statistical anomalies that may or may not be worth engineering against. Systems crashing are as inevitable as off by one errors.
- kbbr 7y agoUnless you can leverage special instructions / hw to realize your array operations, loops will be the fundamental operations generated by the compiler executed by the CPU. For a lot of scenarios, loopless code is surely more expressive. However, I would not say that it is per se in any kind superior. Rather, it is a feature of each language, how much to abstract from the actual CPU instructions. So to have fine granular control over your generated code, classic loops in C always relevant, while a simple array operation can be expressed in such high level way for other use cases.
- kerzol 7y agoThere is a CS concept with a similar name, but completely different meaning: https://en.m.wikipedia.org/wiki/Loopless_algorithm https://en.m.wikipedia.org/wiki/Loopless_algorithm
- ridiculous_fish 7y agoFirst I've heard of J! Say you want to compute the product of integers in a list. Should you encounter a zero, you can exit immediately since the product is known. In C this would be an `if (arr[i] == 0) break`. How would one express this in J's loopless style?
- manmal 7y agoSwift provides many of the required Collection operators to go loopless - map, reduce, flatMap, compactMap (eliminate nil values), filter, first, contains, prefix/suffix, ... there’s even an OSS library that gives a compile time guarantee that a Collection is non-empty. There are only some instances where I ever need a loop: - map cannot produce a Dictionary, so Dictionary manipulations usually require a loop - Sometimes, a more complex qualifier or stopping condition is needed. Like striding through a Collection in a non-linear way.
- dorian-graph 7y ago> - map cannot produce a Dictionary, so Dictionary manipulations usually require a loop Assuming you meaning mapping over a list, some sort of fold function could produce a dictionary, in some languages, such as Elm. [1] [1] https://package.elm-lang.org/packages/elm/core/latest/List#foldl https://package.elm-lang.org/packages/elm/core/latest/List#f...
- deleted 7y ago[deleted]
- Athas 7y agoLast time I did APL programming I was a bit bothered by the performance implications of some of the standard loopless programming styles. In particular, it's hard to nest loops. As an example of the implications, consider computing the Mandelbrot set. I'll be using Numpy here to ensure people can follow what I'm doing, but for the point I wish to make, it's similar to how you'd write it in APL. The Mandelbrot set is compute by applying a function like this to each of a bunch of complex numbers: def divergence(c, d): i = 0 z = c while i < d and dot(z) < 4.0: z = c + z * z i = i + 1 return i To apply this to many points simultaneously in a vectorised "loopless" style, we'd write it like this: def mandelbrot_numpy(c, d): output = np.zeros(c.shape) z = np.zeros(c.shape, np.complex32) for it in range(d): notdone = np.less(z.real*z.real + z.imag*z.imag, 4.0) output[notdone] = it z[notdone] = z[notdone]**2 + c[notdone] return output There is just one `for` loop, which is pretty easy to do in APL. The `while` loop has been subsumed into control flow encoded in boolean arrays. This is not exactly how you'd do in APL, but it has a similar feel. It's also pretty slow, because we are manifesting the entire `z` array in memory for every iteration in the outer loop. In contrast, an old school loop over every point, with an inner while loop for every point, would involve only two memory accesses per point. On a GPU, I have measured the vectorised style to be about 30x slower than one with a conventional `while` loop.
- regularfry 7y agoIn theory you could escape that by using generators rather than explicitly creating the intermediate list. No idea if J does that under the covers though.
- flexblue 7y agoQuote: "(x + y) is an expression rather than a statement. The J programmer can embed (x + y) in a larger expression, perhaps a matrix multiplication (w +/ . * (x + y)) which adds the equivalent of three more nested loops, but is still a single expression. Expressions can be combined; statements cannot." What about an expression like (x * x + y * y)? This would still be a single loop in C. Is J smart enough to figure that out, or will it turn that into three loops?