I was taking a look at some prelude functions to teach a workmate about recursion and I found that some are written in rather a weird way. Example:
{-# NOINLINE [1] zipWith #-}
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f = go
where
go [] _ = []
go _ [] = []
go (x:xs) (y:ys) = f x y : go xs ys
Why is it written as a call to go
where go
is defined right afterwards? IMO a more natural way to define it is:
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith _ [] _ = []
zipWith _ _ [] = []
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
I guess it is due to some inline / loop-fusion / lazy / whatever-feature that allow GHC
to optimize better, but this is the point in which Haskell becomes quite obscure to me. Can anyone explain (as easier as possible) the reason for such a function definition, if there is any.
Edits
Many comments, @AndrewRay's answer and in this question, point in the direction of inlining as the reason for such an auxiliar local function. Nevertheless, zipWith
is marked with the pragma NOINLINE [1] zipWith
, which, up to GHC's user guide: 7.13.5.5. Phase control, means to not inline before first phase and maybe inline afterwards (for whatever this means). In the linked question, the PO refers to foldr
which is implemented using the same trick but without any PRAGMA. I'd argue that the author is avoiding inlining but my knowledge in this is small.
Edit 2
added link to source definition of zipWith