Guia Rapida Haskell
Guia Rapida Haskell
c 1
De hecho, se puede usar cualquier valor que esté ser utilizados al definir el cuerpo de una función. Como se puede ver arriba, la palabra clave in2
en la clase Enum : Por ejemplo, esto no compila: debe también estar en la misma columna que el
• ['a' .. 'z'] – Lista of caracteres – a, b, let . Finalmente, cuando se van múltiples defini-
. . ., z . square2 x = { x * x; } ciones, todos los identificadores deben aparecer en
• [1.0, 1.5 .. 2] – [1.0,1.5,2.0] . la misma columna.
• [UppercaseLetter ..] – Lista de valores Sin embargo, esto funciona bien:
GeneralCategory (en Data.Char).
square2 x = result Declaraciones, Etc.
where { result = x * x; }
Listas & Tuplas La siguiente sección describe las reglas para la
Definición de funciones Aplique una sangría declaración de funciones, las listas por compren-
• [] – Lista vacía.
de al menos un espacio a partir del nombre de la sión, y otras áreas del lenguaje.
• [1,2,3] – Lista de tres números.
• 1 : 2 : 3 : [] – Forma alterna de escribir función:
listas usando “cons” (: ) y “nil” ( [] ).
Definición de Funciones
square x =
• "abc" – Lista de tres caracteres (las cadenas Las funciones se definen declarando su nombre,
x*x
son listas). los argumentos, y un signo de igual:
• 'a' : 'b' : 'c' : [] – Lista de caracteres
A menos que esté presente una cláusula where. En
(lo mismo que "abc" ). square x = x * x
ese caso, aplique la sangría de la cláusula where al
• (1,"a") – Tupla de dos elementos, un
menos un espacio a partir del nombre de la fun- Todos los nombres de función deben comenzar con
número y una cadena.
ción y todos los cuerpos de la función al menos a letra minúscula o “ _”. Es un error de sintaxis de
• (head, tail, 3, 'a') – Tupla de cuatro el-
un espacio a partir de la palabra clave where: cualquier otra forma.
ementos, dos funciones, un número y un car-
acter. square x = Comparación de patrones Se pueden definir
x2 varias “cláusulas” de una función haciendo “com-
Regla “Layout”, llaves y punto y comas where x2 = paración de patrones” en los valores de los argu-
x*x mentos. Aquí, la función agree tiene cuatro casos
Se puede escribir Haskell utilizando llaves y punto
y comas, igual que en C. Sin embargo, nadie lo separados:
Let Aplique sangría sobre el cuerpo del let al
hace. En lugar de eso se emplea la regla “layout”, -- Coincide cuando se da la cadena "y".
menos un espacio a partir de la primera definición
donde se emplea espacio en blanco para separar agree1 "y" = "Great!"
en el let . Si el let aparece por sí solo en una línea,
contextos. La regla general es: siempre usar san- -- Coincide cuando se da la cadena "n".
el cuerpo de cualquier definición debe aparecer en
grías. Cuando el compilador se queje, usar más. agree1 "n" = "Too bad."
la columna después del let :
-- Coincide cuando se da una cadena que
Llaves y punto y comas Los paréntesis final- square x = -- comienza con 'y'.
izan una expresión, y las llaves representan con- let x2 = agree1 ('y':_) = "YAHOO!"
textos. Pueden ser utilizados después de varias x*x -- Coincide con cualquier otro valor.
palabras clave: where, let , do y of . No pueden in x2 agree1 _ = "SO SAD."
2
Nótese que el caracter ‘ _’ es un comodín y co- isEven 0 = True what [] = "Cadena vacía"
incide con cualquier valor. isEven 1 = False what (c:_)
La comparación de patrones se puede extender isEven (n + 2) = isEven n | isUpper c = "Mayúscula"
a valores anidados. Asumiendo esta declaración | isLower c = "Minúscula"
de dato: | otherwise = "No es letra"
Captura de Argumentos La captura de argu-
mentos es útil para comparar un patrón y uti-
data Bar = Bil (Maybe Int) | Baz Comparación & Orden de las Guardas La
lizarlo, sin declarar una variable extra. Utilice un
símbolo ‘@ ’ entre el patrón a coincidir y la variable comparación de patrones procede en un orden de
y recordando la definición de Maybede la página 8
a la cual asociar el valor. Este mecanismo se utiliza arriba hacia abajo. De la misma forma, las expre-
podemos hacer coincidir en valores Maybeanida-
en el siguiente ejemplo para asociar el primer ele- siones con guarda son evaluadas de la primera a
dos cuando Bil está presente:
mento de la lista en l para mostrarlo, y al mismo la última. Por ejemplo, ninguna de estas funciones
f (Bil (Just _)) = ... tiempo asociar la lista completa a ls para calcular sería muy interesante:
f (Bil Nothing) = ... su longitud:
allEmpty _ = False
f Baz = ...
allEmpty [] = True
len ls@(l:_) = "List starts with " ++
La comparación de patrones también permite que show l ++ " and is " ++
alwaysEven n
valores sean asociados a variables. Por ejemplo, show (length ls) ++ " items long."
| otherwise = False
esta función determina si la cadena dada es o no len [] = "List is empty!"
| n `div` 2 == 0 = True
vacía. Si no, el valor asociado a str es convertido
a minúsculas:
Guardas Las funciones booleanas se pueden Sintaxis de Registros Normalmente la com-
toLowerStr [] = [] utilizar como “guardas” en definicione de función paración de patrones ocurre basándose en la posi-
toLowerStr str = map toLower str al mismo tiempo que la comparación de patrones. ción en los argumentos del valor a coincidir. Los
Un ejemplo sin comparación de patrones: tipos que se declaran con sintaxis de registro, sin
Nótese que aquí str es similar a _ en que va a co- embargo, pueden coincidir basándose en los nom-
incidir con lo que sea; la única diferencia es que al which n bres en el registro. Dado el siguiente tipo de datos:
valor que coincide también se le da un nombre. | n == 0 = "Cero"
| even n = "Par" data Color = C { red
n + k Patterns Esta (a veces controversial) com- | otherwise = "Impar" , green
paración de patrones hace fácil coincidir con cier- , blue :: Int }
tos tipos de expresiones numéricas. La idea es Note el otherwise – siempre evalúa verdadero y
podemos hacer coincidir solamente green:
definir un caso base (la porción “n”) con un puede ser utilizado para especificar un caso por
número constante para que coincida, y después “default”. isGreenZero (C { green = 0 }) = True
definir las coincidencias (la porción “k”) como Las guardas se pueden utilizar con patrones. isGreenZero _ = False
sumas sobre el caso base. Por ejemplo, esta es una El siguiente ejemplo es una función que determina
forma ineficiente de determinar si un número es o si el primer caracter en una cadena es mayúscula Es posible capturar argumentos con esta sintaxis,
no par: o minúscula: aunque se vuelve incómodo. Continuando con el
3
ejemplo, podemos definir un tipoPixel y una fun- pero el constructor puede ser Nothing . La sigu- una lista de valores objetivo basados en los gen-
ción que reemplace con negro valores con compo- iente definición podría funcionar, pero no es óp- eradores y en las guardas proporcionados. Esta
nente green diferente de cero: tima porque obtenemos Nothing cuando se le pasa comprensión genera todos los cuadrados:
Nothing .
data Pixel = P Color squares = [x * x | x <- [1..]]
instance Def a => Def (Maybe a) where
-- El valor del color no se defValue (Just x) = Just (defValue x) x <- [1..] genera una lista de todos los valores
-- modifica si green es 0 defValue Nothing = Nothing enteros positivos y los coloca en x, uno por uno.
setGreen (P col@(C {green = 0})) = P col x * x crea cada elemento de la lista multiplicando
Preferiríamos mejor obtener un valor Just ( valor
setGreen _ = P (C 0 0 0) x por sí mismo.
por default ) . Aquí es donde un patrón perezoso
Las guardas permiten que algunos elementos
ayuda – podemos aparentar que hemos hecho co-
Patrones Perezosos Esta sintaxis, también sean omitidos. El ejemplo a continuación mues-
incidir con Just x y usar eso para obtener un
conocida como patrones irrefutables, permite hacer tra cómo calcular los divisores (excluyendo a él
valor por default, aún si entra Nothing :
comparación de patrones que siempre coincida. mismo) para cierto número. Notar cómo se usa el
Esto significa que cualquier cláusula utilizando el instance Def a => Def (Maybe a) where mismo d en la guarda y en la expresión objetivo.
patrón tendrá éxito, pero si trata de utilizar el defValue ~(Just x) = Just (defValue x)
divisors n =
valor que ha coincidido puede ocurrir un error. Mientras el valor x no sea evaluado, estamos a [d | d <- [1..(n `div` 2)]
Esto es generalmente útil cuando se debe realizar salvo. Ninguno de los tipos base necesita inspec- , n `mod` d == 0]
una acción basándose en el tipo de un valor en cionar x (ver la coincidencia con “_” que usan), así
particular, aún si el valor no está presente. que funcionará bien. Las asociaciones locales proveen nuevas defini-
Por ejemplo, defina una clase para valores por Un inconveniente con esto es que debemos ciones para usar en la expresión generada o en
default: proporcionar anotaciones de tipo en el intérprete las guardas y generadores que las siguen. Debajo,
o en el código cuando usemos un constructor z es empleado para representar el mínimo de a y
class Def a where Nothing . Nothing tiene tipo Maybe a pero, a b:
defValue :: a -> a falta de información adicional, se debe informar
a Haskell qué es a. Algunos ejemplos de valores strange = [(a,z) | a <-[1..3]
La idea es que le dé a defValue un valor del tipo , b <-[1..3]
por default:
correcto y regrese un valor por default para ese , c <- [1..3]
tipo. Definir instancias para tipos básicos es fácil: -- Return "Just False" , let z = min a b
defMB = defValue (Nothing :: Maybe Bool) ,z<c]
instance Def Bool where -- Return "Just ' '"
defValue _ = False defMC = defValue (Nothing :: Maybe Char) Las comprensiones no están limitadas a números.
Funcionan con cualquier lista. Se pueden generar
instance Def Char where Listas por Comprensión todas las letras mayúsculas:
defValue _ = ' '
Una lista por comprensión consiste de cuatro tipos ups =
Maybees un poco más complicado, porque quer- de elementos: generadores, guardas, asociaciones lo- [c | c <- [minBound .. maxBound]
emos obtener un valor por default para el tipo, cales, y objetivos. Una lista por comprensión crea , isUpper c]
4
O, para encontrar todas las apariciones de un > " Haskell " ## " Curry " La precedencia y la asociatividad hacen que
valor br en una lista de palabras (con índices Curry, Haskell muchas de las reglas de la aritmética funcionen
desde cero): “como se espera”. Por ejemplo, considere las sigu-
Por supuesto, comparación de patrones, guardas, ientes modificaciones menores a la precedencia de
idxs palabras br = etc. están disponibles en esta forma. La la suma y multiplicación:
[i | (i, c) <- zip [0..] palabras declaración de tipos es un poco diferentes. El op-
, c == br] erador “nombre” debe aparecer entre paréntesis:
infixl 8 `plus1`
(##) :: String -> String -> String
Un aspecto único de las listas por comprensión es plus1 a b = a + b
que los errores en la comparación de patrones no Los símbolos que se permite usar para definir op- infixl 7 `mult1`
causan un error; simplemente son omitidos de la eradores son: mult1 a b = a * b
lista resultante.
# \$ % & * + . / < = > ? @ \ ^ | - ~
Los resultados son sorpresivos:
Operadores Sin embargo, hay varios “operadores” que no
pueden ser redefinidos. Estos son <- , -> y =. En sí
Hay muy pocos “operadores” predefinidos en
mismos no se les puede asignar nueva funcionali- >2+3*5
Haskell—muchos que parecen estar predefinidos
dad, pero pueden ser utilizados como parte de un 17
en realidad son sintaxis (e.g. “=”). En lugar de
operador multicaracter. La función “bind”, >>=, es > 2 `plus1` 3 `mult1` 5
eso, los operadores son simplemente funciones
un ejemplo. 25
que toman dos argumentos y tienen un soporte
sintáctico especial. Cualquier así llamado oper-
ador puede ser aplicado como una función prefijo Precedencia & Asociatividad La precedencia
y asociatividad, colectivamente llamadas fijidad, Revertir la asociatividad también tiene efectos in-
usando paréntesis:
de cualquier operador, pueden ser establecidos teresantes. Redefiniendo la división como asocia-
a través de las palabras clave infix , infixr e tiva por la derecha:
3 + 4 == (+) 3 4
infixl . Éstas pueden ser aplicadas a funciones
Para definir un nuevo operador, simplemente de- en el nivel superior o a definiciones locales. La
infixr 7 `div1`
fínalo como una función normal, excepto que el sintaxis es:
div1 a b = a / b
operador aparezca entre los dos argumentos. Este
infix | infixr | infixl precedencia op
es uno que inserta una coma entre dos cadenas y
asegura que no aparezcan espacios adicionales: donde precedencia varía de 0 a 9. Op puede ser Obtenemos resultados interesantes:
cualquier función que tome dos argumentos (i.e.,
first ## last = cualquier operación binaria). Que el operador
let trim s = dropWhile isSpace sea asociativo por la izquierda o por la derecha > 20 / 2 / 2
(reverse (dropWhile isSpace está especificado por infixl o infixr , respectiva- 5.0
(reverse s))) mente. La declaración infix no tiene asociativi- > 20 `div1` 2 `div1` 2
in trim last ++ ", " ++ trim first dad. 20.0
5
Aplicación parcial toma una cadena y produce una cadena. l33t re- add10 = (10 +)
gresa una versión “currificada” de convertOnly ,
En Haskell las funciones no tienen que recibir to-
donde solamente dos de sus tres argumentos han El argumento provisto puede estar del lado
dos sus argumentos de una vez. Por ejemplo, con-
sido provistos. izquierdo o derecho, lo que indica qué posición
sidere la función convertOnly , que solamente con-
Esto puede ser llevado más lejos. Digamos debe tomar. Esto es importante para operaciones
vierte ciertos elementos de una cadena dependi-
que queremos escribir una función que solamente como la concatenación:
endo de una prueba:
cambie letras mayúsculas. Sabemos cual es la
prueba a aplicar, isUpper , pero no queremos es- onLeft str = (++ str)
convertOnly test change str =
pecificar la conversión. Esa función puede ser es- onRight str = (str ++)
map (\c -> if test c
crita como:
then change c
Que produce resultados diferentes:
else c) str convertUpper = convertOnly isUpper
Usando convertOnly podemos escribir la función Que tiene la declaración de tipos: > onLeft "foo" "bar"
l33t que convierte ciertas letras a números: "barfoo"
convertUpper :: (Char -> Char) > onRight "foo" "bar"
l33t = convertOnly isL33t toL33t -> String -> String "foobar"
where Eso es, convertUpper puede tomar dos argumen-
isL33t 'o' = True tos. El primero es la función de conversión que “Actualizando” Valores y la Sintaxis de
isL33t 'a' = True convierte caracteres individuales y el segundo es Registros
-- etc. la cadena que se va a convertir.
isL33t _ = False Haskell es un lenguaje puro y, como tal, no tiene
Se pueden crear una forma currificada de
toL33t 'o' = '0' estado mutable. Eso es, una vez que un valor ha
cualquier función que toma múltiples argumen-
toL33t 'a' = '4' sido establecido nunca cambia. “Actualizar” es
tos. Una forma de pensar esto es que cada
-- etc. en realidad una operación de copiado, con valores
“flecha” en la declaración de tipos de la fun-
toL33t c = c nuevos en los campos que “cambiaron”. Por ejem-
ción representa una nueva función que puede ser
plo, usando el tipo Color definido antes, podemos
creada al proveer más argumentos.
Nótese que l33t no tiene argumentos especi- escribir una función que establece a cero el campo
ficados. También, que el argumento final de green fácilmente:
Secciones Los operadores son funciones, y
convertOnly no es proporcionado. Sin embargo,
pueden ser currificados como cualquier otro. Por noGreen1 (C r _ b) = C r 0 b
la declaración de tipos de l33t cuenta la historia
ejemplo, una versión currificada de “ +” se puede
completa:
escribir como: Esto es algo extenso y puede ser vuelto a escribir
l33t :: String -> String add10 = (+) 10 con sintaxis de registro. Este tipo de “actual-
ización” solamente establece valores para los cam-
Eso es, l33t toma una cadena y produce una ca- Sin embargo esto es incómodo y difícil de leer.Las pos especificados y copia los demás:
dena. Es “contante” en el sentido de que l33t “secciones” son operadores currificados, usando
siempre regresa un valos que es una función que paréntesis. Este es add10usando secciones: noGreen2 c = c { green = 0 }
6
Aquí capturamos el valor Color en c y devolve- Por supuesto, las lambdas pueden ser regresadas Los tipos pueden aparecer en funciones del
mos un nuevo valor Color . Ese valor resulta tener también de otras funciones. Este clásico regresa nivel superior y en definiciones let o where
el mismo valor para red y blue que c y su com- una función que multiplicará su argumento por el anidadas. En general esto es útil para hacer docu-
ponente green es 0. Podemos combinar esto con que se ha dado originalmente: mentación, aunque en algunos casos es requerido
comparación de patrones para establecer los cam- para prevenir el polimorfismo. Una declaración
pos green y blue para que sean iguales al campo multBy n = \m -> n * m de tipos es primero el nombre del item, seguido
red : de :: , seguido de los tipos.
Por ejemplo:
Las declaraciones de tipos no necesitan
makeGrey c@(C { red = r }) = > let mult10 = multBy 10 aparecer directamente sobre su implementación.
c { green = r, blue = r } > mult10 10 Pueden ser especificadas en cualquier parte del
100 módulo que las contiene (aún debajo). Se pueden
Nótese que debemos usar captura de argumen- definir al mismo tiempo varios items que tengan
tos (“ c@”) para obtener el valor de Color y com- Declaración de tipos la misma declaración de tipos:
parar con sintaxis de registro (“ C { red = r} ”)
para obtener el campo interno red . Haskell cuenta con inferencia de tipos, lo que sig- pos, neg :: Int -> Int
nifica que casi nunca es necesario declarar los
tipos. Indicarlos es todavía útil al menos por dos
Funciones Anónimas ...
razones.
Una función anónima (i.e., una expresión lambda o pos x | x < 0 = negate x
Documentación—Aún si el compilador puede in-
simplemente lambda) es una función sin nombre. | otherwise = x
ferir los tipos de sus funciones, otros progra-
Pueden ser definidas en cualquier momento de la
madore o aún usted mismo podría no ser ca-
siguiente forma: neg y | y > 0 = negate y
paz de hacerlo más tarde. Declarar los tipos
en todas las funciones del nivel principal se | otherwise = y
\c -> (c, c)
considera una buena práctica.
que define una función que toma un argumento Especialización—Las clases de tipos permiten so- Anotaciones de Tipo Algunas veces Haskell
y regresa un tuple conteniendo ese argumento en brecargar funciones. Por ejemplo, una fun- no puede determinar qué tipo se debe aplicar.
ambas posiciones. Éstas son útiles para funciones ción que hace la negación de una lista de La demostración clásica de esto es el denominado
simples donde no necesitamos un nombre. El números tiene la declaración de tipos: problema “show . read ”:
ejemplo siguiente determina si una cadena con-
siste solamente de letras mayúsculas o minúsculas negateAll :: Num a => [a] -> [a] canParseInt x = show (read x)
y espacio en blanco.
Sin embargo, si por eficiencia o por otras Haskell no puede compilar la función porque no
mixedCase str = razones solamente desea permitir tipos Int , conoce el tipo de read x . Debemos restringir el
all (\c -> isSpace c || puede hacerlo declarando los tipos: tipo por medio de una anotación:
isLower c ||
isUpper c) str negateAll :: [Int] -> [Int] canParseInt x = show (read x :: Int)
7
Las anotaciones tienen la misma sintaxis que Igual que en la comparación de patrones, el token Orden de Comparación La comparación pro-
las declaraciones de tipo, pero pueden adornar ‘_’ es un “comodín” que coincide con cualquier cede de arriba hacia abajo. Si el orden de
cualquier expresión. Nótese que la anotación en valor. anyChoice se modifica de la siguiente forma, el
el ejemplo arriba está en la expresión read x , no primer patrón siempre tendrá éxito:
en la variable x. Solamente la aplicación de fun-
ción (e.g., read x ) asocia más fuertemente que las Anidado & Captura Se permite hacer com- anyChoice3 ch =
anotaciones. Si ese no fuera el caso, habría sido paración y asociación anidadas. case ch of
necesario escribir (read x) :: Int . _ -> "Something else."
data Maybe a = Just a | Nothing Nothing -> "No choice!"
Unidad Just (First _) -> "First!"
Figure 1: La definición de Maybe Just Second -> "Second!"
() – tipo “unidad” y valor “unidad”. El valor y
tipo que no representa información útil.
Guardas Las guardas, o comparaciones condi-
Usando Maybepodemos determinar si alguna
cionales, se pueden utilizar en casos igual que en
opción fue proporcionada utilizando una com-
Palabras Clave la definición de funciones. La única diferencia es
paración anidada:
el uso de -> en lugar de =. Esta es una función
Las palabras clave en Haskell están listadas a con- que hace comparación de cadenas sin importar si
tinuación, en orden alfabético. anyChoice1 ch = las letras son mayúscula o minúscula:
case ch of
Nothing -> "No choice!" strcmp s1 s2 = case (s1, s2) of
Case Just (First _) -> "First!" ([], []) -> True
case es similar a la declaración switch en C# o Just Second -> "Second!" (s1:ss1, s2:ss2)
Java, pero puede hacer comparación de un patrón: _ -> "Something else." | toUpper s1 == toUpper s2 ->
la forma del valor siendo inspeccionado. Con- strcmp ss1 ss2
sidere un tipo de datos simple. Se puede asociar un nombre al valor comparado | otherwise -> False
para poder manipularlo: _ -> False
data Choices = First String | Second |
Third | Fourth Clases
anyChoice2 ch =
case puede ser utilizado para determinar qué op- case ch of Una función en Haskell es definida para funcionar
ción se seleccionó: Nothing -> "No choice!" con un cierto tipo o conjunto de tipos y no puede
Just score@(First "gold") -> ser definida más de una vez. Muchos lengua-
whichChoice ch = "First with gold!" jes cuentan con el concepto de “sobrecarga”,
case ch of Just score@(First _) -> donde una función puede tener diferente com-
First _ -> "1st!" "First with something else: " portamiento dependiendo del tipo de sus argu-
Second -> "2nd!" ++ show score mentos. Haskell implementa sobrecarga a través
_ -> "Something else." _ -> "Not first." de declaraciones de clase y de instancia . Una
8
clase define una o más funciones que pueden ser Defaults Se pueden dar implementaciones por Constructores con Argumentos El tipo ar-
aplicadas a cualquier tipo que sea miembro (i.e. default para las funciones en una clase. Éstas riba noes muy interesante excepto como una enu-
instancia) de esa clase. Una clase es análoga a una son útiles cuando ciertas funciones pueden ser meración. Se pueden declarar constructores que
interface en Java o C#, y, las intancias, a una im- definidas en términos de otras en la clase. Un de- tomen argumentos, permitiendo que se almacene
plementación concreta de la interface. fault es definido dadno un cuerpo a una de las más información:
Una clase debe ser declarada con una o más funciones miembro. El ejemplo canónico es Eq,
variables de tipo. Técnicamente, Haskell 98 sola- que define /= (no igual) en términos de ==. : data Point = TwoD Int Int
mente permite una variable de tipo, pero muchas | ThreeD Int Int Int
implementaciones de Haskell implementan tipos class Eq a where
de clase multi-parámetro, que permiten más de una (==) :: a -> a -> Bool Note que los argumentos para cada constructor
variable de tipo. (/=) :: a -> a -> Bool son nombres de tipo, no constructores. Eso sig-
Podemos definir una clase que provee un “sa- (/=) a b = not (a == b) nifica que una declaración como la siguiente es
bor” para un tipo dado: ilegal:
Se pueden crear definiciones recursivas. Contin-
class Flavor a where uando con el ejemplo de Eq, == puede ser definido
data Poly = Triangle TwoD TwoD TwoD
flavor :: a -> String entérminos de /= :
9
Variables de Tipo Declarar tipos de datos data Con = Con { conValue :: String } que automáticamente implementa la clase de tipos
polimórficos es tan fácil como agregar variables de | Uncon { conValue :: String } al tipo asociado. Las siete clases de tipos que per-
tipo en la declaración: | Noncon miten hacerlo son: Eq, Read, Show, Ord, Enum , Ix , y
Bounded.
data Slot1 a = Slot1 a | Empty1 whichCon con = "convalue is " ++ Hay dos formas de usar deriving . La primera
Esto declara un tipo Slot1 con dos constructores, conValue con se utiliza cuando un tipo solamente deriva una
Slot1 y Empty1. El constructor Slot1 puede tomar clase:
Si whichCon es invocado con un valor Noncon,
un argumento de cualquier tipo, que es represen-
ocurrirá un error.
tado por la variable de tipo a arriba.
Finalmente, como se explica en otras partes, data Priority = Low | Medium | High
También podemos mezclar variables de tipo y
esos nombres se pueden utilizar para compara- deriving Show
tipos específicos en los constructores:
ción de patrones, captura y “actualización”.
data Slot2 a = Slot2 a Int | Empty2 El segundo es usado cuando se derivan múltiples
Restricciones de Clase Se pueden declarar clases:
Arriba, el constructor Slot2 puede tomar un valor
tipos de datos con restricciones de clase en las
de cualquier tipo y un valor Int .
variables de tipo, pero en general esta práctica es
desaprobada. Generalmente es mejor ocultar los data Alarm = Soft | Loud | Deafening
Sintaxis de Registro Se pueden declarar los constructores de datos empleando el sistema de deriving (Read, Show)
argumentos del constructor ya sea por su posi- módulos y exportar constructores “inteligentes”
ción, como se hace arriba, o utilizando sintaxis de que apliquen las restricciones apropiadas. En
registros, que le da un nombre a cada argumento. Es un error de sintaxis especificar deriving para
cualquier caso, la sintaxis es: ninguna otra clase además de las indicadas.
Por ejemplo, aquí declaramos un tipo Contact con
nombres para los argumentos apropiados: data (Num a) => SomeNumber a = Two a a
| Three a a a
data Contact = Contact { ctName :: String Deriving
, ctEmail :: String
Esto declara un tipo SomeNumber que tiene un ar-
, ctPhone :: String } Vea la sección en deriving bajo la palabra clave
gumento de variable de tipo. Los tipos válidos son
data en la página 10.
A esos nombres se les llama funciones selector los que están en la clase Num
.
o accesor y son eso, funciones. Deben empezar
con minúscula o guión bajo y no pueden tener el Deriving Muchos tipos tienen operaciones en Do
mismo nombre que otra función en el mismo con- común que son tediosas para definir aunque nece-
texto. Por eso el prefijo “ ct ” en el ejemplo arriba. sarias, como la habilidad de convertir de y a cade- La palabra clave do indica que el código a con-
Varios constructores (del mismo tipo) pueden uti- nas, comparar igualdad, u ordenar en secuencia. tinuación estará en un contexto monádico. Las
lizar la misma función accesor para valores del Esas capacidades están definidas como clases de declaraciones están separadas por saltos de línea,
mismo tipo, pero esto puede ser peligroso si el ac- tipos en Haskell. la “asignación” es indicada por <- , y se puede em-
cesor no es utilizado por todos los constructores. Como siete de estas operaciones son muy co- plear una forma de let que no requiere la palabra
Considere el siguiente ejemplo: munes, Haskell provee la palabra clave deriving clave in .
10
If con IO if puede ser complicado cuando se right2 fileName = do case args of
utiliza con IO. Conceptualmente no es diferente exists <- doesFileExist fileName [] -> putStrLn "No args given."
de un if en cualquier otro contexto, pero intuiti- let result = file -> do
vamente es difícil de asimilar. Considere la fun- if exists f <- readFile file
ción doesFileExists de System.Directory : then 1 putStrLn ("The file is " ++
else 0 show (length f)
doesFileExist :: FilePath -> IO Bool return result ++ " bytes long.")
La declaración if tiene esta pseudo-“declaración
Una vez más, notar donde está return . No lo Una sintaxis alternativa usa llaves y punto y coma.
de tipos”:
ponemos en la declaración let . En lugar de eso Todavía se requiere un do, pero la sangría es in-
if-then-else :: Bool -> a -> a -> a lo usamos una vez al final de la función. necesaria. Este código muestra un ejemplo de
case, pero el principio aplica igual con if :
Eso es, toma un valor Bool y evalúa a algún otro do’s Multiples Al usar do con if o case, se re-
valor con base en la condición. De la declaración quiere otro do is cualquier rama tiene múltiples countBytes3 =
de tipos está claro que doesFileExist no puede declaraciones. Un ejemplo con if : do
ser utilizado directamente por if :
putStrLn "Enter a filename."
countBytes1 f =
wrong fileName = args <- getLine
do
if doesFileExist fileName case args of
putStrLn "Enter a filename."
then ... [] -> putStrLn "No args given."
args <- getLine
else ... file -> do { f <- readFile file;
if length args == 0
putStrLn ("The file is " ++
Eso es, doesFileExost resulta en un valor -- no 'do'.
show (length f)
IO Bool , mientras que if quiere un valor Bool. then putStrLn "No filename given."
++ " bytes long."); }
El valor correcto debe ser “extraído” ejecutando la else
acción IO: -- multiple statements require
-- a new 'do'. Export
right1 fileName = do do
Vea la sección module en la página 12.
exists <- doesFileExist fileName f <- readFile args
if exists putStrLn ("The file is " ++
then return 1 show (length f) If, Then, Else
else return 0 ++ " bytes long.")
Recuerde, if siempre “devuelve” un valor. Es una
Note el uso de return . Como do nos coloca “den- Y uno con case: expresión, no solamente una declaración de con-
tro” de la mónada IO, no podemos “salir”excepto trol de flujo. Esta función revisa si la cadena dada
a través de return . Nótese que no tenemos countBytes2 = inicia con letra minúscula, y, de ser así, la con-
que usar if directamente aquí—también podemos do vierte a mayúscula:
usar let para evaluar la condición y obtener un putStrLn "Enter a filename." lower case letter and, if so, converts it to upper
resultado primero: args <- getLine case:
11
-- Usa comparación de patrones es usado por mappara dar los múltiplos de x por onlyThree str =
-- para obtener el primer caracter 1 hasta 10: let (a:b:c:[]) = str
sentenceCase (s:rest) = in "The characters given are: " ++
multiples x =
if isLower s show a ++ ", " ++ show b ++
let mult n = n * x
then toUpper s : rest ", and " ++ show c
in map mult [1..10]
else s : rest
-- Cualquier otro caso es Las “funciones” let sin argumentos son en reali-
-- sobre cadena vacía dad constantes y, una vez que son evaluadas, no Of
sentenceCase _ = [] serán evaluadas otra vez. Esto es útil para cap-
turar porciones comunes de su función y reuti- Vea la sección case en la página 8.
Import lizarlas. El siguiente es un ejemplo que da la suma
de una lista de números, su promedio, y su medi-
Ver la sección moduleen la página 12. ana: Module
12
Import Las bibliotecas estándar Haskell están En el caso de las clases, podemos importar las fun- para módulos que tienen un gran número de fun-
divididas en un número de módulos. Se accede a ciones definidas para una clase usando una sin- ciones con el mismo nombre que funciones del
la funcionalidad provista por esas bibliotecas im- taxis similar a importar constructores para tipos Prelude . Data.Set es un buen ejemplo:
portándolas en el programa fuente. Para importar de datos:
todo lo que exporta una biblioteca, simplemente
use el nombre del módulo: import Text.Read (Read(readsPrec
import qualified Data.Set as Set
, readList))
import Text.Read
Note que, a diferencia de los tipos de datos, todas
Todo significa todo: funciones, tipos de datos y Esta forma requiere que cualquier función,
las funciones de clase son importadas a menos que
constructores, declaraciones de clase, y aún otros tipo, constructor u otro nombre exportado por
sean explícitamente excluídas. Para importar sólo
módulos importados y luego exportados por ese Data.Set tenga el prefijo del alias dado (i.e., Set).
las clases, usamos esta sintaxis:
módulo. Para importar selectivamente se pasa La siguiente es una forma de remover todos los dupli-
una lista de nombres qué importar. Pr ejemplo, import Text.Read (Read()) cados de una lista:
aquí importamos algunas funciones deText.Read:
Podemos también importar tipos y clases Import Qualified Los nombres exportados por Excepto por el prefijo especificado, la importación cal-
definidos en el módulo: un módulo (i.e., funciones, tipos, operadores, etc.) ificada emplea la misma sintaxis que una importación
pueden tienen un prefijo asociado a través de im- normal. Los nombres importados se pueden limitar de
import Text.Read (Read, ReadS) portación calificada. Esto es particularmente útil las mismas formas descritas arriba.
13
Export Si no se provee una lista de exportaciones, Newtype • El verificador de tipos puede hacer que tipos co-
entonces todas las funciones, tipos, constructores, etc. munes como Int o String se usen de formas re-
Mientras que data agrega nuevos valores y type
estarán disponibles siempre que se importe el módulo. stringidas, especificadas por el programador.
solamente crea sinónimos, newtype está en un
Note que los módulos importados no son exportados Finalmente, se debe notar que cualquier cláusula
punto medio. La sintaxis para newtype está más
en este caso. Limitar los nombres exportados se con- deriving que puede ser anexada a una declaración
restringida—solamente se puede definir un construc-
sigue agregando una lista entre paréntesis de los nom- data puede también ser usada al declarar un newtype.
tor, y ese constructor solamente puede tomar un argu-
bres antes de la palabra clave where:
mento. Continuando con el ejemplo de arriba, podemos
module MyModule (MyType definir un tipo Phonede la forma que sigue: Return
, MyClass Ver do en la página 10.
, myFunc1 newtype Home = H String
...) newtype Work = W String
Type
where data Phone = Phone Home Work
Esta palabra clave define un sinónimo de tipo (i.e.,
La misma sintaxis que se usa para importar puede ser En contraste con type , los “valores” H y Wen alias). Esta palabra clave no define un tipo nuevo, como
usada para especificar qué funciones, tipos, construc- Phoneno son solamente valores String . El verificador data o newtype. Es útil para documentar código pero,
tores, y clases son exportados, con unas pocas diferen- de tipos los trata como tipos completamente nuevos. además de eso, no tiene efecto en el tipo de una función
cias. Si un módulo importa otro módulo, puede también Eso significa que nuestra función lowerNamearriba no o valor dados. Por ejemplo, un tipo de datos Person
exportar ese módulo: compilaría. Esto produce un error de tipos: puede ser definido como:
module MyBigModule (module Data.Set
, module Data.Char) lPhone (Phone hm wk) = data Person = Person String String
where Phone (lower hm) (lower wk)
donde el argumento del primer constructor representa
En lugar de eso, debemos usar comparación de patrones al nombre y el segundo al apellido. Sin embargo, el
import Data.Set
para llegar a los “valores” a los que queremos aplicar orden y significado de los dos argumentos no es muy
import Data.Char
lower : claro. Una declaración con type puede ayudar:
Un módulo puede también re-exportarse a sí mismo,
lo que puede ser útil cuando todas las definiciones lo- lPhone (Phone (H hm) (W wk)) = type FirstName = String
cales y un módulo importado dado van a se exportados. Phone (H (lower hm)) (W (lower wk)) type LastName = String
A continuación nos exportamos a nosotros mismos y a data Person = Person FirstName LastName
Data.Set , pero no a Data.Char: La observación clave es que esta palabra clave no crea
Como type crea un sinónimo, la verificación de tipos
un valor nuevo; en lugar de eso crea un tipo nuevo.
module AnotherBigModule (module Data.Set no se afecta. La función lower , definida como:
Esto proporciona dos propiedades muy útiles:
, module AnotherBigModule) • No hay costo en tiempo de ejecución asociado al
where lower s = map toLower s
tipo nuevo, porque en realidad no produce valores
nuevos. En otras palabras, newtype es absoluta- que tiene tipo
import Data.Set mente gratis en desempeño cuando el programa
import Data.Char es ejecutado. lower :: String -> String
14
puede ser usada igual en valores con tipo FirstName o es la función actual. Si una función está dividida en
LastName: definiciones múltiples con comparación de patrones, en-
tonces el contexto de una cláusula where en particular
lName (Person f l ) = solamente aplica a esa definición. Por ejemplo, la fun-
Person (lower f) (lower l) ción result a continuación tiene un significado difer-
ente dependiendo de los argumentos proporcionados a
Como type es solamente un sinónimo, no puede
la función strlen :
declarar múltiples constructores de la forma que data
puede. Se pueden usar variables de tipo, pero no puede strlen [] = result
haber más que las las variables de tipo declaradas con where result = "No string given!"
el tipo original. Eso significa que un sinónimo como el strlen f = result ++ " characters long!"
siguiente es válido: where result = show (length f)
Where
Traducción al Español
De la misma forma que let , where define funciones y
constantes locales. El contexto de una definición where Jaime Soffer, [email protected]
15