I'm finding the roc track really fun, I've solved about 58 exercises.
However, I'm starting to get to the point where I feel stuck on every challenge.
Does anyone have any high level advice for how to get unstuck without resorting to "cheating"?
For example, I just solved the saddle points exercise but I had to look at the exemplar code to do it, and my solution is basically the same as a result, just with some minor differences.
I want to continue solving exercises at a good pace and coming up with unique solutions but I feel like I'm not able to :sweat_smile:
The next exercise is https://exercism.org/tracks/roc/exercises/rectangles and I can already feel myself wanting to look at the example code because I don't know where to start.
@Aurélien Geron or @Isaac Van Doren should be able to give some good tips
Wow, so cool to hear you've done that many examples already! Awesome.
Wow congrats, that's awesome!
You could try looking at solutions to some of the exercises in other language tracks. That could give you a good start but require you to still adapt the solution you see to Roc.
58 exercises, wow that's impressive, we're going to have to add many more! :sweat_smile:
I've occasionally taken a peak at the Haskell or Elm solutions: it's good to try your best for a while, but it's no use getting stuck for too long, I feel it's better to look for inspiration, learn and move on to the next exercise.
That said, what kind of things do you get stuck on? Personally, as I come from 20+ years of imperative languages, the main difficulty is understanding how to think like a functional programming engineer. I mostly struggled on loops: generally List.walk does what I need it to, it's just not natural to me. Often, writing a little recursive function (ideally using tail recursion) is a simpler option, especially when you don't know in advance how many iterations you'll need.
Hope this helps, and feel free to ask questions here if you get stuck. :+1:
Thank you for the encouragement
That said, what kind of things do you get stuck on?
I think I often get stuck trying to come up with a way of iterating that makes sense for the problem. Walk is useful but sometimes doesn't feel like the right fit. I like the recursive function idea.
I also sometimes struggle with the amount of ceremony needed handling things like array indexing, especially in cases where you happen to know that the array is not empty. I guess there is a skill to dealing with results in an elegant way. I have spent a fair bit of time with Elm so I should be used to this but it still sort of trips me up.
You could try looking at solutions to some of the exercises in other language tracks. That could give you a good start but require you to still adapt the solution you see to Roc.
Yeah that's a good idea.
I also have been prompting claude with the problem description saying "give me high level guidance without showing any code", and that sometimes works quite well.
Asking an LLM for advice without spoilers is a great idea!
Yeah, I'll try that as well! I asked Claude and ChatGPT for Roc code a few times, but they barely know Roc, so they generate a weird mixture of various languages, they're not helpful at all. It will certainly improve once Roc is the #1 language in the world :wink:, but in the meantime, asking for general guidelines is a much better idea.
Regarding the ceremony around accessing arrays and so on, I must admit that I feel the same way, it's one of my main problems with the language right now, tbh, some things feel overly verbose, and I often wish there was an unwrap()
function like in Rust, to say: "please trust me on this, compiler, I know what I'm doing". But unwrap()
is really overused in Rust, and this leads to unstable programs, so Roc is trying to avoid it (disclaimer: I'm not a Roc developer, and in fact I'm fairly new to the language). Since I'm writing many of the Exercism solutions, I try to do things the Roc way and avoid unwrap()
, but if it was a personal side-project, I would definitely be using my own unwrap()
function.
Here are a few variants of a tripleFirst
function that takes the first element of a list that we know for sure is not empty, and it returns the triple of the first element:
# Recommended Roc approach
tripleFirst1 = \list ->
firstElement =
when list |> List.first is
Err ListWasEmpty -> crash "Unreachable: the list cannot be empty here"
Ok first -> first
firstElement * 3
# Using a custom `unwrap()` function:
tripleFirst2 = \list ->
firstElement = list |> List.first |> unwrap "the list cannot be empty here"
firstElement * 3
unwrap = \result, message ->
when result is
Ok value -> value
Err _ -> crash "Unwrap failed: $(message)"
# Using Result.withDefault => quick & easy, but probably a very bad idea
tripleFirst3 = \list ->
firstElement = list |> List.first |> Result.withDefault 0
firstElement * 3
# If the function returns a `Result` (for some other errors) you could write:
tripleFirst4 = \list ->
firstElement = list |> List.first |> Result.mapErr? \ListWasEmpty -> crash "Unreachable: the list cannot be empty here"
if List.len list > 100 then
Err ListIsTooLong
else
Ok (firstElement * 3)
I find the recommended approach a bit too heavy, and the unwrap
option is actually quite nice, imho. The Result.withDefault
option is so easy that I'm sure many people will be tempted to use it (I certainly have), but it's probably a bad idea because if the assumption is one day broken (through refactoring, for example), we may not notice it straight away, the code will just start using the default value. I hope this helps.
I feel like I would do
tripleFirst1 = \list -> list |> List.first |> Result.map \f -> f * 3
I have to admit, I have been opting for Result.withDefault
quite a lot.
Or maybe
tripleFirst1 = \list ->
first = list |> List.first?
first * 3
ohh taht's quite nice
I'm sort of scared to use the crash keyword in actual code outside of scripting
Wait I'm not sure how to use the ?
operator... I thought we added that
Luke Boswell said:
I feel like I would do
tripleFirst1 = \list -> list |> List.first |> Result.map \f -> f * 3
Yes, I should have made it clear that if your function can just return an error, then that's great, do that, it's easy (especially with the ?
try operator). But I've found that I often run into cases where the error just cannot happen, so I don't want it to be part of the function signature.
Luke Boswell said:
Wait I'm not sure how to use the
?
operator... I thought we added that
You just need to return an Ok
value at the end
tripleFirst1 = \list ->
first = list |> List.first?
Ok (first * 3)
But I've found that I often run into cases where the error just cannot happen
I guess this is what crash
is (can be used) for
Yes, I believe that crash
in the right solution in this case, but when you want to unwrap an Ok
value, when you know for sure that it cannot be an Err
, then it can be a bit verbose, and I think an unwrap()
function is fine in this case (others might disagree). Just be careful not to overuse it.
I personally think that there should be a Result.unwrap
function in the standard library, or else:
Result.withDefault
which is much worse (as explained earlier)unwrap
function, and various libraries will use various unwrap
variants, possibly with different names. In the end it will be worse than including it in the standard library (with a big warning: "please don't overuse!").Just my 2 cents, again, I'm still very new to Roc!
I feel less guilty using Result.withDefault than I do using crash. Crash feels super spooky to me lol
Haha, yes, I think it's meant to be scary.
Result.withDefault
is fine if you actually need a default value, but if you're using it to unwrap the value and the default value can never be used, then I fear that this can lead to hard-to-debug bugs in the future, so I think crash
is better when you know for sure that a path is impossible.
My 2 cents regarding crash or withDefault.
I would argue that a function tripleFirst : List a -> a
is impossible to implement in a "good way" since its not a total function. Either you need to resort to use crash
or Result.withDefault
, both are equally bad imho.
The problem is that the function has a bad design from the beginning. The right (imho) approach is to change the signature of the function to make it total. Either you can change the argument to a non empty list (a list that's guaranteed to have at least one element) or you can change the signature to return a result.
# a non empty list can be represented as a tuple (a,List a)
tripleFirst : (a,List a) -> a
or
# return an error on empty list
tripleFirst : List a -> Result a [EmptyList]
I don't know how the Roc problems on Exercism are designed in general but I think it would make sense to try to design them in a way to avoid forcing the student to use bad habits like crash or withDefault.
That's a really interesting insight, thanks.
Looking at the 96 exercises we wrote, I just counted 19 uses of crash
in the solutions. Seven of these are due to the fact that the instructions explicitly say that the user should assume that the inputs are ASCII, so when calling Str.fromUtf8
, we just crash rather than bubble up the error. Perhaps that's a bad idea. In some cases, we start by checking that all characters are digits, then we call some function which can then assume that it's only getting digits as input, so after it does whatever it needs to do (e.g., reversing the order of the digits), it can convert the characters back to a string and it can be certain that there will be no UTF-8 error, so it just crashes. I haven't looked in detail at the other cases yet, but they seem to be mostly access to lists that we know are not empty, or we know they have N elements and we're accessing the element e < N. That sort of thing.
I was going to say, one trivial example is when you're wanting to map over a string.
You call toUtf8
and then map, and then call fromUtf8
which returns a result. But we know the characters are all valid utf8 because we just got them from a string.
e.g
digits = string
|> Str.toUtf8
|> List.keepIf \c -> c >= '0' && c <= '9'
|> Str.fromUtf8
|> Result.withDefault "" # This is unreachable
(caveats about unicode/graphemes not withstanding)
Yes, that's a great example
dependent types can solve this but that would add more complexity to the compiler , we'd like to avoid that for now :p
Last updated: Jul 06 2025 at 12:14 UTC