6 ms·
I'll throw another idea here I've been thinking from a time now. Most languages have a while construct and a do-while. while(condition){block}; do{block}w
by TrianguloY 2y ago
I'll throw another idea here I've been thinking from a time now.
Most languages have a while construct and a do-while.
while(condition){block};
do{block}while(condition);
The while is run as
...
start:
condition
branch-if-false > end
block
branch-always > start
end:
...
And the do-while switches the order:
...
start:
block
condition
branch-if-true > start
...
The issue with the while is that more often than not you need to do some preparations before the condition. So you need to move that to a function, or duplicate it before and inside the loop. Do-while doesn't help, since with that you can't do anything after the condition.
The alternative is a while(true) with a condition in the middle.
while(true){
prepare;
if(!check) break;
process
}
But what if there was a language construct for this? Something like
do{prepare}while(condition){process}
Is there a language that implements this somehow? (I'm sure there is, but I know no one)
The best thing is that this construct can be optimized in assembly perfectly:
...
jump-always > start
after:
process
start:
prepare
condition
branch-if-true > after
...
- digging 2y agoI don't write a lot of while loops so this is just a bit unfamiliar to me, but I'm not really understanding how this isn't the same as `do{block}while(condition);`? Could you give a simple example of what kind of work `prepare` is doing?
- TrianguloY 2y agoThink of a producer (a method that returns data each time you request one, like reading a file line by line or extracting the top of a queue for example) that you need to parse and process until you find a special element that means "stop". Something like do{ raw_data=produce.get() data=parse(raw_data) }while(data!=STOP){ process(data) } I'm aware this example can be trivially replaced with a while(data=parse(producer.get())){process(data)} but you are forced to have a method, and if you need both the raw and parsed data at the same time, either you mix them into a wrapper or you need to somehow transfer two variables at the same time from the parse>condition>process A do-while here also has the same issue, but in this case after you check the condition you can't do any processing afterwards (unless you move the check and process into a single check_and_process method...which you can totally do but again the idea is to not require it)
- bandie91 2y agoi often want to write: do { offset = tell(filehandle) } while(line = readline(filehandle)) { print "line starts at offset: ", offset }
- jolmg 2y agoYou mean like a shell's while-do-done? It's just about allowing statements as the conditions, rather than just a single expression. Here's an example from a repl I wrote: repl_prompt="${repl_prompt-repl$ }" while printf "%s" "$repl_prompt" read -r line do eval "$line" done echo The `printf` is your `prepare`. This should also be doable in languages where statements are expressions, like Ruby, Lisp, etc. Here's a similar Ruby repl: while ( print "repl> " line = gets ) do result = eval line puts result.inspect end puts
- TrianguloY 2y agoExactly, here you are basically keeping it as a while with a condition but allowing it to be any code that at the end returns a boolean, although you need to make sure that variables defined in that block can be used in the do part. Sidenote: I wasn't aware that shell allows for multiple lines, good to know!
- url00 2y agoNot quite the same but almost feels like the BEGIN block in awk.
- brabel 2y agoI suspect you'll love Common Lisp's LOOP: https://gigamonkeys.com/book/loop-for-black-belts https://gigamonkeys.com/book/loop-for-black-belts Example: (loop repeat 8 ;; init x, then decide how to "prepare" next iteration for x = 0 then (+ (* 2 x) 1) collect x) (0 1 3 7 15 31 63 127) You can insert a condition check in the middle, of course: (loop repeat 8 for x = 0 then (+ (* 2 x) 1) ;; but stop if x gets too big while (< x 100) collect x) (0 1 3 7 15 31 63) And much, much more. It's the ultimate loop construct.
- gpderetta 2y agoLambda The Ultimate Loop!
- lispm 2y agoactually, according to the LOOP syntax, the REPEAT clause has to follow the FOR clause...
- sparkie 2y agoDon't forget about the `prog*` family. --- (prog1 foo bar*) Evaluates foo, then bar(s), and returns the result of evaluating foo and discards the results of bar(s). Useful if `foo` is the condition and you need to perform some change to it immediately after, eg: (while (prog1 (< next prev) (setq prev next)) ...) --- (prog2 foo bar baz*) Evaluates foo, then bar, then baz(s) (if present), returns the result of evaluating bar and discards the results of evaluating foo and baz(s). Might be what GP wants. `foo` is the preparation, `bar` is the condition`, and `baz` can be some post-condition mutation on the compared value. Not too dissimilar to for (pre, cond, post) {} With `prog2` you could achieve similar behavior with no built in `for`: (while (prog2 pre cond post) ...) --- (progn foo*) Evaluate each foo in order, return the result of evaluating the last element of foo and discard all the others. `progn` is similar to repeated uses of the comma operator in C, which GP has possibly overlooked as one solution. while (prepare, condition) { process }
- tajpulo 2y agoI think we have a similar way of thinking. I once wrote a blog post about a for loop extension (based on Golang for illustration) [0]. values := []string{"hello", "world"} for v, value := range values { fmt.Printf("%s", value); } inter { fmt.Printf(",") } before { fmt.Printf("[") } after { fmt.Println("]") } empty { fmt.Println("(nothing found)") } [0] https://lukas-prokop.at/articles/2024-04-24-for-loop-extension https://lukas-prokop.at/articles/2024-04-24-for-loop-extensi...
- cdhdc 2y agoEiffel has the loop structure. from <initialization statements> until <termination condition> loop <group of statements> end
- teo_zero 2y agoIn some way it's the dual of break, in that you want to jump into the middle of the loop, while break is to jump out of it. Let's rewrite the loop this way, with 'break' expanded to 'goto': while (true) { prepare... if (!cond) goto exitpoint; process... } exitpoint: The dual would be: goto entrypoint; do { process... entrypoint: prepare... } while(cond); Both constructs need two points: where the jump begins and where it lands. The 'break' is syntactic sugar that removes the need to specify the label 'exitpoint'. In fact with 'break' the starting point is explicit, it's where the 'break' is, and the landing point is implicit, after the closing '}'. If we want to add the same kind of syntactic sugar for the jump-in case, the landing point must be explicit (no way for the compiler to guess it), so the only one we can make implicit is the starting point, that is where the 'do' is. So we need: a new statement, let's call it 'entry', that is the dual of 'break' and a new semantic of 'do' to not start the loop at the opening '{' but at 'entry'. do { process... entry; prepare... } while (cond); Is it more readable than today's syntax? I don't know...
- scotty79 2y agoWhen we are on subject of loops... I'd love to have 'else' block for loops that runs when the loop had zero iterations.
- fuzztester 2y agoNot the same thing (although I thought it was), according to the Python docs, but related: https://docs.python.org/3/reference/compound_stmts.html https://docs.python.org/3/reference/compound_stmts.html See sections 8.3, the for statement, and 8.2, the while statement.
- scotty79 2y agoYeah, `while...else` in Python does the wrong thing. Executes `else` block when the loop finished normally (not through `break`). Scala for example has a `breakable {}` block that lets you indicate where you should land after a `break` breakable { while condition { // loop body if otherCondition then break; // rest of the body } // body of pythonic else block } // you land here if you break However I have no idea how to implement the kind of `else` I described in any language without checking the condition twice.
- scotty79 2y agoIn Scala you can do: while { prepare; condition } do { process } This runs all 3 in order every iteration but quits if condition evaluates to false. It just uses the fact that value of a block is the value of the last expression in the block. Scala has a lot of syntax goodies although some stuff is exotic. For example to have a 'break' you need to import it and indicate where from exactly you want to break out of.
- int_19h 2y agoC-style for-loop is kinda sorta this. Although the "prepare" part has to be an expression rather than a statement, given that you have the comma operator and ?: you can do a lot there even in C. In C++, you can always stick a statement in expression context by using a lambda. So: for ([]{ /*prepare*/ }(); /*condition*/;) { /*body*/ } However, the most interesting take on loops that I've seen is in Sather, where they are implemented on top of what are, essentially, coroutines, with some special facilities that make it possible to exactly replicate the semantics of the usual `while`, `break` etc in this way: https://www.gnu.org/software/sather/docs-1.2/tutorial/iterators.html https://www.gnu.org/software/sather/docs-1.2/tutorial/iterat...
- Mikhail_Edoshin 2y agoThe C language construct for that is 'goto'.
- coldtea 2y agoYeah, but the parent wants a non brain-damaged too-general construct
- Mikhail_Edoshin 2y agoWhat if loops are a design mistake? Look at C: it has 5 loop-related keywords (4.5; 'break' is two-timing in 'switch') yet it is still not enough.
- drpixie 2y agoAda has had something similar and very flexible since from the 80s ... like: loop Get(Current_Character); exit when Current_Character = '*'; Echo(Current_Character); end loop; There's not that much new under the prog lang sun :(
- vrighter 2y agodo { Get(Current_Character); if (Current_Character == '*') break; print(Current_Character); } while (true); I don't see why this needs a new construct in languages that don't already have it. It's just syntactic sugar that doesn't actually save any work. The one with the specialized construct isn't really any shorter and looks pretty much the same. Both have exactly one line in the middle denoting the split. And both lines look really similar anyway.
- jiggawatts 2y agoPowerShell can process 0..n input objects from the pipeline using BEGIN {...} PROCESS {...} END {...} blocks. I find this so incredibly useful, that I miss it from other languages. Something related that I've noticed with OO languages such as Java is that it tends to result in "ceremony" getting repeated n-times for processing n objects. a well-designed begin-process-end syntax for function calls over iterables would be amazing. This could apply to DB connection creation, security access checks, logging, etc...
- Joker_vD 2y agoWell, I am in a process of making a language where general loops will look like loop prepare; while check; process; end; I also think you'd enjoy Knuth's article "Structured Programming with go to Statements" [0]. It's the article that gave us the "premature optimization is the root of all evil" quote but it's probably the least interesting part of it. Go read it, it has a several sections that discuss looping constructs and possible ways to express it. [0] https://pic.plover.com/knuth-GOTO.pdf https://pic.plover.com/knuth-GOTO.pdf
- klibertp 2y agoRaku can do this with Phasers: https://docs.raku.org/language/phasers https://docs.raku.org/language/phasers and last/next/redo: https://docs.raku.org/language/phasers https://docs.raku.org/language/phasers Though, you still need all that to be nested inside the loop (a closure, more generally.)
- smitty1e 2y agoAt what point are you just doing async and coroutines?
- do_not_redeem 2y agoInteresting idea, but once you add scoping: do { let value = prepare(); } while (value.is_valid) { process(value); } Can the second block of the do-while see `value` in its lexical scope? If yes, you have this weird double brace scope thing. And if no, most non-trivial uses will be forced to fall back to `if (...) break;` anyway, and that's already clear enough imo.
- PaulDavisThe1st 2y agoLet me FTFY: do { let value = prepare(); while (value.is_valid) { process(value); }
- coldtea 2y ago>Can the second block of the do-while see `value` in its lexical scope? If yes, you have this weird double brace scope thing As long as it's documented and expected, it's not weird. The scope then is the whole "do-while" statement, not the brace.
- TrianguloY 2y agoThe scope should be unique, yes. In your example value should be visible. Your are right about the word double braces, but I can't think of an alternate syntax other than just removing the braces around the while. But in that case it may seem odd to have a keyword that can only be used inside a specific block...wich is basically a macro for a if(.)break; Maybe I'm too used to the c/java syntax, maybe with a different way of defining blocks?
- deleted 2y ago[deleted]
- adamc 2y agoThat seems more like a programmer expectations issue than something fundamental. Essentially, you have "do (call some function that returns a chunk of state) while (predicate that evaluates the state) ..." Hard to express without primitives to indicate that, maybe.