lexyeevee

troublesome fox girl

hello i like to make video games and stuff and also have a good time on the computer. look @ my pinned for some of the video games and things. sometimes i am horny on @squishfox



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?


lexyeevee
@lexyeevee

robotic is the scripting language in megazeux, the greatest game engine ever made.

i have spoken at length about megazeux before and i will probably do it again, but here is the short version.

megazeux was somehow more obscure than its spiritual predecessor, zzt, but it's the one i landed on, and one of my longest-running regrets is never having quite made a game in it. shown here is the opening shot of caverns of zeux, the game that came with it. (you can play it in a web browser! science is amazing!) or, rather, caverns of zeux was "it", the game you were downloading, but it happened to be bundled with the engine and editor used to create it, and you could just go in and see how it was all put together.

the basic idea is that megazeux gives you a big box of standard gizmos to play with — boxes you can push around, turrets that shoot you, ice you slide on, and so forth — but you can also do absolutely whatever you want by creating a robot and programming it yourself. zzt let you do this, but megazeux let you do it better.

as an example, here is the code for an early robot in Caverns of Zeux, posing as a simple village dwarf:

cycle 10
: "st"
go RANDANY for 1
goto "st"
: "touch"
if "form" = 1 then "sc"
if "form" = 4 then "sc"
[ "Ah! Another traveller. Make sure the visit the healer if you"
[ "get hurt. You'll need some coins, though- look in the Ice"
[ "cavern. I've heard rumors of untold wealth within them!"
goto "st"
: "sc"
* "AAAAAA!!!!!"
end

this is a little terse, but the gist is that the guy will wander around at random (one step every 10 tics), and if you push against them (thus forcing them to their special state "touch"), they will either give some helpful direction, or scream because (spoilers) you are a dragon.

one of the clever things about megazeux is that its scripting is functionally an assembly language, but the language and the editor work together to make it a little more ergonomic. for example, take go RANDANY for 1 — this is an instruction of the form go DIR for NUM. however:

  • there are several special directions for common behaviors to avoid the need for expressions or intermediate variables. RANDANY will evaluate to one of the four cardinal directions at random.

  • the editor recognizes a number of propositions as optional. these are... like croutons. you can sprinkle them throughout a line and they don't affect parsing. as soon as you move to another line, the editor will "fix up" your line to have the canonical arrangement of prepositions. that's what the for is doing here; it doesn't actually do anything, but it helps a human reader understand what the number is for. you could type go into randany through 1 and get the same result.

  • likewise, there is canonical capitalization — directions will be converted to all caps for you.

  • there's syntax highlighting!! in this weird dos game editor from like 1994!!

one downside is that variables are... clumsy. they exist, but they are not exactly pretty. and this is not ideal when a very very very common thing a robot wants to do is remember its state.

but robots are always running, so a very common pattern is to simply use their instruction pointer as state. whatever they're currently doing, that's what their state is.

enter zap + restore.

these two instructions operate on labels. zap "foo" means to find the first active "foo" label and disable it. the secret sauce is that you can have duplicate labels, and any form of jump will take you to the first active one. (restore does the reverse, of course — it enables the last such inactive label.)

thus, a robot that gives a sequence of different responses to the same input is very easy:

end
: "touch"
* "Please don't touch me."
zap "touch"
end
: "touch"
* "I said, please don't touch me."
zap "touch"
end
: "touch"
* "Stop!"
zap "touch"
end
: "touch"
* "AARRGGHH!!"
explode 4

the current state is merely the set of enabled vs disabled labels. you can do the same thing (and in fact caverns of zeux does) to track, say, a boss's health, and have its behavior change when it goes below a certain level. or you could make this more elaborately complicated by having labels that start out disabled, so maybe then you restore everything later and now the sequence is different. all without ever needing to store or examine any data at all.

and that is my favorite form of flow control.


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 😛