A common pattern is for_
of a list enumeration, like
for_ [0 :: Int .. 4] $ \i ->
print i
-- 0
-- 1
-- 2
-- 3
-- 4
The pattern works well but it doesn’t really express what we want. We don’t care to actually make a list; we just want to iterate over the increasing sequence of numbers. In practice GHC probably optimizes all this away to something much nicer, but it’s risky to rely on it: if [0 .. 1000 * 1000]
occurs twice in our program then GHC may float it to the top level and materialise it. That’s a space leak!
Maybe we should have a Range
type instead? For example, see below. I’m actually surprised I’ve never seen anything like this before. Of course, it can be adjusted to incorporate different step sizes too (including negative step sizes). What do folks think of it?
import Data.Foldable
data Range a where
MkRange :: (Enum a, Ord a) => !a -> !a -> Range a
instance Foldable Range where
foldMap f (MkRange i j) =
if i > j
then mempty
else f i <> foldMap f (MkRange (succ i) j)
example =
for_ (MkRange @Int 0 4) $ \i ->
print i
-- ghci> example
-- 0
-- 1
-- 2
-- 3
-- 4