En este módulo final, aplicaremos todos los conceptos aprendidos a lo largo del curso para construir una aplicación completa en Haskell. Este proyecto servirá como una culminación de tus conocimientos y te proporcionará una experiencia práctica en el desarrollo de software con Haskell.

Objetivos del Proyecto

  • Integrar múltiples conceptos de Haskell en una aplicación funcional.
  • Desarrollar habilidades prácticas en la construcción de software.
  • Aprender a estructurar y organizar un proyecto en Haskell.
  • Implementar pruebas y depuración en un proyecto real.

Descripción del Proyecto

Vamos a construir una aplicación de línea de comandos para gestionar una lista de tareas (To-Do List). La aplicación permitirá a los usuarios:

  • Añadir tareas.
  • Listar todas las tareas.
  • Marcar tareas como completadas.
  • Eliminar tareas.

Estructura del Proyecto

El proyecto se dividirá en varias partes:

  1. Configuración del Proyecto
  2. Definición de Tipos de Datos
  3. Implementación de Funciones Básicas
  4. Interacción con el Usuario
  5. Persistencia de Datos
  6. Pruebas y Depuración

  1. Configuración del Proyecto

Primero, configuraremos nuestro entorno de desarrollo y crearemos la estructura básica del proyecto.

Paso 1: Crear un Nuevo Proyecto

Usaremos stack para crear un nuevo proyecto Haskell.

stack new todo-list simple
cd todo-list

Paso 2: Configurar Dependencias

Edita el archivo package.yaml para incluir las dependencias necesarias. Añade aeson y text para manejar JSON y texto respectivamente.

dependencies:
- base >= 4.7 && < 5
- aeson
- text

  1. Definición de Tipos de Datos

Definiremos los tipos de datos que utilizaremos para representar las tareas.

Paso 1: Crear el Módulo Task

Crea un archivo src/Task.hs y define el tipo de datos Task.

module Task where

import Data.Aeson (ToJSON, FromJSON)
import GHC.Generics (Generic)
import Data.Text (Text)

data Task = Task
  { taskId :: Int
  , taskDescription :: Text
  , taskCompleted :: Bool
  } deriving (Show, Generic)

instance ToJSON Task
instance FromJSON Task

  1. Implementación de Funciones Básicas

Implementaremos las funciones básicas para manejar las tareas.

Paso 1: Crear el Módulo TaskManager

Crea un archivo src/TaskManager.hs y define las funciones para añadir, listar, completar y eliminar tareas.

module TaskManager where

import Task
import Data.Text (Text)
import qualified Data.Text.IO as TIO
import Data.List (find, delete)

type TaskList = [Task]

addTask :: TaskList -> Text -> TaskList
addTask tasks desc = tasks ++ [Task (length tasks + 1) desc False]

listTasks :: TaskList -> IO ()
listTasks = mapM_ printTask
  where
    printTask (Task id desc completed) = TIO.putStrLn $ 
      (if completed then "[x] " else "[ ] ") <> (show id <> ". " <> desc)

completeTask :: TaskList -> Int -> TaskList
completeTask tasks id = map markCompleted tasks
  where
    markCompleted task@(Task tid _ _) 
      | tid == id = task { taskCompleted = True }
      | otherwise = task

deleteTask :: TaskList -> Int -> TaskList
deleteTask tasks id = delete (findTask id tasks) tasks
  where
    findTask tid = find (\(Task id _ _) -> id == tid)

  1. Interacción con el Usuario

Implementaremos la lógica para interactuar con el usuario a través de la línea de comandos.

Paso 1: Crear el Módulo Main

Crea un archivo app/Main.hs y define la lógica de interacción.

module Main where

import TaskManager
import Task
import Data.Text (pack)
import qualified Data.Text.IO as TIO

main :: IO ()
main = do
  putStrLn "Welcome to the To-Do List Manager!"
  loop []

loop :: TaskList -> IO ()
loop tasks = do
  putStrLn "Choose an option: (1) Add Task (2) List Tasks (3) Complete Task (4) Delete Task (5) Exit"
  option <- getLine
  case option of
    "1" -> do
      putStrLn "Enter task description:"
      desc <- TIO.getLine
      loop (addTask tasks desc)
    "2" -> do
      listTasks tasks
      loop tasks
    "3" -> do
      putStrLn "Enter task ID to complete:"
      id <- readLn
      loop (completeTask tasks id)
    "4" -> do
      putStrLn "Enter task ID to delete:"
      id <- readLn
      loop (deleteTask tasks id)
    "5" -> putStrLn "Goodbye!"
    _   -> do
      putStrLn "Invalid option, try again."
      loop tasks

  1. Persistencia de Datos

Añadiremos funcionalidad para guardar y cargar las tareas desde un archivo.

Paso 1: Modificar TaskManager para Incluir Persistencia

Añade funciones para guardar y cargar tareas en TaskManager.hs.

import System.IO (withFile, IOMode(..))
import Data.Aeson (encode, decode)
import qualified Data.ByteString.Lazy as B

saveTasks :: FilePath -> TaskList -> IO ()
saveTasks path tasks = B.writeFile path (encode tasks)

loadTasks :: FilePath -> IO (Maybe TaskList)
loadTasks path = do
  content <- B.readFile path
  return (decode content)

Paso 2: Modificar Main para Usar Persistencia

Actualiza Main.hs para cargar y guardar tareas al inicio y al final del programa.

main :: IO ()
main = do
  putStrLn "Welcome to the To-Do List Manager!"
  tasks <- loadTasks "tasks.json"
  loop (maybe [] id tasks)

loop :: TaskList -> IO ()
loop tasks = do
  putStrLn "Choose an option: (1) Add Task (2) List Tasks (3) Complete Task (4) Delete Task (5) Exit"
  option <- getLine
  case option of
    "1" -> do
      putStrLn "Enter task description:"
      desc <- TIO.getLine
      let newTasks = addTask tasks desc
      saveTasks "tasks.json" newTasks
      loop newTasks
    "2" -> do
      listTasks tasks
      loop tasks
    "3" -> do
      putStrLn "Enter task ID to complete:"
      id <- readLn
      let newTasks = completeTask tasks id
      saveTasks "tasks.json" newTasks
      loop newTasks
    "4" -> do
      putStrLn "Enter task ID to delete:"
      id <- readLn
      let newTasks = deleteTask tasks id
      saveTasks "tasks.json" newTasks
      loop newTasks
    "5" -> putStrLn "Goodbye!"
    _   -> do
      putStrLn "Invalid option, try again."
      loop tasks

  1. Pruebas y Depuración

Finalmente, implementaremos pruebas para asegurarnos de que nuestra aplicación funciona correctamente.

Paso 1: Crear el Módulo de Pruebas

Crea un archivo test/Spec.hs y define algunas pruebas básicas.

module Main where

import Test.Hspec
import TaskManager
import Task
import Data.Text (pack)

main :: IO ()
main = hspec $ do
  describe "TaskManager" $ do
    it "adds a task" $ do
      let tasks = addTask [] (pack "Test Task")
      length tasks `shouldBe` 1
      taskDescription (head tasks) `shouldBe` (pack "Test Task")

    it "completes a task" $ do
      let tasks = addTask [] (pack "Test Task")
      let completedTasks = completeTask tasks 1
      taskCompleted (head completedTasks) `shouldBe` True

    it "deletes a task" $ do
      let tasks = addTask [] (pack "Test Task")
      let newTasks = deleteTask tasks 1
      length newTasks `shouldBe` 0

Paso 2: Ejecutar las Pruebas

Ejecuta las pruebas para asegurarte de que todo funciona correctamente.

stack test

Conclusión

En este proyecto, hemos construido una aplicación de línea de comandos para gestionar una lista de tareas utilizando Haskell. Hemos cubierto la configuración del proyecto, la definición de tipos de datos, la implementación de funciones básicas, la interacción con el usuario, la persistencia de datos y la realización de pruebas. Este proyecto te ha proporcionado una experiencia práctica en el desarrollo de software con Haskell y te ha preparado para abordar proyectos más complejos en el futuro.

© Copyright 2024. Todos los derechos reservados