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:
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:
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
Let's start by writing a helper function that converts a list of strings into a list of integers. We'll use it several times, so we'll put it in a module. Using what we know now, the function might look like this:
module Utilities where
stringsToInts :: [String] -> [Int]
stringsToInts strings =
if null strings then
[]
else
let
first = head strings
rest = tail strings
i = read first :: Int
in
i : stringsToInts rest
module Utilities where stringsToInts :: [String] -> [Int] stringsToInts strings = if null strings then [] else let first = head strings rest = tail strings i = read first :: Int in i : stringsToInts rest
Here's a main function that calls it:
import System.Environment
import Utilities
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 import Utilities main = do args <- getArgs let sortedArgs = sort $ stringsToInts args let alpha = head sortedArgs let omega = last sortedArgs putStrLn $ show alpha ++ "-" ++ show omega
When we run this code, we get a warning about head
and tail
being partial functions. Both can fail if passed an empty list, which makes them partial (works for some values) rather than total (works for all values). There are total equivalents of these functions, but they are more involved. For now, we silence the warning by running the script this way:
runhaskell -Wno-x-partial span.hs 9 5 13
runhaskell -Wno-x-partial span.hs 9 5 13
In a week's time, we are going to write this much shorter program:
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:
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:
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:
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:
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:
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:
See you next time.
Sincerely,
P.S. It's time for a haiku!