Could we have a do while loop in Julia?

I agree, underneath they are the same, but the first one is easier to read.

This is an argument against do-while staying in a package forever, not an argument against making the prototype implementation in a package. It’s a lot easier to gather inputs and build consensus in a self-contained package versus filing a pull request against the language directly.

This is how many language features got started, e.g., views, iterators, etc.

1 Like

Common Lisp and Rust (I don’t know more examples, but I’m sure they are) have a loop loop which basically just looping until you break it. I think such thing can be very reasonable replacement of while true.

As I whole I very like Common Lisp control flow with it when, until, dotimes, dolist, loop. But, since Lisp philosophy is very peculiar and in this language hand written while loop construct looks no different than the “built-in”, its construction are not for everyone and not for every language. Regardless of that, maybe it can give some good inspiration what to do?

Also in Lisp distinction between “hand-written” and “bulit-in” is in most cases even more blur that in Julia, but these is different topic.

PS. I know that Common Lisp loop is something like separately language inside Common Lisp and I don’t know 1/1000 of it power. I just mean that at minimum it loop endless and is much more convenient that mighty do loop for this task.

3 Likes

Ada is another example of a language that has a loop statement without mandatory condition, as in

loop
   Get(Current_Character);
   exit when Current_Character = '*';
end loop;

https://www.adaic.org/resources/add_content/standards/05aarm/html/AA-5-5.html

4 Likes

Is this

while true
        n1 = read(f, Int32)
        totbytes -= 4
        if n1 == 0 
              break
        else
              skip(f, n1)
              totbytes -= n1
    end

any different from

while true
        n1 = read(f, Int32)
        totbytes -= 4
        n1 == 0 && break
        skip(f, n1)
        totbytes -= n1
    end

? If there is no performance difference, in terms of code legibility, I prefer the if else block clearly segmenting the conditionally executed code. But, when in doubt, I tend to be more verbose then less.

I also prefer knowing the exit condition is easily accessible in the while line. I see no problem with while true though, since this tells me to look for a break.

In Common Lisp, there’s a “go” special form that is the only fundamental control flow used for building loops, all the iteration constructs such as do, do*, dotimes, dolist, loop etc are all macros.

The point is, macros are first class citizens in Common Lisp, some macros are actually specified as required in the spec, one of the exciting bits about Julia is that it takes this seriously as well.

I can spend whole day listening and talking about Lisp and it main dialects, even if my knowledge is very limited here. But this is Julia Discourse, so this is too much off-topic. If you want, we can discuss it elsewhere.

I just want to suggest, that Common Lisp control flow solutions can be good reference point. Rust also do it very good.

Never mind

do
    body
while cond

What about

repeat
    body
until cond

Which can be rewrite as

do
    body
while !cond

If julia did tail call elimination we’d have an elegant way of expressing all these things without macros or any extra syntax. Aside from mutating state outside of your loop body being more explicit.

Edit: checked what LLVM has to say about this. Looks like a pretty difficult thing to implement for all platforms.

I do like the option of having a do-while or repeat-until because they quickly tell me the intention of the writer. It means it’s a loop that will always execute at least once and will verify the condition by the end. A while true only tells me that the loop will break at some condition at some point (which could be pretty much anywhere until I investigate the entire loop) or not (if it’s intended to really be an infinite loop). In the same way there is both a while and a for when both can be implemented with the other.

I could extend that to pattern matching, a switch for example isn’t much different from if/else but it immediately tells me that all comparisons will be of equality. And pattern matching tells me that it will evaluate all the possibilities that the input can have (which is great for macros, handling complex function outputs and exceptions, all quite common in Julia). And while there are great implementations in packages like Match.jl, package developers in general will probably try to avoid adding dependencies for internal syntax sugar even if it could make the code more readable and concise.

That said having all the nice constructs of every language in one language will end up with the opposite effect, and now you’d have to think which of the half dozen options you’ll use for anything you want to do. And it will also make the work more complex for the language developers. I think a good compromise is keep the base language spec simple, but, like people mentioned, have some second tier constructs using the macro system (like Parameters.jl and Base.@kwdef) in some module already available to import (like an AdvancedControlFlow module) so people don’t need to either reimplement those structures or force more dependencies on the user when creating packages, while making clear which are the first tier options you should consider first.

Why is that? What’s wrong with adding one more dependency? How hard is that for the developers and for the user? AFAICT it’s pretty simple for the developer to add and automatic for the user, especially for simply macros like this that are unlikely to break, unlike big packages.

Your argument applies to basically everything. Following what you said, every popular packages should be moved into base. However, that completely defeat the purpose of packages. Things that doesn’t have to be in Base shouldn’t. OTOH, there’s nothing wrong with stdlib though. If you make a macro in a package that’s working well and is popular enough, there’s nothing wrong with including it the standard library.

4 Likes

Although it’s a slippery slope argument (having a somewhat basic and functionally stable control flow in the base library definitely doesn’t mean everything should be as well, and you could also have it the other way and remove everything but the very base), It’s true that I might have a pointless aversion to adding libraries for just a few lines of code or when I could easily do the same but a little less readable and concise. I wouldn’t know if others would feel the same. As a side argument, it does help in the discoverability of these features, but that could also be solved in other ways with more documentation.

That is still an argument for stdlib, not base.

Sorry, I meant the stdlib all along when I said a module like AdvancedControlFlow.@repeat/@match, Base.@kwdef was probably a bad example for this and I misunderstood your response.

OTOH, I don’t see the advantage of putting something in stdlib either. When

using Foo: @bar

the user should not care if Foo is a standard library or not. If Foo is registered, it should work seamlessly.

At the same time, the disadvantages are numerous. Currently standard libraries are tied to the release cycle of Julia, with all the friction that entails.

2 Likes

Only if he did install Foo himself. As you know, this is precisely the difference between a standard library and an arbitrary package - apart from the (“temporary”) update limitation of stdlibs you mentioned.

This is a difference between standard libraries and other packages, but I would argue is not the most important one. Standard libraries have

  1. API stability guarantees in sync with releases (you cannot make a breaking change without bumping a major Julia version),
  2. a high level of test integration,
  3. implicitly very high maintenance standards,
  4. a synced release cycle with base,
  5. come preinstalled with Julia.

While people usually focus on 5, I think it is the most trivial.

Of course most users don’t realize the other constraints until they contribute to a standard library. In particular, 4 makes standard libraries move quite slowly, can hold up releases, and increases CI time and maintenance burden significantly. The “make everything a standard library” approach would have exorbitant costs and drive Julia development to a standstill, which is why it is resisted.

7 Likes

Let me just say that I don’t think that your points 1-3 are in any way special to standard libraries (point 1 seems to basically be semantic versioning + point 4) - don’t they apply to, say, DifferentialEquations.jl as well? These just seem to be properties of any serious package of the ecosystem. And, yes, stdlibs are currently tight to the base release cycle (point 4) but, AFAIK, the plan is to change this. Hence, in my eyes, point 5 is eventually the only solid difference between a stdlib and a package.

In any case, I agree that we should be conservative about putting things into stdlibs. Let’s not derail this thread :slight_smile:

I think the biggest problem is that only members of JuliaLang can do the maintenance on code in StdLib: sure others can make PRs but someone needs to merge it. I’ve had relatively straightforward PRs that pass the tests sit for months without even comments, clearly JuliaLang is already stretched thin and they won’t rush to sign up for more work.

I expect the chance of there ever being something like AdvancedControlFlow.jl in StdLib to be roughly zero.

This applies to most (non-stdlib) packages, too.

I think it is OK to do a friendly bump after a while.