Stream: beginners

Topic: ✔ inner functions and shadowing


view this post on Zulip removewingman (Sep 02 2026 at 19:40):

Ported this code from ocaml, as far as I know shadowing variables and inner functions are not allowed in roc, is there any way to make this more idiomatic/elegant?

aux = |lines, acc|
    match lines {
        [] => acc
        [_, .. as t] => aux(t, acc.append(lines))
    }

expect aux([1, 2, 3], []) |> List.is_eq([[1, 2, 3], [2, 3], [3]])

lines_lists = |lines| aux(lines, [])

expect [1, 2, 3] |> lines_lists |> List.is_eq([[1, 2, 3], [2, 3], [3]])

apply = |lines, functions| {
    lines_lists(lines)
        |> List.join_map(|list_list| functions.join_map(|f| f(list_list)))
}

expect apply(["A"], [|_| ["A"], |_| ["B"], |_| ["C"]]) |> List.is_eq(["A", "B", "C"])
expect apply(["A"], [|a| a, |a| a]) |> List.is_eq(["A", "A"])

view this post on Zulip Aurélien Geron (Sep 02 2026 at 19:50):

You can't shadow variables, but you can definitely have inner functions.

view this post on Zulip Aurélien Geron (Sep 02 2026 at 19:50):

For example:

apply = |lines, functions| {
    sublists = |lines2, acc|
        match lines2 {
            [] => acc
            [_, .. as rest] => sublists(rest, acc.append(lines2))
        }

    expect sublists([1, 2, 3], []) |> List.is_eq([[1, 2, 3], [2, 3], [3]])

    lines_lists = |lines2| sublists(lines2, [])

    expect [1, 2, 3] |> lines_lists |> List.is_eq([[1, 2, 3], [2, 3], [3]])

    lines_lists(lines)
        |> List.join_map(|list_list| functions.join_map(|f| f(list_list)))
}

expect apply(["A"], [|_| ["A"], |_| ["B"], |_| ["C"]]) |> List.is_eq(["A", "B", "C"])
expect apply(["A"], [|a| a, |a| a]) |> List.is_eq(["A", "A"])

view this post on Zulip Aurélien Geron (Sep 02 2026 at 19:53):

I just noticed that when you run the code with roc test, it only shows the results for the two outer expect statements. The inner expect statements display nothing, unless they fail.

view this post on Zulip removewingman (Sep 02 2026 at 19:57):

Thank you. I think I am going with this:

Do you know the state with variable shadowing? Is it planned or something

lines_lists = |input_lines| {
    aux = |lines, acc|
        match lines {
          [] => acc
          [_, .. as t] => aux(t, acc.append(lines))
    }
    aux(input_lines, [])
}

expect [1, 2, 3] |> lines_lists |> List.is_eq([[1, 2, 3], [2, 3], [3]])

apply = |lines, functions| {
    lines_lists(lines)
        |> List.join_map(|list_list| functions.join_map(|f| f(list_list)))
}

expect apply(["A"], [|_| ["A"], |_| ["B"], |_| ["C"]]) |> List.is_eq(["A", "B", "C"])
expect apply(["A"], [|a| a, |a| a]) |> List.is_eq(["A", "A"])

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:03):

the plan is to have it always be a warning - shadowing is intentionally not something we want to support, for design reasons

view this post on Zulip Aurélien Geron (Sep 02 2026 at 20:05):

Looks good! :+1:

The absence of variable shadowing is a design decision. The tutorial says:

Constants should not be reassigned or shadowed, if you try to do name = again in the same scope, roc will give a compile-time warning with exit code 2. That way, you can quickly write something with shadowing if you want but the non-zero exit code prevents it from ending up in production code because CI will fail.

I suppose the goal is to make the code as unambiguous as possible, and avoid some bugs that might be hard to catch when you think you're using one variable but you're actually using another.

I've been a bit frustrated by that a few times. It forces me to invent variants of variable names, adding an index like list2, or using a short variant like lst, and I find this error prone. In fact, I think it might increase the risk of error because I end up writing list by mistake (instead of list2), and the code uses the variable from the outer scope. I'm not sure the absence of variable shadowing is actually helping overall. Just my opinion.

view this post on Zulip Aurélien Geron (Sep 02 2026 at 20:05):

Oh I hadn't seen your response Richard

view this post on Zulip removewingman (Sep 02 2026 at 20:13):

Richard Feldman said:

the plan is to have it always be a warning - shadowing is intentionally not something we want to support, for design reasons

@Richard Feldman
Can you go more into detail what you mean with design reasons?

Coming from ocaml, I like shadowing inside of functions, similar reasons that Aurélien Geron mentionend.

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:14):

we've talked about it various places in Zulip

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:15):

I guess the quick version is that if shadowing is banned, you can look at a local snippet of code, or a diff, and have stronger guarantees about what names mean

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:17):

e.g. if I have a small diff that introduces a new declaration somewhere in a function, and then uses that same declaration later on in the function, I don't need to expand what I'm reading to consider the entire function to make sure that the later use isn't actually referring to a shadowed declaration somewhere in between the two diff sections

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:17):

I can be confident that if that were happening, I'd have gotten a warning and CI would have failed

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:17):

stuff like that

view this post on Zulip Aurélien Geron (Sep 02 2026 at 20:25):

Yeah, there are definitely pros too. :+1: It's just that after coding in Roc for a little while, I'm now wondering whether the pros truly outweigh the cons in practice. Not a strong opinion, though.

Another con I've run into: I wanted to add a top-level name (e.g. config) but it was already used in several functions. So I end up having to choose another name or rename config everywhere.

I'm not saying "let's change this now!", just giving a bit of feedback. :smile:

view this post on Zulip Aurélien Geron (Sep 02 2026 at 20:27):

Also, modern editors are pretty good at highlighting which variable we're actually referring to, so the risk of ambiguity (inner versus outer) is lower.

view this post on Zulip removewingman (Sep 02 2026 at 20:33):

Thanks, found some discussions if anyone is interested:

view this post on Zulip Notification Bot (Sep 02 2026 at 20:33):

removewingman has marked this topic as resolved.

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:46):

Aurélien Geron said:

Also, modern editors are pretty good at highlighting which variable we're actually referring to, so the risk of ambiguity (inner versus outer) is lower.

this is true, but unfortunately doesn't help when you're reviewing a diff :sweat_smile:


Last updated: Sep 03 2026 at 15:16 UTC