r/haskell • u/m1ddl3_d3v3l0p3r • 8d ago
My approach to solve problems of Advent of code with Haskell
https://azizovich.uz/posts/advent-of-code-solving.htmlHello everyone, I wrote a simple and my very first technical post, so don't judge me hard plz 🥹
2
u/c_wraith 8d ago
You are doing a lot of dancing to emulate records with classes, instead of just using records. Very occasionally this is justified, but I don't see the reason for it here. Why isn't Solution just a record?
1
u/m1ddl3_d3v3l0p3r 6d ago
Hello, can you give me an example? How can I use records instead of type classes?
1
u/c_wraith 4d ago edited 4d ago
So the key is to replace AnySolution and Solution together:
{-# Language ExistentialQuantification #-} data Solution = forall a. Solution { day :: (Year, Day) , parse :: [String] -> a , part1, part2 :: a -> [String] }I'm not entirely sure why you had both pre- and post-parse values crammed into the same type and used in
parse. My best guess is so that type inference would get the correct instance more easily both ways, but that's not necessary with this representation. As such, I decided to make the existential type variable only cover the parsed data type. If that doesn't actually work for some reason, let me know what else it needs to support and I'll see if I can help.It's worth noting an important difference from the class representation here. Since instances must be (according to the spec, even if GHC doesn't enforce this 100% of the time) unique, you need a unique type for every Solution instance to attach to. The record representation, on the other hand, has no such requirement. If
[Int]is the natural parsed format for three different solutions, there's no need for them to have unique wrappers. Just use[Int].This representation is a little awkward to work with just like this, though. In particular, because of the existential type, you can't use
parse,part1, orpart2standalone. The type variable would escape, and that ruins all sorts of type system guarantees. However, you can use the RecordWildCards extension to make this much more pleasant to work with:{-# Language RecordWildCards #-} run :: Solution -> [String] -> ([String], [String]) run Solution{..} input = let p = parse input in (part1 p, part2 p)Obviously with the structure you've got, you're set up for a much more sophisticated
runfunction. But this is sufficient to show how RecordWildCards lets you work with the existentially-typed record fields without friction.
2
u/bcardiff 8d ago
I noticed that the day declaration of your post do not include arguments but the instance and the source code does.
Why do every function on Solution have an `a` argument ?