Skip to content

Instantly share code, notes, and snippets.

@srithon
Last active January 16, 2022 20:45
Show Gist options
  • Save srithon/9fd11424155255a6bf2e642dae03c079 to your computer and use it in GitHub Desktop.
Save srithon/9fd11424155255a6bf2e642dae03c079 to your computer and use it in GitHub Desktop.
A basic TODO list application written in Haskell.
import Data.Maybe
import System.IO
type Todos = [Todo]
type Todo = String
addTodo :: Todos -> Todo -> Todos
addTodo todos todo = todos ++ [todo]
removeTodoN :: Todos -> Int -> Todos
-- NOTE: remember to account for empty patterns
removeTodoN [] i = []
removeTodoN list@(t : ts) i
| i < 1 = list
| i == 1 = ts
| otherwise = t : removeTodoN ts (i - 1)
removeTodoMatching :: Todos -> Todo -> Todos
removeTodoMatching [] s = []
removeTodoMatching (t : ts) s
| s == t = ts
| otherwise = t : removeTodoMatching ts s
readTodos :: FilePath -> IO Todos
readTodos path = do
contents <- readFile path
return $ lines contents
displayTodos :: Todos -> IO ()
displayTodos todos =
mapM_ putStrLn $
-- NOTE: use zipWith rather than map . zip
zipWith
(\i t -> show i ++ ". " ++ t)
[1 ..]
todos
data Action = Remove Int | Add Todo | Exit
parseUserAction' :: String -> Maybe Action
parseUserAction' input
| null inputWords = Just Exit
| action == "remove" =
if numWords > 1
then Just $ Remove $ read $ inputWords !! 1
else Nothing
| action == "add" = Just $ Add $ unwords $ tail inputWords
| otherwise = Nothing
where
inputWords = words input
numWords = length inputWords
action = head inputWords
parseUserAction :: String -> IO Action
-- if `parseUseraction' <input>` yields Nothing, yields the result of
-- `getUserAction`; else, uses `return` to map the `Just a` (Action) value to
-- an `IO Action`
parseUserAction = maybe getUserAction return . parseUserAction'
getUserAction :: IO Action
getUserAction = do
putStrLn "\nOptions: remove `i`, add `str` or empty to exit"
-- source: https://stackoverflow.com/questions/55091150/change-exit-value-on-getline-exception-in-haskell
--
-- getLine will panic if it sees an EOF, so we need to check if there's an EOF beforehand and handle it accordingly
isClosed <- isEOF
if isClosed
then return Exit
else do
input <- getLine
parseUserAction input
run :: Todos -> IO Todos
run todos = do
putStrLn "These are your existing TO-DO items:"
-- display the todos
displayTodos todos
-- ask user what they want to do
action <- getUserAction
case action of
Remove i -> run $ removeTodoN todos i
Add s -> run $ addTodo todos s
Exit -> return todos
main = do
todos <- readTodos "./todo.txt"
newTodos <- run todos
writeFile "./todo.txt" $ unlines newTodos
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment