Reputation: 309
During my solving of AdventOfCode 2018 day 1 question 2 I ran into some weird behavior. The following code works as intended, and prints out the correct solution.
day1_2 = do
content <- day1
let content = scanl (+) 0 content
return $ dup content
where
dup xs = dup' xs []
where
dup' [] _ = Nothing
dup' (x : xs) seen =
if x `elem` seen then Just x else dup' xs (x : seen)
It works, but I wanted to clean it up a little. As soon as I removed the second binding of content
, and inlined the function call, the code stopped working. Since the dup
function doesn't change I've omitted it from the rest of the snippets.
day1_2 = do
content <- day1
return $ dup $ scanl (+) 0 content
My understanding of Haskell tells me those two functions should give identical results, but they don't. On the input I've been given the first function returns Just 0
while the second returns Nothing
. Is this a bug, or am I missing something?
Reducing the code even more still keeps the behavior from the second function.
day1_2 = dup . scanl (+) 0 <$> day1
Here day1
just reads the input given for day 1, and makes it parsable to Haskell.
day1 =
map (read . (\n -> if head n == '+' then tail n else n))
. words
<$> readFile "input/day1.txt"
Further weirdness:
day1_2 = do
content <- day1
let content' = scanl (+) 0 content
return $ dup content
The above gives me Just 6
. (Not the desired result, but that is expected)
day1_2 = do
content <- day1
let smth = scanl (+) 0 content
return $ dup smth
The above, however, gives me Nothing
even though it's functionally equivalent to the first snippet, save for some naming differences. From these last tests it's obvious that the content
binding in the original is shadowed. However, if no shadowing is involved then the wrong output is given.
Upvotes: 0
Views: 85
Reputation: 29193
Your original code is broken.
do content <- day1
let content = scanl (+) 0 content
_etc
Does not mean "bind the result of day1
to content
, apply scanl (+) 0
to that, and bind that to content
". Actually, what you've written is the same as this:
do day1
let content = scanl (+) 0 content
_etc
Which has you executing day1
, discarding its result, and then binding to content
a recursively defined list. You're correct that content
is being shadowed, but you didn't realize that the second content
shadowed the first one in its own definition. If you type let content = scanl (+) 0 content in content
, you'll actually see you have an infinite list of 0
s. It should be obvious why you then get Just 0
as an "answer."
The "correct" solution is actually the final one.
day1_2 = do content <- day1
let smth = scanl (+) 0 content
return $ dup smth
-- don't need do notation; just functor-function-application
day1_2 = dup <$> scanl (+) 0 <$> day1
But you are missing one crucial piece of the question:
Note that your device might need to repeat its list of frequency changes many times before a duplicate frequency is found
You need just one call to a single standard library function to make this work. See if you can find it :). (Fair warning: once you find the function and use it, this solution takes a good few minutes to finish running, but it does work.)
day1_2 = dup <$> scanl (+) 0 <$> _libfunction <$> day1
Upvotes: 1
Reputation: 6703
When you write let content = scanl (+) 0 content
, the second content
is not the content
declared in the previous line, but content
declared in this line using let
.
So the first code just ignores content
gotten from day1
, but the second code uses it. That's why these two behave differently.
Upvotes: 1