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

  1. Initialize the Project: Use Stack to create a new Haskell project.

    stack new todo-app simple
    cd todo-app
    
  2. Project Configuration: Edit the todo-app.cabal file 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
    
  3. 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 filePath

Step 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` 0

Step 5: Running the Application

  1. Build the Project:

    stack build
    
  2. Run the Application:

    stack exec todo-app
    
  3. 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!

© Copyright 2024. All rights reserved