I expect the following behavior from the applicative instance of my ZipList':
zipListApplyTest = fs <*> xs
where fs = ZipList' [negate, id]
xs = ZipList' [1..5]
-- Result: ZipList' [-1,2]
This was my first attempt:
newtype ZipList' a = ZipList' [a]
deriving (Eq, Show)
instance Functor ZipList' where
fmap f (ZipList' xs) = ZipList' $ fmap f xs
instance Applicative ZipList' where
pure x = ZipList' [x]
ZipList' (f:fs) <*> ZipList' (x:xs) =
ZipList' $ f x : (fs <*> xs) -- <-- the bug is here
ZipList' [] <*> _ = ZipList' []
_ <*> ZipList' [] = ZipList' []
-- Unexpected result: ZipList' [-1,2,3,4,5]
After some head scratching, I realized that inside the applicative instance of ZipList' I accidentally used the wrong <*>:
In the line marked with the bug is here, I applied the <*> that belongs to the built-in list type [] instead of applying <*> of ZipList' recursively.
This is why the second function id was applied to the entire rest of the list, instead of only the second element, 2.
This yielded the expected result:
ZipList' fs <*> ZipList' xs = ZipList' $ zipApply fs xs
where zipApply :: [(a -> b)] -> [a] -> [b]
zipApply (f:fs) (x:xs) = f x : zipApply fs xs
zipApply _ _ = []
Is there a compiler flag, language idiom, or other technique that would have prevented this bug or would have made it easier to spot?
I'm on GHC 8.2.2.
<*>that belongs to the built-in list type?"the bug is herethe apply method (<*>) of[]'s applicative type class instance is called. With "built-in" I meant the list type frombase.pureforZipListshould berepeat, and not(:[])-- to see why, try to verify the applicative laws. (Great question, by the way!)