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"])
You can't shadow variables, but you can definitely have inner functions.
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"])
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.
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"])
the plan is to have it always be a warning - shadowing is intentionally not something we want to support, for design reasons
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,rocwill 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.
Oh I hadn't seen your response Richard
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.
we've talked about it various places in Zulip
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
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
I can be confident that if that were happening, I'd have gotten a warning and CI would have failed
stuff like that
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:
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.
Thanks, found some discussions if anyone is interested:
removewingman has marked this topic as resolved.
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