Ha! Just finished the first one. :tada:
https://gist.github.com/georgesboris/3c43ed7618f5816df85d620c41eea73c
Loved the experience! Here are a few takeaways:
=
syntax { state & num = 0 }
. Maybe something the compiler could be prepared for so it gives a helpful message?List.length
and List.foldl
and the only way for me to figure out we use List.len
(short name :( why) and List.walk
was to just read through the docs. If there was some way for a module author to specify common aliases then maybe the compiler could give me a more helpful message? The same thing occurred when trying to go for List.filterMap
to filter out results - then I've found the super helpful function List.keepOks
. I think that working on a "discovery experience" will be a great selling point when first learning the language (or learning a new module).Ah! The tag pattern matching + guard clauses are awesome :heart: I will miss them when going back to Elm for sure
Thank you so much @Georges Boris :) Excellent feedback!
PR for UX is good :+1:
Can you make issues for the listed pain points if you have the time?
Thank you for that feedback, @Georges Boris ! And nicely done on solving the Advent of Code 2021 Day 1 puzzle. I'm hoping to do days 1-3 on Monday. I wonder who else will tackle day 1 today...
@Georges Boris Are you sure to have completed the entire 1st day and not the half? :innocent:
Good point, @Ghislain , I forgot to mention - every AoC puzzle has two halves! Feel free to do one or both, but it's nice that we effectively have 62 puzzles available :present: :present:
My code is not as clean as yours! https://github.com/ghigt/advent-of-code
After a period of frustration on how to deal with the tags and especially retrieve the results contained inside. A lot of indentation in the when
pattern, but after a lot of searching in the docs and tutorials I managed to find just about everything I wanted (I agree that it's still not very easy to find my way around at the moment).
Curiously, writing in the 3 other languages allowed me to find new ways to implement the algorithm and features in Roc (especially with Rust).
After a few iterations of implementation, I am quite satisfied with the result and I enjoyed writing in Roc for its syntax simplicity!
What I remember (for a beginner in FP) after this 1st day:
when
which takes tuples
to be able to test 2 values (when (a, b) is
) at the same time (for the moment I use what is marked in the elm-tutorial when Pair a b is
) Task
/Effect
and what a simple Program
expect.I guess I should try and do this before looking at anything. Though I think I already did a few of these problems last year in BQN, so I am familiar.
Would love to know what is or is not idiomatic in my code to know how to improve it.
That's one of the exciting things about Roc - I don't think there is a such thing as idiomatic Roc yet, we're inventing/discovering that right now!
General question: Are people parsing or just copying the data into a list? I mean in this case the parsing is super simple, but I know it can sometimes get more annoying.
@Brendan Hansknecht That was one of the things I was most curious to see on day 1.
I parsed the file, and @Georges Boris copied it inside its .roc
file
@JanCVanB whoops! had no idea there was a part 2 :sweat_smile:
I decided to go the file parsing route. I also decided to take an arg for the file name to process.
My day 1 code is here: https://github.com/bhansconnect/roc-aoc-2021/tree/trunk/day1
Overall went pretty well. Took a bit of doc diving as I tried to use proper functions. Also definitely took more time to setup the base structure than to solve the real problem
I feel like my actual solutions once the data is loaded are pretty simple and nice. That being said, they are also not efficient...but eh...not important for this amount of data.
I think may main question: Would it be worth adding a List.countIf
function? Instead I just used List.keepIf
and then List.len
which woud be way way less efficient. I also could have used a walk, but that requires state management and I didn't feel like it.
This is definitely a useful pipeline stage: |> List.keepIf (\x -> x)
:rolling_on_the_floor_laughing:
Ghislain said:
Would love to know what is or is not idiomatic in my code to know how to improve it.
Your code looks just fine. That being said, it definitely looks more like a port of imperative code rather than normal FP code. I feel like FP tends to break things up into stages and try to avoid all the work you had to explicitly do via state management. On the other hand, your solution should be reasonably efficient.
If you compare to my solution for part2: https://github.com/bhansconnect/roc-aoc-2021/blob/b4981b6f334c1a95fde72e5c46b23e99ff4a5e4f/day1/part2.roc#L64
Long term in Roc (once we have seamless slices), my algorithm would do the same work as yours with the extra cost of 3 lists:
Was thinking about taking advantage of map2/3, happy to have seen your implementation with it!
That being said, most of those lists could go away while keeping things relatively simple:
walk3
methodcountIf
method.That being said, pipelining will likely always be slightly less performant if the implementation isn't careful due to extra allocation. Though I do reduce the amount of branches which could lead to a win in some case.
I was trying to do only one iteration over the list to be efficient, but is it? Your impl' looks a LOT smaller and easier to read even though you create multiple iterations/lists.
With seamless slice such that list.dropFirst
does not cause a copy and the edition of countIf
, I would probably expect my implementation to run faster. It would allocate once for the movingSum
and then just process into that and over it to get the final result. It would be done with no branching (assuming countIf
compiles correctly)
All the work of checking optionals and nested branches that your implementation does would probably cost more, but also, you could optimize away a number of those branches with some minor changes
I think ultimately the fastest way would be just walk once (using walk3
and list.dropFirst
with seamless slices) while only tracking the last sum and total count. You would init the last sum to -1 because that is guaranteed smaller than any value. The count you would increment on every loop, but using cmov to avoid the cost of an unpredictable branch (using inline if in roc would probably do this).
Huge thanks for this feedback!
Had a lot of fun doing the first problem today. Roc is such a pleasure to use!
For the second part, I used a walk holding state of the last two elements, rather than chopping out two additional lists: https://gist.github.com/ayazhafiz/0d5d40b35bb58f79fb742b1d2ec7515c
one thing that actually surprised me at first is that if you define a function like
foo = \tag ->
when tag is
Tag A (Ok _) -> ...
Tag A (Err _) -> ...
Tag B _ -> ...
this actually gets inferred to [Tag [A, B] [Ok *, Err *]*]
, and not [Tag [A, B] [Ok *, Err *]]
as you might expect (note the addition of the open tag union in the inferred type!). That's because the Tag B _
case is catch-all, so you need type annotations here.. but I wonder if we can make this better somehow.
That's definitely a direct and clean solution
Mine is definitely implemented closer to how someone might do it in an array language.
Which is a tad weird for an FP language
your solution also works well for lazy languages, like haskell
Ah. That's fair
ok - got 5 minutes of free time and managed to finish the 2nd part of day 1 :sweat_smile:
turns out it was just a single diff :)
https://gist.github.com/georgesboris/85dbd20b3c8c1be752fe252a59309cdb
I wanted to catch up on Roc for a while but life kept on happening. With rocktoberfest here, I had no excuses.
I did the tutorials first and I'm still trying to wrap my head around Program
and Task
. Program.quick
seems good enough for AoC (and easy to understand). I encountered a lot of compiler crashes esp. when there was a type mismatch. But the rest of my experience was great. Love the language and the api docs.
One thing I noticed was that tags being first class functions makes up for the lack of partial function application. I like how the Day 2 parser turned out.
Anyway, here's my Day1: https://github.com/shritesh/advent/blob/main/2021/1.roc and Day2: https://github.com/shritesh/advent/blob/main/2021/2.roc
Looking forward to doing the rest as well.
I'm finally reading day 1 solutions, and I'm already thrilled
@Georges Boris Just l if l < num -> { next & result: state.result + 1 }
is brilliant!
@Georges Boris @Ghislain @Brendan Hansknecht @Ayaz Hafiz @Shritesh Bhattarai @Mike Kalvas Do I have your permission to show/mention your solutions for day 1 (and potentially other days going forward) in a public YouTube video?
Sure!
Yeah
https://github.com/tritowntim/roctoberfest/blob/main/2021/01/main.roc
My first roc :guitar: ... array destructuring would be nice, List.len
felt clunky... but it feels so Elm-y :drooling:
Can someone please have a look at my code and help me figure out what is wrong with my Task. I have been playing around for this a while, but the compiler error is a bit hard to decipher. It passes all the tests, and I am confident it reads the file correctly, just not sure about the Task.attempt which is where I think there is an error. aoc day 1 roc attempt
@Luke Boswell to use fileContents
as a Task
with Task.map
, you need to remove the backpassing syntax and Task.await
(otherwise its type is just a Str
).
The other issue you will have is that your count
is a Result U64
(inside a Task
) but you try to concat it to a Str
. You will need to transform it (in the same inspiration as your code, you could add countStr = Task.map count Num.toStr
and Task.attempt
it instead of count
)
Ghislain said:
Luke Boswell to use
fileContents
as aTask
withTask.map
, you need to remove the backpassing syntax andTask.await
(otherwise its type is just aStr
).
The other issue you will have is that yourcount
is aResult U64
(inside aTask
) but you try to concat it to aStr
. You will need to transform it (in the same inspiration as your code, you could addcountStr = Task.map count Num.toStr
andTask.attempt
it instead ofcount
)
Thank you @Ghislain I managed to get something working. I'm pretty happy with the result. I updated my gist in case anyone is interested.
I just finished doing Part B of the first day. Though my function is a little crazy.
getAnswer : List Nat -> Nat
getAnswer = \measurements ->
result =
acc, measurement <- List.walk measurements {first: Start,second: Start,third: Start, previousSum: Start, increases:0}
when acc.first is
Start -> {acc & first:Continue measurement}
Continue firstMeasurement ->
when acc.second is
Start -> {acc & second:Continue measurement}
Continue secondMeasurement ->
when acc.third is
Start -> {acc & third:Continue measurement, previousSum:Continue (firstMeasurement + secondMeasurement + measurement)}
Continue thirdMeasurement ->
when acc.previousSum is
Start -> {acc & previousSum:Continue (firstMeasurement + secondMeasurement + thirdMeasurement)}
Continue previousSum ->
sum = measurement + secondMeasurement + thirdMeasurement
newAcc = {acc & first: Continue secondMeasurement,second: Continue thirdMeasurement,third: Continue measurement, previousSum: Continue sum}
if sum > previousSum
then {newAcc & increases:acc.increases+1}
else newAcc
Is there a way to pattern-match my values without nesting? In Elm, I would have done the following:
getAnswer : List number -> number
getAnswer measurements =
case (acc.first, acc.second, acc.third, acc.previousSum) of
(Start, _, _, _) -> ...
I did it similar, built a filter to take the numbers and then add them using a sliding window.
slidingWindow : List U64 -> List U64
slidingWindow = \depths ->
depths
|> List.walk
{ n1: Nothing, n2: Nothing, filtered: [] }
\state, depth ->
when Pair state.n1 state.n2 is
Pair Nothing Nothing -> { n1: Just depth, n2: Nothing, filtered: [] }
Pair (Just n1) Nothing -> { n1: Just depth, n2: Just n1, filtered: [] }
Pair (Just n1) (Just n2) ->
{ n1: Just depth, n2: Just n1, filtered: List.append state.filtered (n1 + n2 + depth) }
Pair _ _ -> state
|> .filtered
I had to use the [Nothing, Just U64] as the first and second numbers you need to handle as special cases. So n1
is like the n-1
th index and n2
is like n-2
nd index.
You can on a tuple. We just don't have direct tuple syntax, so you have to use a tag technically:
when T acc.first acc.second acc.third acc.previousSum is
T Start _ _ _ -> ....
tuples are planned, just not implemented yet :big_smile:
@Brendan Hansknecht Thank you so much! That is what I need.
I'm late to the party, but I got day 1 working - my solutions. I already solved 10 days in Sesterl/Erlang back in December so I'll be mostly porting my solutions to Roc :)
Last updated: Jul 06 2025 at 12:14 UTC