Week 12: Notes

records
input and output
I/O actions
files and streams
command-line programs

You can read about these subjects in Learn You a Haskell, chapters 8 and 9.

do

In Haskell, a do block is like a generalized list comprehension.

As a first example, we may use do with ordinary lists. Consider this list comprehension:

> list = [(x, y) | x <- [1..2], y <- [1..3]]
> list
[(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)]

We may alternatively write it using do notation:

list = do
    x <- [1..2]
    y <- [1..3]
    return (x, y)

We see that in a do block, we can use the <- operator to retrieve a value. At the end of the do block, we can use the return function to produce a value to be returned. When we use do with ordinary lists, we retrieve values from lists, and the return values are joined together into a list.

We may also use do with Maybe. Here's a function that takes three values of type (Maybe Int), and returns their sum:

add :: Maybe Int -> Maybe Int -> Maybe Int -> Maybe Int
add mx my mz = do
    x <- mx
    y <- my
    z <- mz
    return (x + y + z)

Let's try it:

> add (Just 3) (Just 4) (Just 5)
Just 12
> add (Just 3) Nothing (Just 5)
Nothing

Notice that if any of the input values is absent, the result is Nothing. To see why this is true, recall that a Maybe behaves like a list that has 0 or 1 elements, and notice that the following do block will produce an empty list:

list = do
    x <- [3]
    y <- []
    z <- [5]
    return (x + y + z)

A do block with Maybe values can be quite useful for error handling. Suppose that we want to call several functions, any of which might fail and return Nothing. If we place these calls in a do block, then it will return Nothing if any of the function calls failed.

As we saw in the lecture (and you can also read about in Learn You a Haskell), do blocks also work with I/O objects. In fact do blocks will work with any instance of a type class called Monad, which plays a fundamental role in advanced Haskell programming. [], Maybe and IO are all instances of Monad. In the next lectures we'll learn more about the Monad type class, and will learn how to make our own types that work with do.

tutorial solutions

1. Rotation

Write a Haskell program that reads a rectangular matrix of integers from standard input.

First solution:

import Data.List

main :: IO ()
main = do
  s <- getContents
  let ls :: [String] = lines s
  let m :: [[String]] = map words ls
  let m' = transpose (reverse m)
  let t = unlines (map unwords m')
  putStr t

Solution using interact:

import Data.List

main :: IO ()
main = interact
  (unlines . map unwords . transpose . reverse . map words . lines)

3. Grep

Write a Haskell program that is like the command-line utility 'grep'. It should take two command-line arguments: a string to search for, plus a filename. It should print out all lines in the file that contain the given string.

import Data.List
import System.Environment

main :: IO ()
main = do
  args <- getArgs
  case args of
    [s, filename] -> do
      text <- readFile filename
      let matches = filter (isInfixOf s) (lines text)
      putStr (unlines matches)
    _ -> putStrLn "usage: runghc grep.hs <str> <filename>"

5. Rock, Paper, Scissors (partial solution, Adam Dingle's tutorial only)

import Data.List
import Data.Ord

data Move = Rock | Paper | Scissors
  deriving Eq

type Strategy = [Move] -> Move

beats :: Move -> Move
beats Rock = Paper
beats Paper = Scissors
beats Scissors = Rock

-- a) Write a Strategy that plays whatever will beat the opponent's last move.

beats_last :: Strategy
beats_last (m : _) = beats m

-- b) Write a Strategy that chooses the move that the opponent
--    has played most often in the past, and plays whatever will beat that.

all_moves = [Rock, Paper, Scissors]

beats_most :: Strategy
beats_most moves =
  let count move = length (filter (== move) moves) in
      beats (maximumBy (comparing count) all_moves)

6. Fox and Hounds (partial solution, Vít Šefl's tutorial only)

import Data.List

-- a) Create a type Game representing the state of this game.
--    Also create a value start :: Game representing the initial state of the game.

type Pos = (Int, Int)

-- Bool: true if fox's turn to play
-- [Pos] = position of fox : positions of hounds
data Game = Game (Bool, [Pos])

start :: Game
start = Game (True, [(7, 0), (0, 1), (0, 3), (0, 5), (0, 7)])

-- b) Write a function show_game :: Game → String that produces
--    a visual representation of the board.

show_row_col :: Game -> Int -> Int -> String
show_row_col (Game (__, positions)) r c =
    case elemIndex (r, c) positions of
      Just 0 -> "F"
      Just _ -> "H"
      Nothing ->
        if odd (r + c) then "." else " "

-- show 1 row of the game
show_row :: Game -> Int -> String
show_row game r =
  unwords [show_row_col game r c | c <- [0..7]]

show_game :: Game -> String
show_game game = unlines [show_row game i | i <- [0..7]]