NireBryce

reality is the battlefield

the first line goes in Cohost embeds

🐥 I am not embroiled in any legal battle
🐦 other than battles that are legal 🎮

I speak to the universe and it speaks back, in it's own way.

mastodon

email: contact at breadthcharge dot net

I live on the northeast coast of the US.

'non-functional programmer'. 'far left'.

conceptual midwife.

https://cohost.org/NireBryce/post/4929459-here-s-my-five-minut

If you can see the "show contact info" dropdown below, I follow you. If you want me to, ask and I'll think about it.


JoshJers
@JoshJers

The question here is "what's your favorite non-C-like flow control statement/construct that you've seen in a programming language" but I'm gonna start with a little preamble.


I'm very familiar with C-style flow control:

if (condition)
  { thing; }
else 
  { otherThing; }


// And, thanks to C++17:
if (auto v= initializer(); condition) 
  { thing(v); }
else 
  { otherThing(v); }
for (int i = 0; i < 5; i++)
  { thing(); }
for (var e : elements)
  { thing(e); }
while (condition)
  { thing(); }

(plus, ya know, do/while and goto)

I've seen a few others, like Rust's loop (basically a while(true)):

loop
{ 
  thing(); 
  if (something) 
    { break; }
}

and Swift's ranges, which are nice:

for i in 0..<4
  { thing(i); }

What I'm wondering is: what are your favorite bits of flow control logic (loops, jumps, conditionals, etc) that don't show up in C or C++? Are there loop forms that you think are truly elegant? Is there a switch-like construct that doesn't suck like C's switch statement does?


NireBryce
@NireBryce

choosing the chaotic option and offering up for-else

for num in range(10):
  print(x)
else:
  print("I only go off if loop completed with no break statement")

millenomi
@millenomi

I love Swift's guard:

guard let index,
      index >= 0 else {
    throw Issues.noIndexProvided
}

If you don't use Swift, the else block must exit the current scope (with a return, throw, break or continue), so subsequent statements will always see any variables captured/deconstructured by the guard. In that way, you can signal an early break or return very prominently without silly stuff like 'no early returns, so enjoy your pyramid of if indents'.


You must log in to comment.

in reply to @JoshJers's post:

OCaml's try-with expressions for exception handling are quite nice, mostly because they are, well, expressions and not statements. This means you can return a value from a try-with expression and even put it inside a different expression. Unlike, say, Java's try-catch, this means that handling exceptions does not need to completely mess up the flow of your function!

It's also a nice bonus that you can use these as exception patterns in a regular match expression like this

let value = match arr.(42) with
    | exception Not_Found -> "nope" 
    | 0 -> "zero"
    | x -> string_of_int x

This can be much more convenient sometimes

The Common Lisp equivalent of a for loop, DO, lets you define multiple loop variables with init/step forms instead of just 1 like in C. As a result, it's somewhat common to have empty loop bodies and the whole loop logic is in the variable update.

Fun fact about Rust loops: they are expressions and can return a value. If you use break with an argument, that's the return value.

i like to eliminate explicit loops by using map or each in janet-lang which applies a function over a provided sequence: (map collects results in an array, each is mostly used for evoking side-effects)

(map inc [1 2 3 4 5]) => @[2 3 4 5 6]

(each inc [1 2 3 4 5]) => nil

because the iteration isnt really the important bit here, it's the operation applied that matters...

oh shoot, forgot cond and case:

cond lets you evaluate an expression, its like a list of else-ifs and case is basically a switch except with the usual dynamic typing going on

https://janetdocs.com/cond

https://janetdocs.com/case

Two suggestions:

  1. Is it possible in your language to reduce the number of control loop keywords? E.g., I think that what go does with for to handle all the looping is kind of cute.
  2. I find python for...else and while...else statements more useful than I thought I would when I first encountered them. This is just a loop with an else tagged onto it: the else happens if the loop exits normally by its condition becoming false, and doesn't happen if the loop exits because of a break or return. Incredibly useful when you're writing a loop to find something, want to break when you find it, and want to run some code when you didn't find what you were looking for.

And another related idea from go that rust also has that isn't really new control-flow constructs: ditch the parentheses around the condition, but require the braces around the loop body.

I'm of two minds of reducing the number of loop keywords: on one hand, less ways of doing things is usually better (see C/go for loops which can be constructed to be basically anything), but on the other hand there's something to be said for having a few extra keywords/constructs to more clearly define your intent ("while" loops have a very specific purpose vs. Rust's "loop" which is more freeform, vs "for" or "foreach" which generally are used to iterate over ranges).

I think the right answer is "use exactly as many as needed to make it easy and clear to describe the intent without the possibility of a typo or missed statement turning into a totally different style of loop" - whatever that ends up being lol

for...else is neat (not having to have a "wasFound" variable or equivalent is nice), although I definitely would have picked a different way to write it than "else" because if you gave me 2 guesses as to what for...else does I would have guessed two completely different things - but ignoring the syntax, it's definitely useful!

And re: ditch parens/require braces, that's exactly what I've been doing - so it's good to know that there are a couple of real, good languages that made the same choice!

The way I think of the "else" bit in for...else is that in your loop you're going to guard the break or return statements with some sort of conditional, so you have a loop like this:

for x in thingy:
    if is_acceptable(x):
        print(f"Found {x}")
        break
else:
    print("Didn't find it")

So implicitly, the "if" statements in the loop after the first iteration are a little bit like "elif" statements, since you only consider an "if" statement the second time through the loop if you didn't break out of the loop in the first iteration.

And the else that's after the for is like an "ultimate else" that's the else after all those "if" statements.

Was going to say the same. We use loops in C for more than we should. Comprehensions in Python are fantastic. I think it's very hard to add these to C++ because there's very little agreement on what a range / list / set is though. So I think we're out of luck on seeing it before C++50 ... :(

Of all the languages I have used, Ruby is the one that seems to have thought in most depth about usability for programmers. It's the only one where I think "ideally I'd like to be able to do... this" and that thing works. The other great loop variant it offers is do ..... while ( condition ) for those situations when you know you want to run the code at least once and maybe loop.

Yeah I'll admit ruby's |variable| thing is bizarre to me (I don't love the disconnect between the loop variable and the thing generating the variables) but being able to just say "do a thing 5 times" is really nice - it's a very clear way to express intent, which is crucial for code readability/maintainability

Once upon a time I read an old Scheme greybeard say words to the effect that if a language does not have cond, it is incomplete, and while normally I'd balk at such absolutism ... every time I have to use a language without cond, I feel like he might've been right.

Labelled breaks. Otherwise known as "goto that doesn't trigger people". It's a goto where the target can only be the end of a scope you're already inside, e.g.

for (...)
{
  for (...)
  {
    while (...)
    {
      if (...)
      {
        break we_outta_here;
      }  
    }
  }
  we_outta_here:
}

Yeah I think Rust has labeled loops, which is neat.

goto is great, I use it more than most programmers think I probably should (not frequently but more than 0), but there's definitely something to be said for "a goto that is limited to only allow a specific kind of movement" which, now that I think about it, is all any code flow construct is anyway.

One I've used a lot is Elixir's with operation

with pattern_match <- operation do
  # ...ops
else
  negative_match_1 -> 
    # ...other ops
  negative_match_2 ->
    # ...other other ops
end

If the positive condition matches a pattern (or patterns, comma separated in the with section) from the output of a function or pattern match, then run in the do block, otherwise, try and match on anything that exists in the else block and run code there.

Surprised that nobody has mentioned call-with-current-continuation in Scheme yet. It’s the mother of all control flow structures. You can use it to implement exceptions, early return, suspendible coroutines… it’s one of the reasons Scheme is so delightfully minimal (perhaps even pathologically minimal). Haskell also provides them, as they fall out fairly easily when you have lazy semantics, but the docs warn that “abuse of continuations can produce code that is impossible to understand or maintain,” which is in my experience totally valid.

My personal favorite is a concatenative-style while. It comes in many flavors, but the important part is that unlike C-style while () {} or do {} while () you can have logic both before and after the predicate check. This is similar in spirit to break, but still only allows a single breakpoint in the loop which helps visibility, and actually allows rewriting many break usecases without the problematic keyword. No matter if its Factor's [ pred ] [ body ] while or Forth's BEGIN pred WHILE body REPEAT or even Quackery's [ pred while body again ].

TIL'd about catch when in C# and think it's very nice! Snippet from docs:

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Console.WriteLine(MakeRequest().Result);
    }

    public static async Task<string> MakeRequest()
    {
        var client = new HttpClient();
        var streamTask = client.GetStringAsync("https://localHost:10000");
        try
        {
            var responseText = await streamTask;
            return responseText;
        }
        catch (HttpRequestException e) when (e.Message.Contains("301"))
        {
            return "Site Moved";
        }
        catch (HttpRequestException e) when (e.Message.Contains("404"))
        {
            return "Page Not Found";
        }
        catch (HttpRequestException e)
        {
            return e.Message;
        }
    }
}

Prolly not my favourite though. That'd be the else statement on Python loops that others have mentioned.

Oh that's cool I actually didn't know C# had that! I've only recently picked it back up and it's changed a ton over the last ... well, a while. Last time I used it I think I was working in C#3 and now it's on 11! (if you haven't checked out the raw string literals that they just added, those are also super nice and solve a lot of problems I've had with other languages' implementations of them)

It's cool to see them continuously improving the language! Although at the same time I get the feeling that all languages are gradually converging 😂

Unfortunately I'm limited to C# 9 because that's what the Unity game engine uses. I tend to avert my eyes from the newer features so I don't get too sad 😛

in reply to @millenomi's post: