haskell

In Haskell, how to putStrLn works in foldLeft


TL;DR.

foldLeft (\_ x -> putStrLn x) (pure ()) ("2":."1":.Nil)

Why the above code outputs

1

rather than

2
1

Full question

I am doing exercise of fp-course
https://github.com/system-f/fp-course/blob/5e0848b4eacb3ddff65553ab5beb6447b73f61b3/src/Course/FileIO.hs#L97

I have figured 2 solutions for this.

printFiles ps = void . sequence $ (uncurry printFile) <$> ps
printFiles = foldLeft (\_ (fp, content) -> printFile fp content) (pure ())

The first is right and can print as question's ask.

>> :main "share/files.txt"
============ share/a.txt
the contents of a

============ share/b.txt
the contents of b

============ share/c.txt
the contents of c

The second print as follow.

>> :main "share/files.txt"
============ 
share/c.txt
the contents of c

It looks like pure () only collect the last printFile fp content of foldLeft.
How to make pure () aka IO () collects all putStrLn in foldLeft?


Solution

  • Because Haskell is referentially transparent, you can always answer such questions for yourself by evaluating the expression manually, i.e. inlining function calls and beta-reducing until it's obvious what's going on:

    foldLeft (\_ x -> putStrLn x) (pure ()) ("2":."1":.Nil)
      = {- (recursive) definition of `foldLeft` for Cons case -}
        foldLeft (\_ x -> putStrLn x)
           ((\_ x -> putStrLn x) (pure ()) "2")
           ("1":.Nil)
      = {- beta reduction -}
        foldLeft (\_ x -> putStrLn x)
           (putStrLn "2")
           ("1":.Nil)
      = {- definition of `foldLeft` for Cons case -}
        foldLeft (\_ x -> putStrLn x)
           ((\_ x -> putStrLn x) (putStrLn "2") "1")
           Nil
      = {- beta reduction -}
        foldLeft (\_ x -> putStrLn x)
           (putStrLn "1")  -- woops, the "2" has vanished in the `_` argument
           Nil
      = {- definition of `foldLeft` for Nil case -}
        putStrLn "1"
    

    So that's all you get: one print of "1". OTOH, with Willem Van Onsem's suggestion it will be

    foldLeft (\prev x -> prev >> putStrLn x) (pure ()) ("2":."1":.Nil)
      = {- definition of `foldLeft` for Cons case -}
        foldLeft (\prev x -> prev >> putStrLn x)
           ((\prev x -> prev >> putStrLn x) (pure ()) "2")
           ("1":.Nil)
      = {- beta reduction -}
        foldLeft (\prev x -> prev >> putStrLn x)
           (pure () >> putStrLn "2")
           ("1":.Nil)
      = {- definition of `foldLeft` for Cons case -}
        foldLeft (\prev x -> prev >> putStrLn x)
           ((\prev x -> prev >> putStrLn x) (pure () >> putStrLn "2") "1")
           Nil
      = {- beta reduction -}
        foldLeft (\prev x -> prev >> putStrLn x)
           ((pure () >> putStrLn "2") >> putStrLn "1")
           Nil
      = {- definition of `foldLeft` for Nil case -}
        pure () >> putStrLn "2" >> putStrLn "1"