Yet Another Haskell Tutorial/Io/Solutions

Haskell
Yet Another Haskell Tutorial
Preamble
Introduction
Getting Started
Language Basics (Solutions)
Type Basics (Solutions)
IO (Solutions)
Modules (Solutions)
Advanced Language (Solutions)
Advanced Types (Solutions)
Monads (Solutions)
Advanced IO
Recursion
Complexity

The RealWorld Solution

edit

Actions

edit

Using if, we get something like:

main = do
  hSetBuffering stdin LineBuffering
  putStrLn "Please enter your name:"
  name <- getLine
  if name == "Simon" || name == "John" || name == "Phil"
    then putStrLn "Haskell is great!"
    else if name == "Koen"
           then putStrLn "Debugging Haskell is fun!"
           else putStrLn "I don't know who you are."

Note that we don't need to repeat the dos inside the ifs, since these are only one action commands.

We could also be a bit smarter and use the elem command which is built in to the Prelude:

main = do
  hSetBuffering stdin LineBuffering
  putStrLn "Please enter your name:"
  name <- getLine
  if name `elem` ["Simon", "John", "Phil"]
    then putStrLn "Haskell is great!"
    else if name == "Koen"
           then putStrLn "Debugging Haskell is fun!"
           else putStrLn "I don't know who you are."

Of course, we needn't put all the putStrLns inside the if statements. We could instead write:

main = do
  hSetBuffering stdin LineBuffering
  putStrLn "Please enter your name:"
  name <- getLine
  putStrLn
    (if name `elem` ["Simon", "John", "Phil"]
       then "Haskell is great!"
       else if name == "Koen"
              then "Debugging Haskell is fun!"
              else "I don't know who you are.")

Using case, we get something like:

main = do
  hSetBuffering stdin LineBuffering
  putStrLn "Please enter your name:"
  name <- getLine
  case name of
    "Simon" -> putStrLn "Haskell is great!"
    "John"  -> putStrLn "Haskell is great!"
    "Phil"  -> putStrLn "Haskell is great!"
    "Koen"  -> putStrLn "Debugging Haskell is fun!"
    _       -> putStrLn "I don't know who you are."

Which, in this case, is actually not much cleaner.

The IO Library

edit

A File Reading Program

edit

The code might look something like:

module DoFile where

import IO

main = do
  hSetBuffering stdin LineBuffering
  putStrLn "Do you want to [read] a file, ...?"
  cmd <- getLine
  case cmd of
    "quit"  -> do putStrLn ("Goodbye!")
                  return ()
    "read"  -> do doRead; main
    "write" -> do doWrite; main
    _       -> do putStrLn
                    ("I don't understand the command "
                     ++ cmd ++ ".")
                  main

doRead = do
  putStrLn "Enter a file name to read:"
  fn <- getLine
  bracket (openFile fn ReadMode) hClose
          (\h -> do txt <- hGetContents h
                    putStrLn txt)

doWrite = do
  putStrLn "Enter a file name to write:"
  fn <- getLine
  bracket (openFile fn WriteMode) hClose
          (\h -> do putStrLn
                      "Enter text (dot on a line by itself to end):"
                    writeLoop h)

writeLoop h = do
  l <- getLine
  if l == "."
    then return ()
    else do hPutStrLn h l
            writeLoop h

The only interesting things here are the calls to bracket, which ensure the that the program lives on, regardless of whether there's a failure or not; and the writeLoop function. Note that we need to pass the handle returned by openFile (through bracket to this function, so it knows where to write the input to).

Alternatively, we can make a version with readFile and writeFile that doesn't use bracket:

doRead = do
  putStrLn "Enter a file name to read:"
  fn <- getLine
  txt <- readFile fn
  putStr txt

doWrite = do
  putStrLn "Enter a file name to write:"
  fn <- getLine
  txt <- getWriteLines
  writeFile fn txt

getWriteLines = do
  l <- getLine
  if l == "."
    then return ""
    else do lines <- getWriteLines
            return (line++"\n"++lines)