Stream: ideas

Topic: loop expressions


view this post on Zulip Kiryl Dziamura (Mar 03 2025 at 18:01):

From time to time I think about vars and loops in roc. One thing is not really clear to me (but I haven't thought about it thoroughly), is why for and other loop constructs can't be expressions in the same manner as if/else and match are. Partial mutability of var x_ seems reasonable, but can't similar experience be achieved via immutable syntax? In particular, can we have an early return and readable control flow that reminds .walk, folds, reduce etc.

The idea is to add an initial block to loop constructs. The looping part would always return a value. It probably may be considered as list comprehensions:

fib = |n| {
    for _ in range(0, n) { // initialization block starts here
        a = 0
        b = 1
    ; // initialization block ends here
        (b, a + b)
    }.0 // for loop returned a value, use it
}

Removing some lines, we can keep only the first iteration, which is how the syntax may be read: you write a block and then repeat part of it

fib_once = || {
    {
        a = 0
        b = 1
        (b, a + b)
    }.0
}

The advantage of this syntax is ability to use early return in the function. Or use other loop control flow keywords:

for path in nonSrcFiles.toIter() from {
    files = Dict.empty()
    skipped = Set.empty()
;
    fullPath = Path.join(basePath, path)

    if File.isExecutable!(fullPath) {
        sha = hash!(fullPath)
        continue (files.insert(path, sha), skipped) // continue the loop with the following result. if it's the last iteration - the `for` expression returns it. so `break` and `continue` keywords are the same for loops as `return` for functions
    }

    (files, skipped.insert(path)) // otherwise, fallback here
}->Ok

It's a pretty rough idea. Will be be happy to hear your thought on this.

view this post on Zulip Brendan Hansknecht (Mar 03 2025 at 18:07):

This feels like it is missing a linking from the end tuple back to the original variables. For fib, reading the example loop code, no matter what n is, I would read that as returning 0 (cause it looks like each loop iteration a is set back to 0).

view this post on Zulip Brendan Hansknecht (Mar 03 2025 at 18:08):

Though maybe it just needs two explicit blocks

view this post on Zulip Kiryl Dziamura (Mar 03 2025 at 18:48):

Ah, actually the loop part must take as an input the same type it returns. So the initial state may be returned if the loop conditions never met

view this post on Zulip Kiryl Dziamura (Mar 03 2025 at 20:15):

Just wild takes as part of the brainstorm

fib = |n| {
    match (0, 1) {
        (a, b) for _ in range(0, n) => {
            (b, a + b)
        }
    }.0
}
fib = |n| {
    from (0, 1)
    loop (a, b) for _ in range(0, n) => {
        (b, a + b)
    }.0
}
fib = |n| {
    for _ in range(0, n)
    init (a, b) with (0, 1) {
        (b, a + b)
    }.0
}
fib = |n| {
    loop (a, b, i) from (0, 1, 0) {
        if i == n {
            return a
        }

        (b, a + b, i + 1)
    }
}

Yeah, at most, they're meh. Anyway, the idea is to find a reasonable way to represent loops as expressions

view this post on Zulip Richard Feldman (Mar 03 2025 at 21:19):

Kiryl Dziamura said:

From time to time I think about vars and loops in roc. One thing is not really clear to me (but I haven't thought about it thoroughly), is why for and other loop constructs can't be expressions in the same manner as if/else and match are. Partial mutability of var x_ seems reasonable, but can't similar experience be achieved via immutable syntax? In particular, can we have an early return and readable control flow that reminds .walk, folds, reduce etc.

the way I ended up at var was trying really hard to not introduce var, and then finding a lot of designs that were just way clunkier and more confusing than var, and concluding we should do var :big_smile:

view this post on Zulip Richard Feldman (Mar 03 2025 at 21:19):

that's not to say that it's impossible! Maybe there's something out there, but I don't think it's safe to assume a better alternative exists.

view this post on Zulip Kiryl Dziamura (Mar 03 2025 at 21:29):

I can imagine! It's funny how difficult to represent a simple functional fold in an imperative style not touching mutation


Last updated: Jun 16 2026 at 16:19 UTC