Real World Haskell Revived

Haskell is considered too theoretical by many, but Bryan, Don, and John, begged to differ by writing Real World Haskell. The book was written in collaboration with over 800 online commenters and released back to the public for free.

The book was such a success that it is still read by newcomers eighteen years later, but it is starting to show its age. So, I have now converted it to markdown and started modernizing it:

62 Likes

Great news, and there was another attempt: GitHub - tssm/up-to-date-real-world-haskell: I'm trying to update the Real World Haskell book · GitHub

4 Likes

Yes, I should have said that I am building on their work. I have now added it to the acknowledgments.

4 Likes

Thank you for your work on this! I remember re-reading the monad transformers chapter a few times. Alas, for newcomers this new spot for the book might be a bit hard to find, but I’m not sure what to do about that.

I’ve gone through some of the chapters and I noticed that in chapter 3 the exercises at the end are enumerated 1-7, then after the code block they start with 1 again. I guess that’s a formatting error?

On another note, I couldn’t quite believe the statement in chapter 0 regarding minima k xs = take k (sort xs) being more performant due to laziness. I found this which explains the reasoning, but when I actually tried it, I saw no runtime difference between sort [1..100000000] and take 3 $ sort [1..100000000].

1 Like

How did you measure it?

Thank you, fixed!

Strange, I also cannot see a significant speedup. Even when compiled with optimization using tasty-bench:

{- cabal:
build-depends: base, tasty-bench
-}

import Test.Tasty.Bench
import Data.List (sort)

main = defaultMain
  [ bench "kminima" (whnf (take 3 . sort) xs)
  , bench "sort" (nf sort xs)
  ] where
  xs = [1..100000 :: Int]

GHC 9.10.3 by the way.

2 Likes

I just ran time ./Main a few times so not in a very rigorous way I guess.

This was my attempt:

module Main where

import System.Environment
import Data.List (sort)

runSortOnly :: IO ()
runSortOnly = do
    let !s = sort [1..100000000]
    return ()

runTakeSort :: IO ()
runTakeSort = do
    let !s = take 3 $ sort [1..100000000]
    return ()

main :: IO ()
main = do
    args <- getArgs
    case args of
        [] -> putStrLn "sort only" >> runSortOnly
        _  -> putStrLn "sort + take" >> runTakeSort

Both code paths take ~15s on my machine.

1 Like

Isn’t the problem that you’re using WHNF? I would think that that would computer the same thing in both cases, and you’d only see a difference with full nf

Possibly [1..1000000] is a case where sort is almost linear anyway? Maybe if you try it with the same list in reverse, or random numbers (I know that sort does a lot of work to try and detect runs)

4 Likes

That’s it! Reversing didn’t work, but pseudo random numbers do show the difference:

{- cabal:
build-depends: base, tasty-bench
-}

import Test.Tasty.Bench
import Data.List (sort)

lcg :: Int -> Int
lcg n = n * 313 + 1 `mod` 1000

main = defaultMain
  [ bench "kminima" (whnf (take 3 . sort) xs)
  , bench "sort" (nf sort xs)
  ] where
  xs = take 100000 (iterate lcg 0)
All
  kminima: OK
    4.32 ms ± 316 μs,  10 MB allocated, 7.1 MB copied,  25 MB peak memory
  sort:    OK
    62.2 ms ± 2.2 ms, 112 MB allocated,  43 MB copied,  25 MB peak memory
5 Likes

I am trying to grasp this comment. I guess I just don’t have an intuitive understanding of this topic yet :frowning:

I think I am close to understanding it now that I tried this in ghci:

ghci> xs <- getRandomList
ghci> !a = sort xs
ghci> !b = take 3 $ sort xs
ghci> :sprint a
a = 2 : _
ghci> :sprint b
b = 2 : _

I had thought let !s = ... would completely evaluate s, but I guess it only evaluates the head of the list (i.e. the list data constructor is in WHNF? I am not completely sure where I am “using WHNF”)?

Is this correct? How could I easily “use NF” then?

Yes, as you can see with the :sprint command in GHCi, using bang patterns (the ! syntax) only evaluates the first constructor of the list.

To evaluate the list completely you have to produce a value that depends on all of the list constructors. You can do that by traversing the whole list and produce a dummy result (in the unit type):

ghci> go [] = (); go (_:xs) = go xs
ghci> go $ sort xs
()

You can also define that using foldr:

ghci> foldr (\_ y -> y) () $ sort xs

Also note that the elements of the list might still not be evaluated. You can use the seq function to evaluate that to WHNF.

ghci> foldr seq () $ sort xs
1 Like

I would recommend these tutorials for more info:

I wonder if something like that could be included in the modernized real world book.

8 Likes

Once this project is closer to complete it could be added to the haskell.org homepage as a resource.

5 Likes

Hi! Thanks for starting this. Is there any plan for larger extensions of the original contents? I thought I’d submit a chapter or two.

1 Like

As long as they fit with the theme of teaching Haskell through practical examples, I’m all for additional chapters. I also wouldn’t want beginners to find the length of the book daunting, but maybe we can split it off into multiple volumes if that becomes a real concern.

3 Likes

Do you have a plan for deployment as a website, or do you intend the readers to use the Codeberg file viewer?

1 Like

I guess that’s the goal. Hosting shouldn’t be hard to find, for a bare minimum Codeberg has some pages support now. I can provide some hosting VMs too.

1 Like

I think perhaps the best way to use the book is to clone the repository and read it in VSCodium with the extensions mentioned in the contributing section in the readme. That way you have easy access to the code examples and they can also easily write their own exercise solutions or open a ghci repl. I have added a todo to write up a little tutorial for setting that up.

A static website would still be nice to use as a reference or on mobile devices. I’m considering https://www.mkdocs.org/ for that purpose, but this is outside my area of expertise so I am open for suggestions. Thanks @exaexa for offering to help with hosting. I will probably first try Codeberg pages and see how well that works.

2 Likes

Is there a roadmap or a timeline?