I am trying to learn haskell through the advent of code (I know, it's late in the season), but I am encountering indentation issues I do not understand, despite checking what I believed to be the relevant sections on haskell.org or wikibooks.
Specifically, while indenting my inner auxiliary function, haskell will send a parse error if I indent by 4 or less spaces (not counting the 4 spaces of indentation of the main block), but will work if indenting by at least 5 additional spaces. So I need to indent my let ... in by 9 spaces at least. However indenting the main block by only 4 spaces and not 5 seems to work fine, and I do not understand the difference.
So this works :
solvePart1 :: String -> IO ()
solvePart1 input = do
let parsedInput = map (map read . words) . lines $ input :: [[Int]]
let validateReport report =
let differences = zipWith (-) report (tail report)
in all (\d -> d >= 1 && d <= 3) differences || all (\d -> d <= -1 && d >= -3) differences
let areReportsValid = map validateReport parsedInput
let validReportsCount = sum $ map fromEnum areReportsValid
putStrLn $ "Day 2, Part 1: " ++ show validReportsCount
but this does not :
solvePart1 :: String -> IO ()
solvePart1 input = do
let parsedInput = map (map read . words) . lines $ input :: [[Int]]
let validateReport report =
let differences = zipWith (-) report (tail report)
in all (\d -> d >= 1 && d <= 3) differences || all (\d -> d <= -1 && d >= -3) differences
let areReportsValid = map validateReport parsedInput
let validReportsCount = sum $ map fromEnum areReportsValid
putStrLn $ "Day 2, Part 1: " ++ show validReportsCount
The issue is not related to the do vs let ... in syntax, as rewriting the inner function as a do block does not change the issue : 5 spaces are still required.
(I am using vscode as an editor and building with stack in case it is relevant)
To some extent, it is the same as:
solvePart1 input = do
let parsedInput = map (map read . words) . lines $ input :: [[Int]]
-- …
versus:
solvePart1 input = do
let parsedInput = map (map read . words) . lines $ input :: [[Int]]
-- …
The item needs to be one column to the right of the start of the identifier that defines the scope. So the let
itself does not count, indeed, for example the same does not work either:
let x y =
let tmp = y
in tmp
in x 5
It works if the let
is one column more to the right than x
:
let x y =
let tmp = y
in tmp
in x 5
That being said, typically you don't use a lot of let
s in a do
block, but work with a function without a do
block, like:
validateReport :: [Int] -> Bool
validateReport report = all (\d -> d >= 1 && d <= 3) differences || all (\d -> d <= -1 && d >= -3) differences
where
differences = zipWith (-) report (tail report)
solve :: [[Int]] -> Int
solve parsedInput = sum (map fromEnum (map validateReport parsedInput))
and then solve it with:
solvePart1 :: String -> IO ()
solvePart1 input = do
let parsedInput = map (map read . words) . lines $ input :: [[Int]]
putStrLn $ "Day 2, Part 1: " ++ show (solve parsedInput)