Una Introducción agradable a Haskell
anterior siguiente inicio


11  Módulos

Un programa Haskell consta de una colección de módulos. Un módulo en Haskell responde al doble propósito de controlar el espacio de nombres y de crear tipos abstractos de datos.

El nivel superior de un módulo contiene cualquier tipo de declaraciones que ya hemos discutido: declaraciones de modo, declaraciones data y type, declaraciones de clase y de instancias, declaraciones de tipos, definiciones de función, y enlaces a través de patrones. A excepción del hecho de que las declaraciones de importación (descritas posteriormente) deben aparecer en primer lugar, el resto de declaraciones pueden aparecer en cualquier orden (el ámbito a nivel superior es mutuamente recursivo).

El diseño de módulos de Haskell es relativamente tradicional: el espacio de nombres de los módulos es totalmente plano, y los módulos no son "de primera categoría". Los nombres de los módulos son alfanuméricos y deben comenzar con una letra mayúscula. No hay conexión formal entre un módulo de Haskell y el sistema de ficheros que (típicamente) lo contiene. En particular, no hay conexión entre los nombres de los módulos y los nombres de los archivos, y más de un módulo podría residir en un solo fichero (un módulo puede incluso definirse entre varios ficheros). Por supuesto, una implementación adoptará muy probablemente convenciones que hagan la conexión entre los módulos y los ficheros más rigurosa.

Técnicamente hablando, un módulo es realmente una declaración que comienza con la palabra clave module; he aquí un ejemplo de un módulo con nombre Tree:

module Tree ( Tree(Leaf,Branch), fringe ) where

data Tree a                = Leaf a | Branch (Tree a) (Tree a) 

fringe :: Tree a -> [a]
fringe (Leaf x)            = [x]
fringe (Branch left right) = fringe left ++ fringe right

El tipo Tree y la función fringe deben ser familiares; fueron dados como ejemplos en la sección 2.2.1. [ debido a la palabra clave where, las reglas de sangrado están activas en el nivel superior de un módulo, y las declaraciones deben escribirse en la misma columna (típicamente la primera). Observe también que el nombre del módulo es igual que el del tipo; esto está permitido. ]

Este módulo exporta explícitamente Tree, Leaf, Branch, y fringe. Si la lista de exportación del módulo se omite, se considera que se utiliza all y todos los nombres ligados en el nivel superior del módulo serán exportados. (En el ejemplo anterior todo se exporta explícitamente, así que el efecto sería el mismo.) Obsérvese que el nombre de un tipo y de sus constructores tienen que estar agrupados, como en Tree(Leaf, Branch). Como simplificación, podríamos también escribir Tree(..). También es posible la exportación de un subconjunto de los constructores. Los nombres en una lista de exportación no han de ser locales al módulo de exportación; cualquier nombre en el ámbito se puede enumerar en una lista de exportación

El módulo Tree puede ahora ser importado por cualquier otro módulo:

module Main (main) where
import Tree ( Tree(Leaf,Branch), fringe )

main = print (fringe (Branch (Leaf 1) (Leaf 2)))

Los elementos que son importados/exportados a/desde un módulo se llaman entidades. Obsérvese la lista de importación explícita en el declaración de importación; omitirla causaría que todas las entidades exportadas por Tree fuesen importadas.

11.1  Nombres cualificados

Existe un problema obvio con los nombres importados en el espacio de nombres de un módulo. ¿Qué ocurre si dos módulos importados contienen diversas entidades con el mismo nombre? Haskell soluciona este problema usando nombres cualificados. Una declaración de importación puede utilizar la palabra clave qualified para hacer que los nombres importados sean precedidos por el nombre del módulo de donde se importaron. Estos prefijos son seguidos por el carácter`.' sin espacios. [ los nombres cualificados son parte de la sintaxis léxica. Así, A.x y A . x son absolutamente diferentes: el primero es un nombre cualificado y el segundo un uso de la función '.' ] Por ejemplo, usando el módulo Tree anterior:

module Fringe(fringe) where
import Tree(Tree(..))

fringe :: Tree a -> [a]   -- Una definicion diferente de fringe
fringe (Leaf x) = [x]
fringe (Branch x y) = fringe x

module Main where
import Tree ( Tree(Leaf,Branch), fringe )
import qualified Fringe ( fringe )  

main = do print (fringe (Branch (Leaf 1) (Leaf 2)))
          print (Fringe.fringe (Branch (Leaf 1) (Leaf 2)))

Algunos programadores de Haskell prefieren utilizar todas las entidades importadas de un modo cualificado, haciendo el origen de cada nombre explícito en cada uso. Otros prefieren nombres cortos y utilizan solamente nombres cualificados cuando son absolutamente necesarios.

Los cualificadores suelen usarse para resolver conflictos entre diversas entidades que tengan el mismo nombre. Pero ¿qué ocurre si la misma entidad se importa a través de varios módulos? Afortunadamente, se permiten tales choques: una entidad se puede importar por varias rutas sin conflicto. El compilador sabe si las entidades de diversos módulos son realmente iguales.

11.2  Tipos Abstractos de Datos

Aparte del control del espacio de nombres, los módulos proporcionan la única manera de construir tipos abstractos de datos (TAD) en Haskell. Por ejemplo, la característica de un TAD es que la representación del tipo está oculto; todas las operaciones sobre el TAD se hacen en un nivel abstracto que no depende de la representación. Por ejemplo, aunque el tipo Tree es bastante simple como para que lo hagamos abstracto, un TAD para él puede incluir las operaciones siguientes:


data Tree a             -- el nombre del tipo 
leaf                    :: a -> Tree a
branch                  :: Tree a -> Tree a -> Tree a
cell                    :: Tree a -> a
left, right             :: Tree a -> Tree a
isLeaf                  :: Tree a -> Bool

Un módulo que implementa esto es:

module TreeADT (Tree, leaf, branch, cell, 
                left, right, isLeaf) where

data Tree a             = Leaf a | Branch (Tree a) (Tree a) 

leaf                    = Leaf
branch                  = Branch
cell  (Leaf a)          = a
left  (Branch l r)      = l
right (Branch l r)      = r
isLeaf   (Leaf _)       = True
isLeaf   _              = False

Obsérvese que en la lista de exportación, el nombre del tipo Tree aparece sólo (sin sus constructores). Así Leaf y Branch no son exportados, y la única manera de crear y manejar árboles fuera de este módulo es a través de las operaciones abstractas exportadas. Una ventaja de esta ocultación es que posteriormente podemos cambiar la representación del tipo sin afectar a los usuarios del mismo.

11.3  Más Características

He aquí una breve revisión de otros aspectos del sistema de módulos. Para más detalles, consultase el Informe.

Aunque el sistema de módulos de Haskell es relativamente tradicional, existen muchas reglas relativas a la importación y exportación de valores. La mayoría de ellas son obvias--- por ejemplo, es ilegal importar dos entidades diferentes con el mismo nombre en el mismo ámbito. Otras reglas no son tan obvias---por ejemplo, para un tipo y clase dados, no puede haber más de una combinación de la clase y del tipo en cualquier lugar del programa. El lector debería leer el Informe para más detalles (§5).


Una Introducción agradable a Haskell
anterior siguiente inicio