Loopless Programming
code.jsoftware.com
code.jsoftware.com
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.
Sure the equivalent C for loops are a lot more wordy for the problems that this language solves, but you'll drive yourself crazy trying to write a simple event loop for your GUI in J.
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..
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.
> 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.
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.
But the equivalent for loop is not.
> "(x + y) is an expression rather than a statement".
The point isn't that (x + y) wouldn't be a statement in other languages, rather than point is that in languages the reader of the article is most likely to be familiar with, you would use a for loop to solve the problem, and obviously a for loop is a statement, not an expression. You can't compose statements as you can expressions. That was the only point the article was making there.
You clearly missed it, because you went on saying that (x + y) is an expression in almost any language, which while true, is irrelevant for languages where (x + y) isn't the way you would express the operation in question. Which, by the way, includes idiomatic C++, Haskell, and Rust, in all of which overloading (+) to do something so specific would at best be seen in a purpose-specific library (like numpy) only, but isn't automatically present in the language. That means it doesn't force you to think in that way, which J does, which is the point the article is making.
In most languages that most programmers use, for loops are common, and if all of those are replaced by expressions, you get a more expressive language (for dealing with arrays). That's the point that the article is making, and you haven't addressed it at all.
For simple things, you have list comprehensions and things like “sum”, while for complex things one should reach for numpy.
That said, high-performance Python does generally discourage the use of loops in favor of vectorised operations.
No, in other languages you would write one function that specifies how to perform an operation per type, then reuse that, and most linear algebra/numerical libraries come with these defined for you. In languages without polymorphism, such as C, you might have special names for these functions instead, such as vector_add. The actual use of these would look like '(x + y)' in Haskell, Rust, C++, etc. and 'vector_add(x, y)' in C, and would (or at least could) be expressions.
> . Which, by the way, includes idiomatic C++, Haskell, and Rust, in all of which overloading (+) to do something so specific would at best be seen in a purpose-specific library (like numpy) only, but isn't automatically present in the language.
Linear Algebra types/functions aren't automatically present in most languages. A library can be implemented to use this syntax to add vectors/matrices/whatever very easily, and many are [1][2]. Even if the library doesn't use '+' as the operator for eg. vector addition, it could, and it chooses to not use '+', but that is not very important.
> In most languages that most programmers use, for loops are common, and if all of those are replaced by expressions, you get a more expressive language (for dealing with arrays).
I'm not sure what you mean by 'more expressive'? Maybe you prefer a more declarative/functional programming style but for loops have their place in imperative languages. J doesn't actually force you to write all of you code in that style - the language has constructs that can be used to construct loops that look like imperative C [3].
> That's the point that the article is making, and you haven't addressed it at all.
The features described in this article are equivalent to a library implemented on top of Haskell, Rust, C++. The usage of such a library would even look almost exactly the same.
[1] http://arma.sourceforge.net/docs.html#part_classes [2] http://hackage.haskell.org/package/linear [3] https://www.jsoftware.com/docs/help701/dictionary/ctrl.htm
You're not the audience for the article and that's what this entire thread shows. You missed the fact that you were not the audience, and that makes this a low-effort dismissal. Rather than trying to get the point of the article, you're picking on minutiae and trying to pick it apart. This is why we can't have nice discussions about programming languages.
The audience for the article is: people using, learning, or interested in J, who want to know how to think about loops in a "loopless" way, which the language encourages. The fact that you're talking about linear algebra libraries shows that you're not the audience the article is written for.
None of your points are wrong, in some cases you are literally restating my points, but you're not getting the emphasis. Rather than try to prove the article wrong, why not try to see it for what it is, and who it is aimed for, and leave it at that?
> J's approach to iteration is vastly better than other languages.
Those are opinions.
> not well written
That's yours.
Opinions and claims are not mutually exclusive.
> That's yours.
Yup, thanks.
Sometimes commenters will use code formatting with manually entered line breaks, e.g. keeping lines to 72 characters or less. But that is still hard to read on mobile devices.
Instead of code formatting using leading spaces, here's a good alternative for block quotes (the '>' goes in the first column, no leading spaces):
> *A paragraph of block quoted text.*
This renders as:> A paragraph of block quoted text.
If the quote has * characters within it, don't try to italicize it, just do this:
> A paragraph of block quoted text.
which renders as:> A paragraph of block quoted text.
Either way the quoted text will be wrapped correctly on any device.
Give each paragraph its own '>' indicator and a blank line between them so they don't get wrapped together.
In case you don't get a chance to edit your comment, here's a more readable copy:
- - - - - - -
> 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.
- - - - - - -
(end of reformatted version of NOGDP's parent comment, please direct any replies to the original)
Maybe your time would be better spent solving some coding challenges in J or another array language like Dyalog APL, Kona, or A+, then comparing your solutions with others’ using the same language, rather than posting voluminous comments about how your lack of understanding means there's nothing to understand. It probably won't change your life, but it might be a useful tool in your mental toolbox when you're using more mainstream languages like R, SQL, or Octave; libraries like TensorFlow, Numpy, Pandas, PyTorch, or parts of Boost; or hardware like GPUs or processors with NEON and SSE.
(Related: Blub.)
I suffer from the same annoyance about the same thing, so I know where you're coming from; I still do it myself and ought to moderate my own comments when I do. But blunt statements about others' ignorance only lead to pain instead of pleasure, and drive people further away. Not only does accuracy in such blunt statements not help, it makes their effects considerably worse. One of my teachers had a Ph.D. in psychology from Stanford and told me that he learned one thing from his Ph.D.: punishment is not good for learning.
In case it helps with bona fides, we're somewhat licentious about bending the rules in favor of APL and its family on HN. Of the great alternative programming universes (Lisp, Forth, Prolog, ?) it is surely the least understood.
My frustration comes from the quality of the discourse rather than from some feeling that my favorite language is being slighted. Perhaps it's unfair of me to be so demanding of others when I'm so often stubbornly ignorant myself, but I would like people to just not post middlebrow dismissals of this sort (and to tell me when I'm doing something similar); they make rational discussion impractically difficult to find in the interstices between the aggressive posturing. (As inimino said, https://news.ycombinator.com/item?id=21302174 "This is why we can't have nice discussions about programming languages." I believe that's actually true.) Can you imagine biologists attempting to discuss the evidence about the evolution of a particular signaling pathway at a conference where Creationists are shouting that evolution doesn't create new species?
Clearly the folks voting on HN have different preferences.
I want to emphasize that it's not the poster's cluelessness that I'm criticizing. Everyone starts out clueless about everything and stays that way about most things; there is nothing wrong with that. Nor is it their willingness to spout off about their cluelessness — expressing your misconceptions is very often the quickest way to get people to correct them, which happened in this case; both inimino and I wasted our time laying some deep knowledge on the dude. Rather, it's their persistent refusal to notice their own lack of understanding — and the public approbation of that refusal — that I think poisons the well of rational discourse. That's what reminded me of my encounter with Kent Hovind on the streets of Berkeley.
Think, by contrast, of Leibniz's ideal: Quando orientur controversiae, non magis disputatione opus erit inter duos philosophos, quam inter duos computistas. Sufficiet enim calamos in manus sumere sedereque ad abacos, et sibi mutuo (accito si placet amico) dicere: calculemus. Think of the pleasant collegiality that in fact exists among mathematicians, where a few minutes of discussion commonly suffices to convert an opponent into an ally, and the youngest and least experienced can point out an error made by the most respected with the full expectation that their correction — if correct — will be gratefully accepted. In this case, at the other extreme, we have a controversy which is easily resolved with three minutes watching YouTube, but which instead spawned a long thread of aggressive comments, complete with boasts about how easy it is to understand while loops. This is the kind of thing that led to JWZ's famous gibe.
How can we foster an environment more like Leibniz's ideal, if not by pointing out the most egregious discrepancies in behavior? Perhaps my arrogance is not the way — this is IIRC why you didn't want to work with me at Skysheet — but what is?
In everyday human interactions, we have a lot of contextual and social cues about who is more experienced, respected, expert, even who is older; in short, all kinds of social hierarchical information that guides and constrains our behavior. Online, especially in a community of sufficient size that most interactions are between strangers, as here, we lack these cues and must approach each other as equals, or at least as unknowns. It's very democratizing, and sometimes it's incredibly aggravating.
There are things a professor can say to a student that a student can't say to a professor. The professor can police students' behavior and ask a student to leave. So we would not expect this kind of "aggressive defense of ignorance" in a classroom, because there is someone there who is responsible for creating a different environment, as part of an effective, millenia-old tradition of inquiry.
The zen master can whack the novice with a stick. Sometimes leading to enlightenment, sometimes just to the master getting some peace.
As with the professor, it's the relationship and the environment that makes this acceptable. You won't get far by telling strangers on the street that they are woefully uninformed, or by whacking them with a stick. Online, we're essentially all strangers on the street.
Certain basic life skills and attitudes can be communicated and acquired almost immediately when you can get whacked with a stick that can hardly be acquired online at all. Online communities seem to be a terrible place to learn humility, for example.
In response to these realities of online engagement, there are community-level and individual-level approaches. Leaving the former aside, as an individual there are two things I find helpful, one is detachment and the other is to remember the audience.
The "wrong on the internet" compulsion maybe comes primarily from expectations developed offline, where we can talk sense into people, generally within some institution that facilitates this (school, church, work, etc) and which generally doesn't exist online. This can be annoying. Perhaps it's this annoyance that largely leads to endless online arguments. Sometimes you want to communicate to someone, gently but unmistakeably, that you know more about the topic at hand than they are likely to learn in the next ten years. This is likely something that would be communicated automatically and invisibly by environment and context offline, but is almost impossible to communicate at all online. Online, the distinguished biologist and the earnest creationist high school student appear to have equal weight, especially to the high school student. Sometimes you feel the need to communicate to someone that their ignorance of the topic is matched only by their ignorance of their own ignorance, but you can't. You wouldn't be heard, the environment doesn't support it, and anyway it looks bad. If we're all as equals, or at least unknowns, the next person who copies your strong style, more likely than not, will lack the experience that justifies it.
Remembering the audience means that any interaction in a public forum is more likely to influence bystanders than the one directly addressed. It's like a debate, in which debaters address each other but actually aim to persuade the audience. Unfortunately this usually means it's all rhetoric and favors shallow attention-getting over deep discussion and exploration, but that's another topic. Anyway, you can reply to the person but aim more to persuade the audience, which in general is bigger and more likely to be swayed by your reasons than someone who is already arguing against them.
Beyond promoting your position, trying to encourage better discourse, from the audience, rather than the person addressed, seems to help.
It makes me want to take a deeper look at McLuhan, because "the medium is the message" describes this phenomenon like no other phrase can. The more experience I have the truer it seems, specifically about HN.
[1] Large is relative, and HN is small relative to the big fish—5M or so readers a month—but still large relative to human history and to the communities humans are used to. In a discussion like this one, where the participants have deep experience with online communities, it would be more precise to call HN medium-sized.
There is some amount of "social hierarchical information" available, if you look carefully — the original poster in this thread has "karma" of 47, while you have 2957, I have 12549, and dang has 52985, plus 29993 as gruseom, although that's less visible. But of course most of the people at the Hackers Conference don't have HN accounts on here at all, so this is at best a poor guide; and, even for those hackers with accounts on the site, surely it would be a grave error to consider me senior to, say, lutusp, lispm, davewiner, Arnt, tonyg, kens, masswerk, or DonHopkins, simply because my account has higher karma. An even more extreme example is my friend johncowan, who has 6 karma and is one of the major authors of R7RS.
High karma is perhaps more an indicator of the kind of poor impulse control that results in wasting our time trying to educate the deliberately clueless, or in my case just going off half-cocked on topics I don't know enough about, than of actual seniority. All of the people in that list are more accomplished hackers than I am, but they have less karma in large part because they post less, perhaps because they're hacking.
To some extent, spelling, vocabulary, and punctuation are similar signals, but consider https://news.ycombinator.com/item?id=20404735, written by someone who apparently really knew what they were talking about, in depth, in a way that I absolutely did not — "Lol" or no "Lol". And even at best those indicators only serve to indicate social background and literacy, which are only weakly correlated with competence.
I wonder if there is at least something we could do to make voting more thoughtful; for example, put the voting arrows at the end of the comment rather than its beginning, or even after all the replies to the comment. (People could collapse the replies to find the arrows if they were really determined to vote without looking at the responses.) Or use a PageRank-style or Advogato-style trust metric rather than raw vote count, so that the votes of people like the ones I listed above would count for more; or, like lobste.rs, request a reason for downvoting. Fundamentally, though, I think there's a kind of insuperable conflict between thoughtful discussion and hair-trigger interactivity. Long comments rarely get many votes, either up or down, because they take too long to read.
I'm really sick of seeing thoughtful comments like vkou's in https://news.ycombinator.com/item?id=20395050, mine in https://news.ycombinator.com/item?id=20276994, and eloff's in https://news.ycombinator.com/item?id=20275006 (although it was mistaken) punished by downvotes and even flags like we're filthy spammers.
Again, I really appreciate your thoughtful reply. Maybe we should set up an "Old Hats" mailing list or something for discussions like these. Maybe it's possible to rescue HN from the "finance-obsessed man-children and brogrammers" JWZ refers to.
I agree with you about some of those examples and have reset the score on them; we do that routinely when we see good comments unfairly downvoted. So do a lot of users, while the upvote window is still open. The corrective upvote is a standard practice here. And yes, that still leaves some good comments in negative space. Voting is a big messy statistical cloud. I don't think there's a way to make it precise. Maybe it's worth noting that the examples you cited are already months old? There have been about a million comments posted to HN since those. It's inevitable that a sample that large will contain some shitty outliers, i.e. really unjust cases. Comments tend to fluctuate up and down in score; some are going to end up in the red just stochastically. Perhaps we should be more open to experimenting with the voting system, but years of looking closely at that data has diminished my sense of what's possible. I think the two biggest factors are human nature and randomness, and we can't do much about either. There could still be better mechanisms for channeling them, though.
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.
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.
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.
All I ever wanted
All I ever needed
Is here
In ML
Loops are very
Unnecessary
map will work just as well
But the function you pass to map or filter is called once for every sequence element, and reduce is nothing more than a foreach loop with explicit dataflow. So, although I agree that these are often better than a for loop, they are not the same thing as making the looping implicit.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!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.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...
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.
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.
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.
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.
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.
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.
It seemed to me that was often more performant than using a loop.
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.
- 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?
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.
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.
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.
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.
> 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?
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"?
Also, please show me one successful software package (and by successful, I mean largely consumed by consumers and a product leader) that does not use looping techniques.
Like all technical decisions, they are made by using the most correct and appropriate choice. Which is why the industry has been using pre and post condition loops since the beginning. This "don't use loops" attitude is equivalent to trying to reinvent the wheel.
Except you clearly are. The only other reason stated in the original post seems to be "because it's similar to what the hardware does", which in my opinion is completely irrelevant - how does that in any way change how useful it is to program with?
> Also, please show me one successful software package (and by successful, I mean largely consumed by consumers and a product leader) that does not use looping techniques.
Must functional programming languages have no loop construct or using loops is heavily discouraged.
> Like all technical decisions, they are made by using the most correct and appropriate choice.
I don't believe that you're naive enough to think that one decision is correct in all scenarios, or that programming paradigms cannot evolve over time.
> Which is why the industry has been using pre and post condition loops since the beginning.
Congrats, we're back to "it's popular so it's good".
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.
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.
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.
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.
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.
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?
J is quite fast, shockingly so for an interpreted language.
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.
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.
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#f...
The Standard Library algorithms explicitly implement such a taxonomy.
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)
"(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?