hello everyone! If you did any amount of Advent of Code in Roc this year, I'd love to hear your feedback about the experience!
in the past for things like this we've done a video chat - if that sounds good to you, here's a link for coordinating on when we can all make it: https://www.when2meet.com/?18034195-ZJlyp - alternatively, if you'd rather share feedback in written form, feel free to either post it here or DM it to me...whatever you prefer, I'd love to hear about how the experience went - whatever comes to mind!
Advent of Code 2022 Feedback and Observations
I had a lot of fun working through the AoC puzzles; and some friendly competition with my mates who insist on using legacy technologies. :wink:
My first introduction to this kind of challenge was for Roctoberfest. My experience has been similar, though I managed to get much further this time around. I have enjoyed writing Roc apps, learning more about the language and finding new ways to solve problems. The best part has been comparing my solutions with other people, which really highlighted different approaches to the same problem.
I experienced some issues using Roc which was what I expected; however I tried to focus on contributing bug reports or ideas where I could. My progress through the challenges has slowed recently. I think the main reasons for this is; the puzzles are getting harder, I’m getting distracted with other things, coming across bugs which are hard to workaround, and Roc doesn’t have a mature ecosystem of libraries for common algorithms, data structures, visualisations etc.
Parsers are fun, not-that-hard, and very worthwhile to learn. I’ve never really had much need to write a parser, the occasional regex has always been more than sufficient. I thought parsers were difficult and time consuming to write and test. However, during this AoC I started writing parsers in an attempt to learn more. I’m still a beginner; and can see there is much more to learn, but from my experience I would highly recommend using parsers to anyone, especially beginners.
URL packages are great to work with. I’ve had no issues and found they really helped to reduce the friction to write an app. I could just copy-paste code into a file, type roc run
and it ‘just worked’! As someone who doesn’t live in a cli, I found this a significant improvement over googling how to write a symlink each time, or writing my apps in a roc subdirectory.
I had some trouble learning how to do Task management and error handling. Roc felt strict sometimes; wouldn’t run my app, I couldn’t isolate the issue and the errors were confusing. I didn't find many examples of how to do Tasks or handle errors properly. However; I think my experience has now been overtaken by events, and I don’t think it is likely to be repeated in future. There have been changes to the API and at least one bug here that I experienced. I think a relevant lesson here is that it will be important for an introduction to Roc programming and using Tasks to include working examples and show error handling. Note that at the start of AoC there weren’t many (any?) examples of Tasks in the wild; this is no longer the case. The pattern of sequencing Tasks with backpassing syntax is really nice, just new and different enough from patterns in other languages.
Minimising errors and reporting crashes was easy to do and really helped the team find and fix compiler issues. I experienced plenty of crashes and panics; usually due to my lack of understanding or a crazy misapplication of syntax. I was encouraged by the team who have consistently asked me to raise issues when I find them. I found the process to minimise these examples to be straightforward and also helped me to understand my code better in the process. All I needed to do was copy-paste the app into a new file and then just repeatedly delete things and run the code until I got to a minimal example. Simple enough, but something I haven’t needed to do before.
Thank you to the Roc contributors for your work building a great language. It’s awesome to see all the progress; and I feel lucky to be part of the development at this time.
thank you for the feedback and the kind words, Luke! :smiley:
besides fixing the Task
-related bug, this tells me I should make sure to prioritize explaining Task
error handling in the tutorial :thumbs_up:
On error handling it is so cool that error tags combine and "just work".
Elm (does not compile)
type ThingErr
= Wat
| Hmm
thing : Result ThingErr {}
thing =
Err Wat
type Thing2Err
= Other
| Stuff
thing2 : Result Thing2Err {}
thing2 =
Ok {}
dooStuff =
thing |> Result.andThen (\_ -> thing2)
Roc (Does compile!)
ThingErr : [Hmm, Wat]
thing : Result {} ThingErr
thing = Err Wat
Thing2Err : [Other, Stuff]
thing2 : Result {} Thing2Err
thing2 = Ok {}
dooStuff : Result {} [Hmm, Other, Stuff, Wat]
dooStuff =
thing
|> Result.try \_ -> thing2
yeah, this was the original motivation for having tag unions in the language! :smiley:
Here's a few notes about my experience:
dbg
showing incorrect results is worse than not having dbg
at allexpect
s inside functions to workif .. then .. else ...
crash
has been a huge productivity boost. Not sure if this is good for the language in the long termList.range
is now fixed. Had to write generally-expected functions in FP languages like scan, windows and chunk myself. See Elixir's Enum module for inspirationThanks for the feedback @Shritesh Bhattarai!
There were a number of string manipulation functions that I missed not having, such as:
Additionally, I found a couple places when doing string manipulation where it would've been nice to be able to re-declare an identifier within the same scope (the same way that Rust permits), i.e. by shadowing the previous declaration. An example would be splitting a string into words, then incrementally consuming each of those words: with redeclaration, I could have a words
identifier that I peel words off the front of, and have a successively smaller list of remaining words; without such an ability, I need to use different variable names (naming is hard), or I need to carefully count indices.
+1 to shadowing - even though I love a language like Elm, I've been using Elixir a lot and shadowing never caused me any bugs there. However, not shadowing did cause bugs for me on Elm since I need to maintain multiple versions of the same thing on the same scope with the same type, but if I reference an old one by mistake the compiler is not helpful (since they're the same type). maybe a restricted shadowing where only things with the same type could be generated would be ideal?
I tend to try and rewriting things that require shadowing with pipelining, but shadowing would be more flexible.
Definitely think it would be a nice addition based on my current experience.
Though i think maybe shadowing should be limited to scopes somehow. So can't shadow top level declarations or functions which might get called recursively....not sure the best rule here. Maybe the type idea above would work
@Kevin Gillette want to open a new topic in #ideas about shadowing so we can discuss more there?
Hmm....this said, a common use case in rust for shadowing is explicitly changing types. So path as a string and then path as a PathBuf. And other things of that nature.
I like the language and I had fun :)
I should've kept a list of stuff that came up as I went along, but here's what I can recall:
_ <- \f -> Result.withDefault (f {}) []
dbg
was both great when it worked and annoying when it didn't :Dexpect
was lovely to write tests with, usually writing tests has a lot of friction (gotta name stuff and create files and whatnot)crash
felt wrong every time, but it kept working out for AOC stuff, so I kept using it :DI've had a good number of times in which i used successive backpassing with Result.map and saw type mismatches from nested results, and it took me a while to get comfortable enough with the Roc mindset to realize i needed Result.try in such cases.
This is an area where documentation could be more helpful to beginners if it includes some wording along the lines of "you want this function if..."
Last updated: Jul 06 2025 at 12:14 UTC