In this final module, we will bring together all the concepts learned throughout the course to build a complete Haskell application. This project will help you understand how to structure a Haskell application, manage dependencies, and implement various functionalities using Haskell's powerful features.
Project Overview
We will build a simple command-line tool for managing a to-do list. The application will allow users to:
- Add tasks
- View tasks
- Mark tasks as completed
- Delete tasks
Project Structure
Here is the structure of our project:
todo-app/ ├── app/ │ └── Main.hs ├── src/ │ ├── Todo.hs ├── data/ │ └── tasks.txt ├── test/ │ └── TodoSpec.hs ├── todo-app.cabal └── stack.yaml
Step 1: Setting Up the Project
- 
Initialize the Project: Use Stack to create a new Haskell project. stack new todo-app simple cd todo-app
- 
Project Configuration: Edit the todo-app.cabalfile to include necessary dependencies.name: todo-app version: 0.1.0.0 build-type: Simple cabal-version: >=1.10 executable todo-app main-is: Main.hs hs-source-dirs: app, src build-depends: base >=4.7 && <5 , directory , filepath , text , containers default-language: Haskell2010
- 
Directory Structure: Create the necessary directories and files. mkdir -p app src data test touch app/Main.hs src/Todo.hs data/tasks.txt test/TodoSpec.hs
Step 2: Implementing the Todo Module
Create the Todo module in src/Todo.hs to handle the core functionality of the application.
module Todo (
    Task(..),
    addTask,
    viewTasks,
    completeTask,
    deleteTask,
    loadTasks,
    saveTasks
) where
import System.IO
import Data.List
import Data.Maybe
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
data Task = Task {
    taskId :: Int,
    taskDescription :: T.Text,
    taskCompleted :: Bool
} deriving (Show, Read)
addTask :: T.Text -> [Task] -> [Task]
addTask desc tasks = tasks ++ [Task (length tasks + 1) desc False]
viewTasks :: [Task] -> T.Text
viewTasks tasks = T.unlines $ map formatTask tasks
  where
    formatTask (Task id desc completed) =
        T.pack (show id) <> ". " <> desc <> if completed then " [x]" else ""
completeTask :: Int -> [Task] -> [Task]
completeTask id tasks = map markCompleted tasks
  where
    markCompleted task@(Task tid _ _)
        | tid == id = task { taskCompleted = True }
        | otherwise = task
deleteTask :: Int -> [Task] -> [Task]
deleteTask id tasks = filter (\(Task tid _ _) -> tid /= id) tasks
loadTasks :: FilePath -> IO [Task]
loadTasks path = do
    content <- TIO.readFile path
    return $ map readTask (T.lines content)
  where
    readTask line = read (T.unpack line) :: Task
saveTasks :: FilePath -> [Task] -> IO ()
saveTasks path tasks = TIO.writeFile path (T.unlines $ map (T.pack . show) tasks)Step 3: Implementing the Main Module
Create the Main module in app/Main.hs to handle user interaction.
module Main where
import System.IO
import System.Directory
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Todo
main :: IO ()
main = do
    let filePath = "data/tasks.txt"
    tasks <- loadTasks filePath
    putStrLn "Welcome to the Haskell Todo App!"
    mainMenu tasks filePath
mainMenu :: [Task] -> FilePath -> IO ()
mainMenu tasks filePath = do
    putStrLn "\nMain Menu:"
    putStrLn "1. View Tasks"
    putStrLn "2. Add Task"
    putStrLn "3. Complete Task"
    putStrLn "4. Delete Task"
    putStrLn "5. Exit"
    putStr "Choose an option: "
    hFlush stdout
    option <- getLine
    case option of
        "1" -> do
            TIO.putStrLn $ viewTasks tasks
            mainMenu tasks filePath
        "2" -> do
            putStr "Enter task description: "
            hFlush stdout
            desc <- TIO.getLine
            let newTasks = addTask desc tasks
            saveTasks filePath newTasks
            mainMenu newTasks filePath
        "3" -> do
            putStr "Enter task ID to complete: "
            hFlush stdout
            id <- readLn
            let newTasks = completeTask id tasks
            saveTasks filePath newTasks
            mainMenu newTasks filePath
        "4" -> do
            putStr "Enter task ID to delete: "
            hFlush stdout
            id <- readLn
            let newTasks = deleteTask id tasks
            saveTasks filePath newTasks
            mainMenu newTasks filePath
        "5" -> putStrLn "Goodbye!"
        _   -> do
            putStrLn "Invalid option, please try again."
            mainMenu tasks filePathStep 4: Testing the Application
Create tests in test/TodoSpec.hs to ensure the functionality works as expected.
module TodoSpec where
import Test.Hspec
import Todo
main :: IO ()
main = hspec $ do
    describe "Todo" $ do
        it "adds a task" $ do
            let tasks = addTask "Test task" []
            length tasks `shouldBe` 1
            taskDescription (head tasks) `shouldBe` "Test task"
        it "completes a task" $ do
            let tasks = addTask "Test task" []
            let completedTasks = completeTask 1 tasks
            taskCompleted (head completedTasks) `shouldBe` True
        it "deletes a task" $ do
            let tasks = addTask "Test task" []
            let remainingTasks = deleteTask 1 tasks
            length remainingTasks `shouldBe` 0Step 5: Running the Application
- 
Build the Project: stack build
- 
Run the Application: stack exec todo-app
- 
Run the Tests: stack test
Conclusion
Congratulations! You have successfully built a simple command-line to-do list application in Haskell. This project has helped you understand how to structure a Haskell application, manage dependencies, and implement various functionalities using Haskell's powerful features. You can now extend this application with more features or start building your own Haskell projects. Happy coding!
Haskell Programming Course
Module 1: Introduction to Haskell
- What is Haskell?
- Setting Up the Haskell Environment
- Basic Syntax and Hello World
- Haskell REPL (GHCi)
