Acrostic
Suppose you're writing a program that prompts users to write an acrostic poem. The theme of the poem is passed as a command-line argument, and then the program prompts the user to enter words that start with the theme's letters. For example, here's an acrostic that the user might enter for the theme COFFEE
:
> runhaskell acrostic.hs COFFEE
Costly
Overpriced
Fanatics
Fetish
Expensive
Elitism
The program prints the first letters. The user only inputs ostly
, verpriced
, and so on.
The first task is to get the theme from the command-line arguments. The getArgs
function from the System.Environment
module provides these and has this type signature:
getArgs :: IO [String]
Separate runs of the program will have different command-line arguments. Because it doesn't give back the same result every single time you call it, the getArgs
function is impure. Its returned value must be unpacked with the <-
operator inside a do
block:
import System.Environment
main = do
args <- getArgs
let theme = head args
print theme
Next we need a function that recurses through the theme, one character at a time. When the theme is entirely consumed, we end the recursion with a return ()
. Otherwise we print the first character, prompt the user to fill in a word that starts with that character, and then recurse on the remaining characters. The cue
function in this program does the trick:
import System.Environment
cue :: String -> IO ()
cue theme =
if theme == "" then
return ()
else do
putChar (head theme)
getLine
cue (tail theme)
main = do
args <- getArgs
let theme = head args
cue theme
Summary
Our imperative programming experiences suggest that mutability is necessary for computation, but Haskell demonstrates that it is not. Many functions are pure, meaning that their behavior is entirely predetermined by their parameters. They do not rely on persistent and modifiable state. Functions that do rely on and effect state changes, like I/O and random number generators, may be written to accept the incoming state as a parameter and return the new outgoing state. Haskell's do
notation hides the ugliness of passing around this modified state. It doesn't hide everything, however. Data returned from an impure function is boxed up in an IO
wrapper. The box may only be opened inside an impure context like another do
block. Using do
and IO
, programmers write Haskell programs that look similar to the ones they'd write in imperative languages, but without the dangers of mutability.