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:
- Configuración del Proyecto
- Definición de Tipos de Datos
- Implementación de Funciones Básicas
- Interacción con el Usuario
- Persistencia de Datos
- Pruebas y Depuración
- 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.
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.
- 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
- 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)
- 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
- 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
- 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.
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.
Curso de Programación en Haskell
Módulo 1: Introducción a Haskell
- ¿Qué es Haskell?
- Configuración del Entorno de Haskell
- Sintaxis Básica y Hola Mundo
- Haskell REPL (GHCi)