We want to be able to:
But first, let's greet the user.
-- Hello.hs
module Main where
main :: IO ()
main = putStrLn "Hello user!"
$ runghc Hello.hs
Hello user!
module Main where
main :: IO ()
main = putStrLn "Hello user!"
::
means "type of"=
means equality (the two sides are interchangeable).putStrLn
is String -> IO ()
IO ()
.module Main where
main :: IO ()
main = putStrLn "Hello user!"
IO a
means This is a description of a subroutine which when run, may perform IO actions and in the end will return a value of type amain
is the name of the entry point of the program.main
and will run it.module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
let out = "Nice to meet you, " ++ name ++ "!"
putStrLn out
$ runghc Hello.hs
"Hello! What is your name?"
suppi
"Nice to meet you, suppi!
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
let out = "Nice to meet you, " ++ name ++ "!"
putStrLn out
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
let out = "Nice to meet you, " ++ name ++ "!"
putStrLn out
do
is a special syntax that lets us sequence IO actionsgetLine
is IO String
IO String
means This is a description of a subroutine which when run, may perform IO operations and in the end will return a value of type String
getLine
produces a String
by taking a line from the standard inputmodule Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
let out = "Nice to meet you, " ++ name ++ "!"
putStrLn out
getLine
is IO String
name
is String
<-
is special syntax that can only appear in do
notation.<-
means run the subroutine and bind the value it produces to the name on the left side of <-
let <name> = <expr>
means that the <name>
is interchangeable with <expr>
for the rest of the do
blockdo
notation, let
does not need the accompanying in
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
let out = "Nice to meet you, " ++ getLine ++ "!"
putStrLn out
Hello.hs:6:37: error:
• Couldn't match expected type ‘[Char]’
with actual type ‘IO String’
• In the first argument of ‘(++)’, namely ‘getLine’
In the second argument of ‘(++)’, namely ‘getLine ++ "!"’
In the expression: "Nice to meet you, " ++ getLine ++ "!"
|
6 | let out = "Nice to meet you, " ++ getLine ++ "!"
| ^^^^^^^
IO String
in place of String
String
is defined as type String = [Char]
String
which was expected, with IO String
which is the type of getLine
IO a
and a
are different typesmodule Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
putStrLn "Nice to meet you, " ++ name ++ "!"
Hello.hs:7:3: error:
• Couldn't match expected type ‘[Char]’ with actual type ‘IO ()’
• In the first argument of ‘(++)’, namely
‘putStrLn "Nice to meet you, "’
In a stmt of a 'do' block:
putStrLn "Nice to meet you, " ++ name ++ "!"
In the expression:
do putStrLn "Hello! What is your name?"
name <- getLine
putStrLn "Nice to meet you, " ++ name ++ "!"
|
7 | putStrLn "Nice to meet you, " ++ name ++ "!"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Parenthesis are needed around the expression string
putStrLn ("Nice to meet you, " ++ name ++ "!")
We want to be able to:
How should we
We can store the items in a linked list.
type Item = String
type Items = [Item]
We can save the items in a linked list.
type Item = String
type Items = [Item]
How can we refer to an item?
We can save the items in a linked list.
type Item = String
type Items = [Item]
How can we refer to an item?
By its index in the list
We can save the items in a linked list.
type Item = String
type Items = [Item]
How can we refer to an item?
By its index in the list
What will the operations we want to do look like?
-- Returns a new list of Items with the new item in it
addItem :: Item -> Items -> Items
-- Returns a string representation of the items
displayItems :: Items -> String
-- Returns a new list of items or an error message if the index is out of bounds
removeItem :: Int -> Items -> Either String Items
We use Either
to mark a possible failure
-- Returns a new list of Items with the new item in it
addItem :: Item -> Items -> Items
addItem item items = item : items
-- Returns a string representation of the items
displayItems :: Items -> String
displayItems items =
let
displayItem index item = show index ++ " - " ++ item
reversedList = reverse items
displayedItemsList = zipWith displayItem [1..] reversedList
in
unlines displayedItemsList
zipWith
, reverse
, and unlines
to find more about them[1..]
and only evaluate what it needs to evaluate.Let's skip removeItem
for now and add user interaction
-- Takes a list of items
-- Interact with the user
-- Return an updated list of items
interactWithUser :: Items -> IO Items
Let's start by reading a line, treat it as an item, add it to the list, and display the new items
interactWithUser :: Items -> IO Items
interactWithUser items = do
putStrLn "Enter an item to add to your todo list:"
item <- getLine
let newItems = addItem item items
putStrLn "Item added.\n"
putStrLn "The List of items is:"
putStrLn (displayItems newItems)
pure newItems
do
notation is the result of the computationIO Items
newItems
has the type Items
pure
which has the type a -> IO a
pure
creates a subroutine that produces an a
without doing any IOmain :: IO ()
main = do
putStrLn "TODO app"
let initialList = []
interactWithUser initialList
putStrLn "Thanks for using this app."
We can now try and run this program.
$ runghc Todo.hs
TODO app
Enter an item to add to your todo list:
Make a better app
Item added.
The List of items is:
1 - Make a better app
Thanks for using this app.
To do this in Haskell, we use recursion.
Instead of returning the todo list, we feed it back to interactWithUser
interactWithUser :: Items -> IO ()
interactWithUser items = do
putStrLn "Enter an item to add to your todo list:"
item <- getLine
let newItems = addItem item items
putStrLn "Item added.\n"
putStrLn "The List of items is:"
putStrLn (displayItems newItems)
interactWithUser newItems
TODO app
Enter an item to add to your todo list:
Make
Item added.
The List of items is:
1 - Make
Enter an item to add to your todo list:
This
Item added.
The List of items is:
1 - Make
2 - This
Enter an item to add to your todo list:
Stop
Item added.
The List of items is:
1 - Make
2 - This
3 - Stop
Enter an item to add to your todo list:
^C
data Command
= Quit
| DisplayItems
| AddItem String
We create a new ADT to model the possible user commands
parseCommand :: String -> Either String Command
parseCommand line = case words line of
["quit"] -> Right Quit
["items"] -> Right DisplayItems
"add" : "-" : item -> Right (AddItem (unwords item))
_ -> Left "Unknown command."
We change interactWithUser to accomodate for our new functionality
interactWithUser :: Items -> IO ()
interactWithUser items = do
putStrLn "Commands: quit, items, add - <item to add>"
line <- getLine
case parseCommand line of
Right DisplayItems -> do
putStrLn "The List of items is:"
putStrLn (displayItems items)
interactWithUser items
Right (AddItem item) -> do
let newItems = addItem item items
putStrLn "Item added."
interactWithUser newItems
Right Quit -> do
putStrLn "Bye!"
pure ()
Left errMsg -> do
putStrLn ("Error: " ++ errMsg)
interactWithUser items
TODO app
Commands: quit, items, add - <item to add>
add - Add a remove item command
Item added.
Commands: quit, items, add - <item to add>
add - Maybe also display the list of commands only once
Item added.
Commands: quit, items, add - <item to add>
items
The List of items is:
1 - Add a remove item command
2 - Maybe also display the list of commands only once
Commands: quit, items, add - <item to add>
quit
Bye!
Thanks for using this app.
data Command
...
| Help
parseCommand :: String -> Either String Command
parseCommand line = case words line of
...
["help"] -> Right Help
_ -> Left "Unknown command."
interactWithUser :: Items -> IO ()
interactWithUser items = do
line <- getLine
case parseCommand line of
Right Help -> do
putStrLn "Commands: help, quit, items, add - <item to add>"
interactWithUser items
...
The pattern _
serves as a "catch all" so we need to add the pattern for ["help"]
before it.
-- Returns a new list of items or an error message if the index is out of bounds
removeItem :: Int -> Items -> Either String Items
removeItem reverseIndex allItems =
impl (length allItems - reverseIndex) allItems
where
impl index items =
case (index, items) of
(0, item : rest) ->
Right rest
(n, []) ->
Left "Index out of bounds."
(n, item : rest) ->
case impl (n - 1) rest of
Right newItems ->
Right (item : newItems)
Left errMsg ->
Left errMsg
data Command
...
| Done Int
parseCommand :: String -> Either String Command
parseCommand line = case words line of
...
["done", idxStr] ->
if all (\c -> elem c "0123456789") idxStr
then Right (Done (read idxStr))
else Left "Invalid index."
_ -> Left "Unknown command."
interactWithUser :: Items -> IO ()
interactWithUser items = do
line <- getLine
case parseCommand line of
Right Help -> do
putStrLn "Commands: help, quit, items, add - <item to add>, done <item index>"
interactWithUser items
Right (Done index) -> do
let result = removeItem index items
case result of
Left errMsg -> do
putStrLn ("Error: " ++ errMsg)
interactWithUser items
Right newItems -> do
putStrLn "Item done."
interactWithUser newItems
...
TODO app
help
Commands: help, quit, items, add - <item to add>, done <item index>
add - Greet user
Item added.
add - Model data and user interaction
Item added.
add - Implement data modification
Item added.
add - Implement state and iteration using recursion
Item added.
add - Parse user input
Item added.
add - Interact with the user
Item added.
items
The List of items is:
1 - Greet user
2 - Model data and user interaction
3 - Implement data modification
4 - Implement state and iteration using recursion
5 - Parse user input
6 - Interact with the user
done 1
Item done.
items
The List of items is:
1 - Model data and user interaction
2 - Implement data modification
3 - Implement state and iteration using recursion
4 - Parse user input
5 - Interact with the user
done 1
Item done.
done 1
Item done.
done 1
Item done.
items
The List of items is:
1 - Parse user input
2 - Interact with the user
done 1
Item done.
done 1
Item done.
items
The List of items is:
quit
Bye!
Thanks for using this app.
The final app's source code can be viewed here.
Haskell provides us with all kinds of features to simplify the code and reduce code duplication!
But this is a story for another time.