Una Introducción agradable a Haskell
anterior siguiente
inicio
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.
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.
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.
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