Error in user YAML: (<unknown>): did not find expected ',' or ']' while parsing a flow sequence at line 3 column 1
---
# Writing Simple
[![Haskell](https://www.haskell.org/static/img/haskell-logo.svg)](https://haskell.org)
---
- This is a follow-up to Reading Simple Haskell
- Markdown format available here.
- We're going to build a cli todo app.
- To get the most out of this, follow along using a haskell toolchain or repl.it.
We want to be able to:
- Add a new todo item
- Display all items
- Mark an item as done
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).- The type of
putStrLn
isString -> IO ()
- The type of main is
IO ()
.
module Main where
main :: IO ()
main = putStrLn "Hello user!"
- The type
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 a main
is the name of the entry point of the program.- The Haskell runtime will look for
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
- Haskell is indentation sensitive
- Code which is part of some expression should be indented further in than the beginning of that expression
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 actions- the type of
getLine
isIO 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 typeString
getLine
produces aString
by taking a line from the standard input
module Main where
main :: IO ()
main = do
putStrLn "Hello! What is your name?"
name <- getLine
let out = "Nice to meet you, " ++ name ++ "!"
putStrLn out
- The type of
getLine
isIO String
- The type of
name
isString
<-
is special syntax that can only appear indo
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 thedo
block- In
do
notation,let
does not need the accompanyingin
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 ++ "!"
| ^^^^^^^
- Note:
String
is defined astype String = [Char]
- Haskell says it can't match the types
String
which was expected, withIO String
which is the type ofgetLine
IO a
anda
are different types
module 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:
- Add a new todo item
- Display all items
- Mark an item as done
How should we
- Model the data?
- Store the items?
- 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?
- Add a new todo item -> Add an item to the start of the list
- Display all items -> Reverse and print with indices
- Mark an item as done -> Remove an item by its reverse-index
- How can we represent these actions?
-- 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
- Use hoogle to search for
zipWith
,reverse
, andunlines
to find more about them - Haskell only evaluates values when it needs to (for example, when they need to be evaluated in order to print something to the user).
- It lets us write functions that work on infinite lists such as
[1..]
and only evaluate what it needs to evaluate. - You can read more about evaluation in Haskell in this guide
- 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
- The last line of the
do
notation is the result of the computation - In this case, it needs to be a value of the type
IO Items
- But
newItems
has the typeItems
- So we use
pure
which has the typea -> IO a
pure
creates a subroutine that produces ana
without doing any IO
main :: 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.
- We want to let the user add more than one item to their todo list
- We want to remember the changes to the list
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
- This program will run forever
- We can stop it using Ctrl-C, but that's not very nice
- Let's make it possible for the user to use different commands
data Command
= Quit
| DisplayItems
| AddItem String
- We create a new ADT to model the possible user commands
- And parse a user command to our data type
- This may fail
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.