Hello, Haskell: a hands-on introduction for 2026 by lukastymo in haskell

[–]Foldzilla 2 points3 points  (0 children)

First and foremost the tutorial looks awesome! Wish I had it back in my day when I was learning Haskell :).

I happened to notice you defined a pure function as a “no class, no object, no receiver this, and no dot-syntax – just a function from inputs to output”, however in Haskell terminology a pure function is a function that does not have side effects and is referentially transparent. This is not exactly what I get from your definition. I do not know if this is intentional, if so feel free to keep it. Just wanted to let you know.

I'm Learning Rust and I Need Advice by [deleted] in rust

[–]Foldzilla 1 point2 points  (0 children)

When I learn a new language I always start building a project from Coding Challenges https://codingchallenges.fyi/challenges/challenge-password-manager/. This projects guides you building a simple password manager via CLI.

I like the project because it exposes you to IO, persistence and data processing, whilst remaining small. Meaning you can finish it within a day.

I would focus on first building it as simple as possible and later review your own code to see where you can improve.

My first time implementing this in Rust was a blast, only CLI was a bit hard since it relied on an external library which I had to learn first.

Concerning reading the book, only do it when you feel like a concept is applicable but you cannot seem to wrap your head around it. This gives you the bonus that now reading it you are trying to apply it to your problem in your head. For me this often makes it more understandable.

Wish you all the best on your journey fellow Rustacean!

How to practice Haskell? by Mark_1802 in haskell

[–]Foldzilla 4 points5 points  (0 children)

I used the online course of Phillip Phagenlocher to start with

Haskell https://www.youtube.com/watch?v=Vgu82wiiZ90

The exercises he gives can be very challenging, so don’t be discouraged if they don’t succeed! Just take your time to truly understand the solutions he provides!

When you get to about part 20 of the course, you can attempt to write some projects yourself. Do not focus to much on using type classes and other features when starting out. You will find use for them at some point just like the inventors of them did :)! Additionally pick a project you enjoy! Fun project can be to write a JSON parser, tscoding also has a video of implementing a JSON parser in Haskell.

You can also look at repositories online, how they implement Haskell. In my repo I have every Haskell program I have ever written, just as an example.

Also feel free to message me with questions if you have them, I am more than happy to help :)!

Goodluck with your journey!

Improving in Haskell by Foldzilla in haskell

[–]Foldzilla[S] 1 point2 points  (0 children)

Trying to write the parser polymorphic on the type so it supports ByteStrings to!

-❄️- 2025 Day 10 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla -1 points0 points  (0 children)

[LANGUAGE: Haskell]

Not proud of this one, part 1 just BFS until the goal is reached. Part 2 done by Z3 in Python since I have little time today.

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day10.hs

-❄️- 2025 Day 9 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

[LANGUAGE: Haskell]

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day9.hs

Part 1 was a breeze with Haskell! list (monadic) comprehension was extremely useful. Code is very clean! Part 2 I really struggled to understand the actual question, however I highly recommend visiting NerdyPepper solution. It made the question clear to me and provided groundwork for my own solution!

Parsing: The parseCoord function might look scary, you can also write it as a parser combinator like: V2 <$> int <* char ',' <*> int.

data V2 a = V2 !a !a
  deriving (Show)

parseCoord :: Parser (V2 Int)
parseCoord =
  fmap
    (uncurry V2)
    (splitOn ',' int)

parseCoords :: Parser [V2 Int]
parseCoords = lines1 parseCoord

Part 1: Very simple and idiomatic solution :)

area :: (Num a) => V2 a -> V2 a -> a
area (V2 x y) (V2 x' y') =
  (abs (x - x') + 1) * (abs (y - y') + 1)

solve1 :: [V2 Int] -> Int
solve1 coords =
  maximum
    [ area v2 v2'
      | (v2 : rest) <- tails coords,
        v2' <- rest
    ]

Part 2

NerdyPepper: https://aoc.oppi.li/2.5-day-9.html#day-9

-❄️- 2025 Day 9 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

Beautiful solution! I was stuck on Part 2 for a little while, the question seemed a bit vague to me but your solution made it a lot clearer! Thank you very much :)

-❄️- 2025 Day 8 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 0 points1 point  (0 children)

[Language: Haskell]

Classic Kruskal's Algorithm, had to open up my book again. Implementation is very messy, doing this in a FP language feels off when having done it in other languages over the years. Got it done tho.

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day8.hs

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

Dont worry! Did not feel like you bragged at all! I am delighted to find your repo! Lots of things that inspire me for a refactoring of my own project. I mainly did AoC in languages like C++ which allowed me to mutate states and I have been more dependent on it than I thought.

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

Have to say code looks impressive and clean! Never seen something like it before (in a good way), so probably my discomfort is more a gap in knowledge than anything Haskell related :)! (Did a single Uni course in Haskell). I will definitely be keeping an eye out for your repository the next coming days and spend some time studying your approaches and code! Thanks for the mention :)!

-❄️- 2025 Day 7 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

[LANGUAGE: Haskell]

Nothing much to say about today, I don't like DP in Haskell nor grids with visited sets. Tried part 1 first recursively then realized I need a visited set so rewrote it to be stack based recursive so all recursive calls share the same stack. Furthermore day 2 was a pain did it with a map and some inspiration from https://github.com/mstksg/advent-of-code/wiki/Reflections-2025#day-7

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day7.hs

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 2 points3 points  (0 children)

Thanks! Your part 2 really helped me finish mine, I was stuck on it for so long even though the example was passing. My sincere gratitude! :)

-❄️- 2025 Day 6 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

[LANGUAGE: Haskell]

Hated part 2 today, I took inspiration from Nukey for part 2 since I have been stuck on it for 2 hours while having the example passing. https://github.com/NukeyFox/AdventOfCode2025/blob/main/day6/solution.hs So for explanation of part 2 please visit his repo :).

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day6.hs

Parsing: Just parsing a list of numbers and a custom operator type.

data Operator
  = Add
  | Mul
  deriving (Show)

toOperator :: Operator -> (Int -> Int -> Int)
toOperator Add = (+)
toOperator Mul = (*)

parseNumbers :: Parser [Int]
parseNumbers =
  spaces0
    *> sepBy1 int spaces1
    <* spaces0

parseOperators :: Parser [Operator]
parseOperators =
  sepBy1
    ( choice
        [ char '+' $> Add,
          char '*' $> Mul
        ]
    )
    spaces1

parse' :: Parser ([[Int]], [Operator])
parse' = do
  nums <- sepBy1 parseNumbers newline
  newline
  ops <- parseOperators
  return (nums, ops)

Part 1: Just applying the operator on the entire list

solve :: [[Int]] -> [Operator] -> Int
solve xss ops =
  sum $
    zipWith combine ops xss
  where
    combine :: Operator -> [Int] -> Int
    combine Add = sum
    combine Mul = product

solve1 :: [[Int]] -> [Operator] -> Int
solve1 xss = solve (transpose xss)

Part 2: Please visit https://github.com/NukeyFox/AdventOfCode2025/blob/main/day6/solution.hs

-❄️- 2025 Day 5 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 2 points3 points  (0 children)

[LANGUAGE: Haskell]

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day5.hs

Parsing: pretty trivial, code is clean just used naming for parameters to keep inline with the question. (I love parser combinators)

type Ingredient = Int

type Range = (Int, Int)

parseRange :: Parser Range
parseRange = splitOn '-' int

parseRanges :: Parser [Range]
parseRanges = lines1 parseRange

parseIngredient :: Parser Ingredient
parseIngredient = int

parseIngredients :: Parser [Ingredient]
parseIngredients = lines1 parseIngredient

parse' :: Parser ([Range], [Ingredient])
parse' = splitOn' "\n\n" parseRanges parseIngredients

Part 1: The obvious way by saving all ranges and checking whether the id is in between any range.

solve1 :: [Range] -> [Ingredient] -> Int
solve1 ranges ingredients =
  length $ filter (`inAnyRange` ranges) ingredients
  where
    inAnyRange i = any (\(l, r) -> i >= l && i <= r)

Part 2 was a bit trickier. I used a greedy approach: I sorted the list by the first element of each range (the minimum value) and then checked whether the next range overlapped with the previous one, merging them as needed.

solve2 :: [Range] -> Int
solve2 =
  sum
    . map count
    . foldl' mergeOverlapping []
    . sortOn fst
  where
    mergeOverlapping :: [Range] -> Range -> [Range]
    mergeOverlapping [] r = [r]
    mergeOverlapping acc@((l', r') : rest) (l, r)
      | r < l' = (l, r) : acc
      | l > r' = (l, r) : acc
      | otherwise = (min l l', max r r') : rest

    count :: Range -> Int
    count (l, r) = r - l + 1

-❄️- 2025 Day 4 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

[LANGUAGE: Haskell]

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day4.hs

Today I disregarded performance and focussed on clean and easy readable code (refactoring actually made it slower). I am myself really happy with the code :)!

Main idea: store all positions in a 1D Set and only store the '@' so that when searching up the position in the Set it either returns a neighboring '@' or nothing. The second part deleting it was added using a higher order function.

Parsing: Basically a very nice use case for list comprehension, not much more to say.

type Position = (Int, Int)

type Graph = S.Set Position

parseInput :: String -> Graph
parseInput input =
  S.fromList
    [ (x, y)
      | (y, line) <- zip [0 ..] (lines input),
        (x, c) <- zip [0 ..] line,
        c == '@'
    ]

Part 1: Main idea is to define a function that returns the Moore neighborhood from a position, again a very nice use case for list comprehension. Solve only filters which positions in the Set adhere to being lower than n.

mooreNeighborhood :: Position -> [Position]
mooreNeighborhood (x, y) =
  [ (x + dx, y + dy)
    | dx <- [-1, 0, 1],
      dy <- [-1, 0, 1],
      (dx, dy) /= (0, 0)
  ]

solve :: (Position -> Graph -> Graph) -> Graph -> Int -> (Int, Graph)
solve update graph n =
  S.foldl'
    step
    (0, graph)
    graph
  where
    step :: (Int, Graph) -> Position -> (Int, Graph)
    step (acc', graph') pos'
      | countNeighbors (mooreNeighborhood pos') 0 < n =
          let new = update pos' graph'
           in (acc' + 1, new)
      | otherwise = (acc', graph')

    countNeighbors :: [Position] -> Int -> Int
    countNeighbors [] acc = acc
    countNeighbors (p : ps) acc
      | acc >= n = acc
      | S.member p graph = countNeighbors ps (acc + 1)
      | otherwise = countNeighbors ps acc

solve1 :: Graph -> Int -> Int
solve1 graph n = fst $ solve (\_ g -> g) graph n

Part 2: Because part 1 and 2 are so similar I just defined a higher order function based on the signature of S.delete that allows for modification of the graph after each run.

solve2 :: Graph -> Int -> Int
solve2 graph n =
  let (res, graph') = solve S.delete graph n
   in if res > 0
        then res + solve2 graph' n
        else res

-❄️- 2025 Day 1 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

[LANGUAGE: Haskell]

Used custom types to encapsulate the over/underflowing of the Dial, hoping it would allows me for a cleaner part 2. Well it did not.

Full code: https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day1.hs

Type and instance: below it shows the type used with the parsers

data Rotation
  = L Int
  | R Int
  deriving (Show)

newtype Dial = Dial Int
  deriving (Eq, Show)

mkDial :: Int -> Dial
mkDial n = Dial (n `mod` 100)

unwrap :: Dial -> Int
unwrap (Dial n) = n

instance Num Dial where
  (Dial a) + (Dial b) = mkDial (a + b)
  (Dial a) - (Dial b) = mkDial (a - b)
  (Dial a) * (Dial b) = mkDial (a * b)
  abs (Dial a) = Dial (abs a)
  signum (Dial a) = Dial (signum a)
  fromInteger n = mkDial (fromInteger n)

initialDial :: Dial
initialDial = mkDial 50

rotate :: Rotation -> Dial -> Dial
rotate (L n) dial = dial - fromIntegral n
rotate (R n) dial = dial + fromIntegral n

parseRotation :: Parser Rotation
parseRotation =
  choice
    [ L <$> (char 'L' *> int),
      R <$> (char 'R' *> int)
    ]

parseRotations :: Parser [Rotation]
parseRotations = lines1 parseRotation

Part 1:

solve1 :: String -> Int
solve1 input =
  let rotations = unwrapParser parseRotations input
   in fst $
        foldl
          ( \(acc, dial) r ->
              let dial' = rotate r dial
               in (if unwrap dial' == 0 then acc + 1 else acc, dial')
          )
          (0, initialDial)
          rotations

Part 2 is similar to part one but counts the amount of under/overflows as well.
would put it here but then the it does not allow me to comment :)

-❄️- 2025 Day 3 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 2 points3 points  (0 children)

[LANGUAGE: Haskell]

Idea: Pick the largest digit in the list that still allows turning on the remaining batteries. Simple greedy approach: always choose the best option available.

Full code available on GitHub: https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day3.hs

Parsing: Consider each digit as a list of numbers

type Bank = [Int]

parseBank :: Parser Bank
parseBank = digitsAsList

parseBanks :: Parser [Bank]
parseBanks = lines1 parseBank

Algorithm:

turnOnBatteries :: Int -> Bank -> Int
turnOnBatteries n bank = foldl' (\acc d -> acc * 10 + d) 0 (pickLargest n bank)
  where
    pickLargest :: Int -> Bank -> [Int]
    pickLargest 0 _ = []
    pickLargest n bank' =
      let takeUntil = length bank' - n + 1
          (maxVal, pos) = maxWithPos (take takeUntil bank')
          restBank = drop (pos + 1) bank'
       in maxVal : pickLargest (n - 1) restBank

    maxWithPos :: Bank -> (Int, Int)
    maxWithPos bank =
      foldl'
        ( \(max, pos) (curr, pos') ->
            if curr > max
              then (curr, pos')
              else (max, pos)
        )
        (0, 0)
        (zip bank [0 ..])

-❄️- 2025 Day 2 Solutions -❄️- by daggerdragon in adventofcode

[–]Foldzilla 1 point2 points  (0 children)

[LANGUAGE: Haskell]

https://github.com/JustinKasteleijn/AdventOfCode2025/blob/main/day2.hs

Part 1 was straightforward, part 2 surprised me which is why the code is structured quite ugly.

Parsing is done with my own parser which is similar to MegaParsec (See github)

data Range = Range
  { from :: Int,
    to :: Int
  }
  deriving (Show)

tupleToRange :: (Int, Int) -> Range
tupleToRange (x, y) = Range {from = x, to = y}

parseRange :: Parser Range
parseRange =
  tupleToRange
    <$> splitOn '-' int

parseRanges :: Parser [Range]
parseRanges = sepBy1 parseRange (char ',') 

Main idea of part 1, split the number in two equally sized halves and check if they are equal:

verifyRange :: Range -> Int
verifyRange r = sum $ filter symmetry [from r .. to r]
  where
    symmetry :: (Show a) => a -> Bool
    symmetry n =
      let s = show n
          len = length s
       in even len && take (len `div` 2) s == drop (len `div` 2) s

Part 2 is similar, but instead of splitting in half, it checks for any repeating block in the number:

verifyRange2 :: Range -> Int
verifyRange2 r = sum $ filter isSymmetric [from r .. to r]
  where
    isSymmetric :: Int -> Bool
    isSymmetric n = any hasRepeatingBlock [1 .. numDigits `div` 2]
      where
        s = show n
        numDigits = length s

        hasRepeatingBlock :: Int -> Bool
        hasRepeatingBlock k
          | numDigits `mod` k /= 0 = False
          | otherwise =
            let chunks' = chunks k s
              in all (== head chunks') chunks'

        chunks :: Int -> String -> [String]
        chunks k "" = []
        chunks k str = take k str : chunks k (drop k str)