Comparing

strict and lazy

Arnaud Spiwack

The sad truth

Most of the time: it doesn't even remotely matters

The happy truth

Can you spot the bug?

atomicPut :: Handle -> String -> IO ()
atomicPut h line =
  withMVar lock $ \_ -> do
    hPutStrLn h line

All you've got to do is call

or :: [Bool] -> Bool
or =  foldr (||) False

any :: (a -> Bool) -> [a] -> Bool
any p =  or . map p

Applicative functors 🤩

zipWith1 :: (a -> b) -> [a] -> [b]
zipWith2 :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith3 :: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
zipWith4 :: (a -> b -> c -> d -> e) -> [a] -> [b] -> [c] -> [d] -> [e]
zipWith5 :: (a -> b -> c -> d -> e -> f) -> [a] -> [b] -> [c] -> [d] -> [e] -> [f]





zipWith5 :: (a -> b -> c -> d -> e -> f) -> [a] -> [b] -> [c] -> [d] -> [e] -> [f]
zipWith5 f as bs cs ds es = f <$> as <*> bs <*> cs <*> ds <*> es
(<$>) :: (a -> b) -> [a] -> [b]
(<*>) :: [a -> b] -> [a] -> [b]

Matching lazy data is weird

“The answer to all questions in Haskell is ‘bottom’.”
— Simon Peyton Jones

f :: Bool -> Bool -> Int
f _    False = 1
f True False = 2
f _    _     = 3
f :: T a -> U a -> Int
f (TBool b1) (UBool b2) = 0
data U a where
  UChar :: Char -> U Char
  UBool :: Bool -> U Bool 
data T a where
  TInt :: Int -> T Int
  TBool :: Bool -> T Bool

Guarded recursion

map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (a:as) = f a : map f as

Memory leaks

Tail recursion

length :: [a] -> Integer
length [] = 0
length (_:as) = 1 + length as
length :: [a] -> Integer
length = go 0
  where
    go :: Integer -> [a] -> Integer
    go acc [] = acc
    go acc (_:as) = go (acc+1) as
length :: [a] -> Integer
length = go 0
  where
    go :: Integer -> [a] -> Integer
    go !acc [] = acc
    go !acc (_:as) = go (acc+1) as

Memory heisenbug

Observing a lazy value changes its state

This slide is intentionally left blank

Lazy IO

readFile :: FilePath -> IO String

Limitations:

Lazy IO

readFile :: FilePath -> IO String
lines :: String -> [String]
read :: Read a => String -> a
f :: Read a => FilePath -> IO [a]
f p = (map read . lines) <$> readFile p

Limitations:

Lazy deserialisation

…  { … , foo : { … }, … }  …
(…, Object (<thunk>, Field "foo" (Object …), <thunk>), …)

Observability

traceEvent "START PureCode" $
somePureCode

¿¿Where do I put

traceEvent "STOP PureCode"

??

Taking laziness seriously

Laziness has a real implementation cost

— Simon Peyton Jones (2003)

Keeps you honest

“Every call-by-value language has given into the siren call of side effects.”

— Simon Peyton Jones (2003)

https://slides.com/aspiwack/hx2020

Thank you for listening

Comparing strict and lazy

By Arnaud Spiwack

Comparing strict and lazy

Strict and lazy languages are often pitted against each other but rarely honestly compared. Let’s take a step back from the slogans and examine the trade-offs between lazy and strict. Not which is best: how they compare. — Most languages are strict. Haskell is very much the odd one out here. So it is not surprising that programmers in strict language sneer at Haskell’s laziness, nor that Haskell programmers feel compelled to defend this choice. But because of the adversarial nature of this debate, it ends up with cheap slogans such as “strict languages are more efficient”, “lazy languages compose better”. This doesn’t tend to be very enlightening. After all, the most likely answer, if maybe unsatisfactory is: it depends. The choice between strictness and laziness is, at the end of the day, as most things in engineering, a trade-off. And to make an informed choice between these two technologies, we need to know what the costs and benefit of each are. My goal, is to provide tools to make such an informed decision, by sharing, based on my experience as a programmer in both lazy languages and strict languages, strengths and weaknesses of both paradigm. If your looking for me to tell you which is best, you will be disappointed. But, on the plus side, there will definitely be some production horror stories.

  • 1,285