Lecture: Haskell Mains

Dear Computer

Chapter 7: Immutability and I/O

Lecture: Haskell Mains

Dear students:

The way to get better at new things is to actively throw yourself at them. Haskell's IO system and immutability is different, so we'll spend this lecture throwing ourselves at a series of small problems that require input and output.

power2

Write a program that asks the user for a integer \(n\) and prints \(2^n\).

Here's one possible solution:

Haskell
main = do
  text <- getLine
  let n = read text :: Int
  print $ 2 ^ n
main = do
  text <- getLine
  let n = read text :: Int
  print $ 2 ^ n

multiplier

Write a program that multiplies two numbers entered by the user.

Here's one possible solution:

Haskell
main = do
  lineX <- getLine
  let x = read lineX :: Int
  lineY <- getLine
  let y = read lineY :: Int
  print $ x * y
main = do
  lineX <- getLine
  let x = read lineX :: Int
  lineY <- getLine
  let y = read lineY :: Int
  print $ x * y

span

Write a program that receives a bunch of integers as command-line arguments. It prints the range of the numbers. For example:

> runhaskell main.hs 7 3 80 9
3-80
> runhaskell main.hs 7 3 80 9
3-80

Here's one possible solution:

Haskell
import System.Environment

stringsToInts :: [String] -> [Int]
stringsToInts strings =
  if null strings then
    []
  else
    i : stringsToInts rest
    where
      first = head strings
      rest = tail strings
      i = read first :: Int

main = do
  args <- getArgs
  let sortedArgs = sort $ stringsToInts args
  let alpha = head sortedArgs
  let omega = last sortedArgs
  putStrLn $ show alpha ++ "-" ++ show omega
import System.Environment

stringsToInts :: [String] -> [Int]
stringsToInts strings =
  if null strings then
    []
  else
    i : stringsToInts rest
    where
      first = head strings
      rest = tail strings
      i = read first :: Int

main = do
  args <- getArgs
  let sortedArgs = sort $ stringsToInts args
  let alpha = head sortedArgs
  let omega = last sortedArgs
  putStrLn $ show alpha ++ "-" ++ show omega

In a week's time, we are going to write this much shorter program:

Haskell
main = do
  args <- getArgs
  let sortedArgs = sort $ map read args :: [Int]
  let alpha = head sortedArgs
  let omega = last sortedArgs
  putStrLn $ show alpha ++ "-" ++ show omega
main = do
  args <- getArgs
  let sortedArgs = sort $ map read args :: [Int]
  let alpha = head sortedArgs
  let omega = last sortedArgs
  putStrLn $ show alpha ++ "-" ++ show omega

sum

Write a program that prints an equation summing up the command-line arguments. One side shows the addition expression, while the other shows the total.

Here's one possible solution:

Haskell
import System.Environment
import Data.List

stringsToInts :: [String] -> [Int]
stringsToInts ss =
  if null ss then
    []
  else
    (read $ head ss :: Int) : (stringsToInts $ tail ss)

main = do
  args <- getArgs
  putStr $ intercalate " + " args
  putStr " = "
  print $ sum $ stringsToInts args
import System.Environment
import Data.List

stringsToInts :: [String] -> [Int]
stringsToInts ss =
  if null ss then
    []
  else
    (read $ head ss :: Int) : (stringsToInts $ tail ss)

main = do
  args <- getArgs
  putStr $ intercalate " + " args
  putStr " = "
  print $ sum $ stringsToInts args

number

Write a program that echoes the lines of a file, prefixing each line with its number. The file path is passed as a command-line argument.

Here's one possible solution:

Haskell
import System.Environment

printNumberedLines :: Int -> [String] -> IO ()
printNumberedLines i lines =
  if null lines then
    return ()
  else do
    putStr $ show i
    putStr " "
    putStrLn $ head lines
    printNumberedLines (i + 1) $ tail lines

main = do
  args <- getArgs
  let path = head args
  text <- readFile path
  let rows = lines text
  printNumberedLines 0 rows
import System.Environment

printNumberedLines :: Int -> [String] -> IO ()
printNumberedLines i lines =
  if null lines then
    return ()
  else do
    putStr $ show i
    putStr " "
    putStrLn $ head lines
    printNumberedLines (i + 1) $ tail lines

main = do
  args <- getArgs
  let path = head args
  text <- readFile path
  let rows = lines text
  printNumberedLines 0 rows

cat

Write a program like cat that dumps all files passed as command-line arguments to standard out.

Here's one possible solution:

Haskell
import System.Environment

catFiles :: [String] -> IO ()
catFiles files = do
  if null files then
    return ()
  else do
    text <- readFile $ head files
    putStr text
    catFiles $ tail files

main = do
  args <- getArgs
  catFiles args
import System.Environment

catFiles :: [String] -> IO ()
catFiles files = do
  if null files then
    return ()
  else do
    text <- readFile $ head files
    putStr text
    catFiles $ tail files

main = do
  args <- getArgs
  catFiles args

spell

Write a program that spells out the phrase passed as a command-line argument, one character at a time. Use the say utility, which is builtin on macOS. Linux has espeak. I don't know about Windows.

Here's one possible solution:

Haskell
import System.Environment
import System.Process

spell :: String -> IO ()
spell chars =
  if null chars then
    return ()
  else do
    if head chars == ' ' then
      system $ "say space"
    else do
      system $ "say " ++ [head chars]
    spell $ tail chars

main = do
  args <- getArgs
  let chars = head args
  spell chars
import System.Environment
import System.Process

spell :: String -> IO ()
spell chars =
  if null chars then
    return ()
  else do
    if head chars == ' ' then
      system $ "say space"
    else do
      system $ "say " ++ [head chars]
    spell $ tail chars

main = do
  args <- getArgs
  let chars = head args
  spell chars

Advent of Code 2015, Day 1

Write a program that solves Advent of Code 2015, Day 1. Accept the input text as a command-line argument.

Here's one possible solution:

Haskell
import System.Environment

deliver :: String -> Int -> Int
deliver directions floor =
  if directions == "" then
    floor
  else
    deliver rest floor'
    where
      first = head directions
      rest = tail directions
      offset = if first == '(' then 1 else -1
      floor' = floor + offset

main = do
  args <- getArgs
  let directions = head args
  print $ deliver directions 0
import System.Environment

deliver :: String -> Int -> Int
deliver directions floor =
  if directions == "" then
    floor
  else
    deliver rest floor'
    where
      first = head directions
      rest = tail directions
      offset = if first == '(' then 1 else -1
      floor' = floor + offset

main = do
  args <- getArgs
  let directions = head args
  print $ deliver directions 0

Note that this follows the fold pattern. We could write a much simpler solution using tools we discuss next week.

TODO

Here's your list of things to do before we meet next:

Complete the middle quiz as desired.
There's a ready date coming up. There's generally a two- or three-week gap between dates, but I extend this one to the end of spring break because students have requested that. Feel free to submit before break so that you can have peace over break.
Also, Duo is getting replaced by Okta. If you have issues submitting, use the direct submission link that I will share on Discord. It will only work if the textbook remembers who you are. (Access the book from Canvas before next week, and don't delete your cookies.) If that too fails, just message me on Discord.
Keep learning Haskell from videos and books.

See you next time.

Sincerely,

P.S. It's time for a haiku!

They said the dog's fixed But I taught him to not bark Guess he's mutable
← AcrosticLab: Haskell Mains →