To get a little more Roc familiarity (very little hands-on experience) I have been trying the Exercism track, and got a bit stuck on the OCR numbers exercise. I'm interested in seeing what techniques people would reach for to solve this? I don't believe it's complicated, but felt like I was forcing Roc in a direction that was unnatural. I can't apologise for being a beginner, but I'm sorry if the snippets below are not great!
Grids have been a pain point for me in statically typed functional languages, and in this case the problem is compounded by having a subgrid of ASCII number representations within a grid of numbers described by the string, each to be parsed into the number they represent.
_ _ _ _ _ _ _ _ #
| _| _||_||_ |_ ||_||_|| | # decimal numbers.
||_ _| | _||_| ||_| _||_| #
# fourth line is always blank
I supposed I could represent each row as U8s, use chunks_of
to get the groups of 3 (the width of each number), then transpose and chunk again by 4 (the height). My problem now was proving to the type system that that there were groups of 12 elements, which is where I'd usually reach for multiple clauses and list destructuring to declaratively handle each case.
As (I don't believe?) Roc has list destructuring, I instead went the route of defining chunks3 and 4, to return 3/4-tuples the type system can reason with, (each requiring multiple list gets and result destructuring instead):
chunk3 : List a -> List (a, a, a)
chunk3 = |l|
first = List.get(l, 0)
second = List.get(l, 1)
third = List.get(l, 2)
when (first, second, third) is
(Ok(a), Ok(b), Ok(c)) -> List.prepend(chunk3(List.drop_first(l, 3)), (a, b, c))
_otherwise -> []
with the final endpoint (after converting the nested tuples to a binary representation to make pattern matching less noisy) as such:
when s is
# gfedcba
0b0111111 -> Ok(0)
0b0000110 -> Ok(1)
# ...
_ -> Err(InvalidPattern)
This just felt bad, and I feel like I have either missed some standard library functions, or not adapted to alternative methods (or just written some very bad code!!).
Transposing a nested list was also not papercut free (using the standard map head
concatenated onto recursive transpose tail
approach) as it also required a helper function to prove that the list had a head available.
transpose : List (List a) -> List (List a)
transpose = |nested_list|
head_tail = map_head_tail(nested_list)
when head_tail is
Err(UnequalLength) -> []
Ok((first, tails)) -> List.concat([first], transpose(tails))
map_head_tail : List (List a) -> Result (List a, List (List a)) [UnequalLength]
map_head_tail = |nested_list|
firsts = List.map(nested_list, List.first)
all_ok = List.all(firsts, |r| Result.is_ok(r))
if Bool.not(all_ok) then
Err(UnequalLength)
else
ok_firsts = List.keep_oks(firsts, |x| x)
tails = List.map(nested_list, |l| List.drop_first(l, 1))
Ok((ok_firsts, tails))
I would really appreciate any pointers - Roc specific or general - for parsing and dealing with these kinds of structures idiomatically!
If someone could also help me understand the precedence of types in WSA type annotations too - List List a
does not behave as List (List a)
, but I'm having a hard time figuring out what the former could mean
Roc has list destructuring:
when list is
[a, b, c, .. as rest] -> ...
_ -> ...
That's embarrassing! No idea how I missed that in the tutorial, thank you!!
Will re-impliment in a more sane manner and see how it goes :laughing:
Docs even say
This can be both more concise and more efficient (at runtime) than calling
List.get
multiple times
:man_facepalming: :man_facepalming: :man_facepalming:
List List a
is definitely a bug. It is essentially equivalent to Dict k v
in parsing. It is a List type with 2 type variables (which already should fail cause list takes only one type variable). The first type variable is List without any type variable (also a bug). The second type variable is a
.
I think roc is missing the check on type variable count which often leads to weird failures with types like this.
Ah ok, thanks. I wasn't sure because it wasn't giving me any errors, and was resulting in some weird type signatures from the LSP that I do not yet understand
image.png
image.png
Anyhow, I suppose these (bugs?) are moot with the compiler rewrite
Jonathan has marked this topic as resolved.
Jonathan said:
Anyhow, I suppose these (bugs?) are moot with the compiler rewrite
yeah
Last updated: Jul 06 2025 at 12:14 UTC