This seems very complicated, and confusing. Unless your code is heavily math-oriented, it doesn't seem like a win. Even in that case, you would lose the ability to easily move and factor subexpressions, one of the things I prize most in Lisp.
I also foresee problems with code that looks like infix but doesn't actually have the operator precedence rules of infix, especially with the interaction between (1) and (2). We really don't need to lose more spaceships. ;)
Ha! That's fantastic. Nice to see you're still around here, too. I've been taking a break from Arc for a while (job search, recently completed). I'm still working on something related to our little LOOP tiff a while back. Just you wait, it's gonna be great! :)
I think the logic is to make if a "primitive macro," the way + is a primitive function. Just as you can't define + in terms of Arc functions, you can't define if in terms of Arc macros. Nevertheless, (isa + 'fn), and so the thinking is that (isa if 'mac) makes sense.
There are valid reasons not to do things this way, of course. For instance, what's so powerful about macros is that they literally expand into a code tree, which (as you observed) if can't do. For that reason, why not have (isa if 'form) (or 'prim, or 'special, or something similarly descriptive)? Then you have first-class special forms without literally making them macros. This would be a big improvement over the current state of affairs:
arc> (type if)
Error: "reference to undefined identifier: _if"
If can be implemented as a primitive function instead of a special form given the fn operator. It could take three arguments: a condition, a then function, and an else function and then call the appropriate function. An example implementation is:
(def if2 (cond then else)
(if cond (then) (else)))
If2 would be given as a primitive function and its implementation would be hidden like that of cons is. Given access to if2, a basic if construct is:
(mac basic-if (cond then (o else))
`(if2 ,cond (fn () ,then) (fn () ,else)))
This would make code walkers easier because they wouldn't have to know about as many special forms.
See my point? Even in this incredibly simple iterative task you are forced into a clever trick leveraging how append works and then totally artificially taking each thing you want to collect and wrapping it in a list of one.
Looked at another way, you are not really going out and getting lists of variable length and then appending them, you are just using this trick to avoid collecting nils.
In this case the issue is not efficiency, it is that the loop DSL provides a more natural way for the developer to express themself. Now scale this to a loop that does three things at once, perhaps partitioning a list into several while counting or summing something else and the non-loop version explodes in complexity exponentially while the loop version gracefully grows linearally. (Say that three times fast.)
I got a little carried away with being clever in that version. But look at the canonical Arc version:
(each y whatever
(awhen (pfft y) (collect (yo-mama (cons y it))))))
This version is pretty straightforward in expressing my intent.
As for the scaling issue, I'm still thinking about your other example. I'm not sure it's a good thing that Loop allows more and more to be tacked on. Subroutines in imperative-style languages like C++ have the same agglutinative property, and we're all familiar with the results of that.
Where on earth in your intent was reversal? Either the semantic or the run-time cost (ie, now you have introduced an efficiency issue that was not there with mappend/list.
You cannot win this fight, find a white flag, run it up. Why can you not win? Because loop the DSL was written with the most common iterative design patterns in mind, and hard-coded to make them both more succinct, more efficient, and to play well with other iterative patterns we occasionally want to run merged as one iteration.
This is what DSLs are for! Read On Lisp. We build the language up to our requirements. Loop is about iteration, and Lisp stands for list-processing. 2+2 left as an exercise. :)
That reminds me, I ended up reinventing Cells over the table in Arc because the real deal was so big it would have been a heckuva project, but I started on the actual code and... whoa! I have one chunk I found easiest to express as a very simple state machine using Common Lisp's tagbody/go and I had very little confidence in my conversion to a functional solution.
New contribution?! That was a joke, a painful transliteration of a couple of pages of graph paper formulas and diagrams working out the parameterized construction of a 3-dimensional button out of insanely small OpenGL atoms, including the torturous calculation of normals to support lighting.
I steered this thread to the Common Lisp Hyperspec entry on loop. By reading that and examining your own use of Arc iterators you can deduce how it can express them all more briefly and with fewer parens. That immediately helps the hacker over one bump... when it is time to iterate I type "(loop " without thinking and just take it from there -- no worrying about whether it is a sequence, list, or hash table, or whether I will need temp variable to be defined in a let/with statement, loop includes a mechanism for that, etc etc etc...
I think you misunderstand. The "weird" to which I refer is precisely pg's anti-(cl)-loopism. He should love loop not just for the brevity, but also because it is a triumph of DSL. ie, CL loop is a DSL for iteration, and I know because I use it for everything. But! pg seems closer to being a schemer at heart so maybe he prefers recursion for things I handle by iteration.
ps. Are you saying BASIC eliminates parentheses the same way LOOP does? :)
Well I resisted Loop for almost ten years then broke down and learned it when PCL came out cuz it had a good chapter on it, so having made the transition I can assure you it is a powerful little iteration language, not just a simple iterator of which CL has many so it is not clear what you do not like about those. I'll ignore your continued insistence that BASIC has anything to do with this discussion. :)
Btw, loop offers a little-known second syntax that is Lispy, one just never sees examples in the wild.
If you have found through long experience that Loop truly offers something unique that can't be offered as well (or as intelligibly) by a combination of simpler operators, then I'll have to take your opinion seriously.
I've made a thread to collect exemplary examples of Loop in action (http://arclanguage.org/item?id=2938) and it'd be great if you could contribute your favorites, e.g. from Cells or other code you've written.
Personally, I don't have the same experience as you. I've found Loop to be useful as a replacement for iteration constructs that should have been in CL to begin with, e.g. dovector, when I'm too lazy to code the requisite macro. I haven't ever had to use Loop in its full complexity.
Sorry, it just sounds from what you have posted so far that you do not know CL's loop, by which I mean make an effort to use all of its capabilities such that you would know what's there so well that you had it at your fingertips. I am having the same problem with CL idiots denouncing Arc who probably have not even installed it.
You want me to code (dotimes (x 10) (foo))?! No way!
(loop for y in whatever
for py = (pfft y)
when py collect (yo-mama (cons y py)))
Is the first version starting to look like a disassembly? Bingo!!! :)
btw, the thing that got me to break down and give loop a chance was learning that the expansion was highly optimized code, meaning (for example) it would not first map across whatever and then delete the nils.
but these examples still do not get to the point of LOOP being a DSL. That property emerges only in the next level of application, as even the loop form expands to ten lines. But once you have loop under your belt (I kill myself) you do not even want to code a simple dotimes, that bogus unused count variable just pisses you off no end, never mind all the extra (wait for it) parentheses!
That means (+ . (1 2 3)) does what you want, doesn't it?
Granted, to do the equivalent of (+ @(foo)) you would have to do;
(let temp (foo)
(+ . temp))
Which is not very nice at all, so I'm not arguing against @. On the contrary.
I used to think that . and @ would only differ in the sense that cons and splice have different list building semantics, but now I believe that they should also differ in the timing of the operation;
(+ . (foo)) -> (+ foo)
(+ . '(foo)) -> (+ quote foo)
(+ @(foo)) -> what we want, given that foo returns a list
(+ @'(foo)) -> (+ 'foo)