Thank you for starting this thread Owen. Before brain dumping some thoughts, let me take stock of the current situation.
What actually is ‘@parameter for`?
The current @parameter for structure. Isn’t a “compile time for loop”, in fact, it isn’t a loop at all! While this construct syntactically looks like a loop, it actually fully unrolls the iteration space given to it. This has a number of benefits (including reducing loop overhead) and a number of drawbacks (spamming out a huge amount of codesize as you point out), but the most material thing it does is that it makes the induction variable a parameter:
@parameter
for i in range(4):
# Can use i as a parameter.
... SIMD[dt, 1 << i] ...
# parameters materialize to runtime values of course:
print(i)
This behavior is only achievable if you fully unroll a loop, which is what this construct does. If you don’t fully unroll, the induction variable would have to be a runtime construct.
What should this actually be named?
Given that this isn’t actually a loop at all, calling this thing a “for loop” seems pretty dangerous and misleading, which is one of the reasons this gets abused. IMO, you could go so far as to say that the current name is abhorent! We need to rename this, and I’d propose something explicit and clear, e.g. a strawman would be:
comptime_fully_unroll i in range(4):
# Can use i as a parameter.
... SIMD[dt, 1 << i] ...
Doing a rename like this would make it far more clear what the behavior is, and would reduce a certain amount of abuse where people use it for general “unroll for performance” - which isn’t its intended use-case. People use it for this purpose because it is misleadingly named, but also because we don’t have a collection of better alternatives they could choose from to solve their problem.
How does this fit in with library-based design?
It turns out that Mojo is a language that wants to push things from the language into the standard library. Indeed we used to have a function named unroll (as a peer to vectorize) that did partial unrolling of iteration spaces. Even the existing @parameter for approach is also expressible in the standard library as well, today, e.g:
fn body[i: Int]():
# Can use i as a parameter.
... SIMD[dt, 1 << i] ...
comptime_fully_unroll[body](range(4))
The primary downside is syntactic sugar: the existing statement supports destructuring, break statements, and supports putting the body “where it is supposed to go” without the burden of defining a nested function. The current syntax is substantially more ergonomic than a library-based solution. We could remove this from the langauge and go with a library-only solution today, but doing so would be a pretty bad regression for commonly-used functionality.
That said, at the same time, I don’t support adding a ton of bells and whistles to this thing: It is not at all about runtime partial unrolling - such a thing would have to get its own new name and design (eg the induction variables would be runtime).
Further, even though we already hard coded this case into the language, I’m also not excited about expanding the language with other special cases. There are a LOT of loop transformations beyond unrolling that are useful (e.g. vectorize, parallel for loops, unrolled loops, etc etc etc) and putting these all into the language isn’t appealing to me.
A possible future direction
The right direction IMO is to expand the ability to model these capabilities in the library, and aspire for comptime_fully_unroll to become a library feature some day (which is perhaps auto imported). I can imagine a few steps towards this:
- Once the closure work settles, we will introduce lambda syntax making things slightly more ergonomic.
- Beyond control flow statements, higher order functions are a thing in general, and while we need to support python-style lambda syntax, it is quite onerous for simple things. I think it would make sense to explore Scala-style
=> syntax sugar at some point.
- Similarly, we can explore allowing higher order functions to have their body written as a nested block, but treat such a nested block as an implicit closure argument. Swift did something conceptually similar with it’s “trailing closure” syntax: Trailing closure syntax - a free Hacking with Swift tutorial
- One problem with control flow statements as functions is that break/continue/return don’t work the right way. Solving this is work, but not conceptually difficult, it would be a new closure effect.
- Getting pattern binding to work will require something more fancy. I think we will eventually want to explore a macro system or some other way to enable this.
This is a long path and not all of it is likely to happen for Mojo 1.0. As such, I’d advocate for this approach:
-
resyntax the existing @parameter if and @parameter for structures into something that could be subsumed in the future by a macro. This is why I think a new name with clear semantics that looks like a statement(eg comptime_fully_unroll ) is the right way to go.
-
Hold back on adding more stuff, instead, implementing these things as algorithms in libraries.
-
Continue to build out the core language features that enable these sorts of things to be defined in libraries and used ergonomicly despite that. Ideally eventually allowing this to revert back to being a standard library feature. Perhaps if we’re good enough, we can also get with and for and maybe even if and try to also become library features. 
In any case, these are all just my personal thoughts, not speaking on behalf of the Mojo team. The Mojo team definitely has consensus that we need to rename @parameter if and @parameter for before Mojo 1.0 though.
-Chris