Skip to content

Instantly share code, notes, and snippets.

@monadplus
Last active October 17, 2019 18:40
Show Gist options
  • Save monadplus/657c88dd8df7442f679e8e8ea931085a to your computer and use it in GitHub Desktop.
Save monadplus/657c88dd8df7442f679e8e8ea931085a to your computer and use it in GitHub Desktop.
Space leak: when laziness bites us (Source: https://chrispenner.ca/posts/wc)

This code is hella slow and consumes ludicrous amounts of memory. But why ? Laziness shouldn't be a problem since we are using foldl'.

import Data.List
import Data.Char

simpleFold :: FilePath -> IO (Int, Int, Int)
simpleFold fp = do
    countFile <$> readFile fp

countFile :: String -> (Int, Int, Int)
countFile s =
    let (cs, ws, ls, _) = foldl' go (0, 0, 0, False) s
     in (cs, ws, ls)
  where
    go :: (Int, Int, Int, Bool) -> Char -> (Int, Int, Int, Bool)
    go (cs, ws, ls, wasSpace) c =
        let addLine | c == '\n' = 1
                    | otherwise = 0
            addWord | wasSpace = 0
                    | isSpace c = 1
                    | otherwise = 0
         in (cs + 1, ws + addWord, ls + addLine, isSpace c)

foldl' is only strict up to WHNF, it'll be strict in the structure of the tuple accumulator, but not the actual values!

How to solve it ? BangPattern (forcing evaluation seq)

{-# LANGUAGE BangPatterns #-}

...
    go :: (Int, Int, Int, Bool) -> Char -> (Int, Int, Int, Bool)
    go (!cs, !ws, !ls, !wasSpace) c =
        let addLine | c == '\n' = 1
                    | otherwise = 0
            addWord | wasSpace = 0
                    | isSpace c = 1
                    | otherwise = 0
         in (cs + 1, ws + addWord, ls + addLine, isSpace c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment