Está en la página 1de 9

¿Qué es Haskell?

Haskell es un lenguaje de programación funcional, creado a finales de 1980 por un


comité de académicos. Había una gran cantidad de lenguajes funcionales, todo el
mundo tenía su favorito, y era difícil intercambiar ideas. Así fue que un Grupo se reunió
y diseñó un nuevo lenguaje, tomando algunas de las mejores ideas de los ya
existentes (y algunas nuevas ideas). Y así nació Haskell.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 95
Características

Cuando hablamos de Haskell sabemos que estamos hablando de un lenguajeque


cumple con una serie de características. Es un lenguaje funcional, puro, perezoso,
tipado estaticamente e inferencia de tipos, asimismo brinda un contexto propicio para
la programación integral y la abstracción.

Funcional

No hay, una acepción precisa del término "funcional", que etimológicamente se ajuste
a su aplicación en este contexto. Pero cuando decimos que Haskell es un lenguaje
funcional, solemos tener en cuenta dos cosas:
 Las funciones tienen aquí preponderancia, y estas se pueden utilizar de la
misma forma que cualquier otro tipo de valor.
 Los programas Haskell se centran en la evaluación de expresiones, en lugar
de ejecutar instrucciones.
Esto resulta en una forma completamente diferente de pensar a la programación.

Puro

Las expresiones de Haskell son siempre referencialmente transparentes, es decir:


 ¡Sin mutaciones! Todas (las variables, las estructuras de datos...) son
inmutables. (No modificable “su significado o contenido” una vez creado)
 Las expresiones carecen de efectos secundarios o colaterales, que es una
consecuencia de la transparencia referencial.
 Llamar a la misma función con los mismos argumentos da como resultado la
misma salida en todo momento.
Esto puede parecer un poco raro en este momento. ¿Cómo es posible hacer algo sin
mutación o efectos secundarios? Bueno, desde luego requiere un cambio radical en
la forma de pensar (si se está acostumbrado a un paradigma imperativo u orientado a
objetos). Pero una vez que has hecho el cambio, hay una serie de aspectos
beneficiosos:
 razonamiento ecuacional y refactorización: En Haskell siempre se puede
"reemplazar una parte por su equivalente", tal como en álgebra.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 96
 Paralelismo: La evaluación de expresiones en paralelo es fácil, cuando se
garantiza no afectar a la otra.

Perezoso

En Haskell, las expresiones no se evalúan, hasta que sus resultados son realmente
necesarios. Esta es una característica sencilla, con consecuencias de gran alcance,
lo cual vamos a explorar. Algunas de las consecuencias son:
 Es fácil reemplazar las estructuras de control, sólo mediante la definición de
una función.
 Es posible definir y trabajar con estructuras de datos infinitas.
 Permite un estilo de programación más composicional (ver programación
integral más abajo).

Tipos
Tipado estáticamente

Cada expresión Haskell tiene asociado un tipo, dichos tipos son controlados en tiempo
de compilación. Los programas con errores de tipo no corren, ni siquiera se compilan
exitosamente.
Los sistemas de tipos estáticos pueden parecer tediosos. De hecho, pasa también
en lenguajes como C ++ y Java. Pero esto se debe a que los sistemas de tipo estático
como el C ++ y Java son insuficientemente expresivos6. Vamos a echar un vistazo
de cerca al sistema de tipos de Haskell, que:
 Ayuda a clarificar el pensamiento y a estructurar un programa expresivo.
El primer paso para escribir un programa Haskell suele ser anotar todos los
tipos. Porque el sistema de tipos de Haskell es expresivo, este es una etapa
no trivial del diseño.
 Sirve como una forma de documentación cuando un sistema es expresivo, sólo
al mirar el tipo de una función dice mucho acerca de lo que esta puede hacer y

6
Un programa fuente es expresivo cuando no hace falta aclarar nada, sino que se entiende con solo leerlo.
Un lenguaje es expresivo si permite crear programas fuentes expresivos.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 97
cómo se puede utilizar, incluso antes de haber leído una sola palabra de la
documentación escrita.
 Convierte errores en tiempo de ejecución en errores en tiempo de compilación.
 Es mucho mejor poder corregir los errores en la cabecera, que simplemente
probar sucesivamente y esperar lo mejor (aun así es muy posible tener errores
en la lógica, incluso en un programa tipado correctamente).

Inferencia de Tipos

Otra de las características de Haskell respecto de los tipos, es la capacidad de inferir


el tipo de una expresión, sin necesidad que el programador lo especifique. Esto lo
logra analizando las operaciones a las que una expresión es sometida. El sistema
asigna el tipo general que contenga todas las instancias posibles de la expresión.

Abstracción

 Teniendo piezas similares de código, factorizar para determinar que tienen


en común, es conocido como el proceso de abstracción. "No repitas" es un
preconcepto que se escucha a menudo en el mundo de la programación. Nada
debe estar duplicado: todas las ideas, algoritmos, y parte de los datos debe
ocurrir exactamente una vez en el código.
 Haskell es muy bueno en la abstracción: características como el polimorfismo
paramétrico, funciones de orden superior y las clases de tipo, ayudan para
evitar la repetición.

Programación integral

La programación integral implica pensar en grande: trabajar con una lista completa,
en lugar de una secuencia de elementos; desarrollar un espacio de soluciones, en
lugar de una solución individual; imaginar una trayectoria, en lugar de un tramo de
esta. El enfoque integral a menudo ofrece nuevos puntos de vista u ofrecen nuevas
perspectivas sobre un problema dado. Está muy bien complementado por la idea de
la programación proyectiva: primero resolver un problema más general, a
continuación, extraer las partes y piezas interesantes mediante la transformación del
programa general en otras más especializadas.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 98
Por ejemplo, considere este pseudocódigo imperativo:
lst = {2,3,5,7,11};
int total = 0;
for ( int i = 0; i < lst.length; i++)
total = total + 3 * lst[i];
print total;

Este código tiene que preocuparse por los detalles de bajo nivel de iteración del
arreglo mediante el seguimiento de un índice. También se mezcla lo importante con
lo accesorio necesarios para implementar el ciclo repetitivo.
En Haskell, se crea un programa fuente con las siguientes sentencias:

lst :: [Int] -- lst es una lista de enteros


lst = [2,3,5,7,11] -- Los valores de lst son
total = sum (map (3*) lst) -- Hacer la suma de cada elemento x3
main = print total 
Se corre y la salida sera: 84

Declaraciones y Variables

Analicemos el siguiente código en Haskell:


x :: Int
x = 3
-- Observe que los comentarios se preceden con dos guiones
{- o encerrado en un par de llaves y guiones. -}
-- x = 4
main = print x 
Pruebe descomentar la 5ta. línea, y verá el error “Multiple declarations of 'x'”.
El código anterior declara una variable x con el tipo Int (:: se lee "tiene tipo") y
declara que el valor de x sea 3. Tenga en cuenta que solo este será el valor de x,
mientras dure el programa. El valor de x no cambiará más adelante. En Haskell, las
variables no son cajas mutables; no son más que valores estaticos con identificador.

Dicho de otra manera, el símbolo ‘=’ no denota asignación, tal com sucede en
muchos otros lenguajes imperativos. Sino que denota una definición, como se lo hace
en matemática. Es decir, x = 4 no debería leerse como "4 es asignado a x" o "asignar
a x un 4", sino como "x se define como 4" o “x es 4”.

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 99
Veamos que hace el siguiente código

y :: Int
y = y + 1
main = print y

Debido a que = denota definición en lugar de asignación, esta no se incrementa el
valor de y. En su lugar, esta afirmación se toma como una definición recursiva.

Las sucesivas producciones internas seran:

y = y + 1
= (y + 1) + 1
= ((y + 1) + 1) + 1
=
.
.
.
En definitiva, el resultando es un loop infinito y el consiguiente error:

ERROR - C stack overflow

Tipos básicos
-- Enteros dependientes de la arquitectura e implementación
i :: Int
i = -78
32 32
Los Int del lenguaje Haskell estándar garantizan valores en rango [-(2 ) .. (2 -1)]
sin embargo, el tamaño exacto depende de la arquitectura. Se puede encontrar el
rango específico de una arquitectura eimplementación particular mediante la
evaluación siguiente:

print (minBound :: Int, maxBound :: Int)7


La salida en una implementación sobre una cierta arquitectura será:
(-2147483648,2147483647)
Asimismo Haskell implementa además el tipo Integer, que difiere deltipo int, debido a
que sólo está limitado por la cantidad de memoria en la máquina.

7
Note que Haskell utiliza la convención CamelCase en la forma de darle nombre a los identificadores

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 100
-- Enteros de precision Arbitraria
n :: Integer
n = 1234567890987654321987340982334987349872349874534
realmenteGrande :: Integer
realmenteGrande = 2^(2^(2^(2^2)))
numDigitos :: Int
numDigitos = length (show realmenteGrande)
-- show lo convierte en String
main = print numDigitos

La salida es: 19729
-- Para los nros reales se utiliza el Double.
-- El Float se usa menos
d1, d2 :: Double
d1 = 4.5387
d2 = 6.2831e-4

-- Booleanos
b1, b2 :: Bool
b1 = True
b2 = False

-- caracteres Unicode
c1, c2, c3 :: Char
c1 = 'x'
c2 = 'Ø'
c3 = 'ダ'

-- Los Strings son listas de caracteres con una sintaxis


especial
s :: String
s = "Hello, Haskell!"
Aritmetica
ex01 = 3 + 2
ex02 = 19 - 27
ex03 = 2.35 * 8.6
ex04 = 8.7 / 3.1
ex05 = mod 19 3
ex06 = 19 `mod` 3
ex07 = 7 ^ 222
ex08 = (-3) * (-7)
Note como `` acentos abiertos hacen a una función operador infijo.
i = 30 :: Int
n = 10 :: Integer
main = print (i + n)

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 101
La suma es sólo entre valores del mismo tipo numérico, y Haskell no tiene
promoción de tipos implícita. Se debe convertir explícitamente:
 fromIntegral: convierte algún tipo entero (Int o Integer) a algún otro tipo
numérico.
Solución al inconveniente de inconsistencia anterior:
i = 30 :: Int
n = 10 :: Integer
main = print (i + fromIntegral n)
 round, floor, ceiling: convierte numeros punto flotante a Int.
main = print (round 1.6, floor 1.6, ceiling 1.3)
La salida es: (2, 1, 2)
round: redondea el argumento.
floor: Trunca. Devuelve el mayor entero menor al argumento.
ceiling: Devuelve el menor entero posterior al argumento.
i = 30 :: Int
main = print (i / i)
Esto devuelve un error al pretender hacer una división coma flotante entre dos enteros.
Para la división entera se debe utilizar el operador div:
i = 30 :: Int
main = print (i `div` i, 12 `div` 5)
La salida es: (1, 2)
Lógica Booleana

Los valores booleanos se pueden comparar con (&&), (| |), y not (and, or y not lógicos,
respectivamente).

ex11 = True && False


ex12 = not (False || True)
Y se puede comparar por la igualdad con (==) y (/ =), o para determinar el orden
usando (<), (>), (<=) y (> =).
ex13 = ('a' == 'a')
ex14 = (16 != 3)
ex15 = (5 > 3) && ('p' <= 'q')
ex16 = "Haskell" > "C++"
Haskell no tiene estructuras lógicas, pero si cuenta con las expresiones

if: if b then t else f

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 102
es una expresión que se evalúa como t si la expresión booleana b se evalúa como
True, y f si b se evalúa como False. Se debe tener en cuenta que las expresiones if
son muy diferentes de las sentencias if. Por ejemplo, con una sentencia if, la parte
else puede ser opcional; si el else es omitido significa que "si la expresión se evalúa
como False, no se hará nada", en cambio, en la expresión if, se requiere la parte else,
de la expresión if debe dar lugar a un cierto valor.

Definición de Funciones Básicas


Veamos el siguiente caso de función con enteros.
-- Calcule la suma para enteros >=0.
sumatorial :: Integer -> Integer
sumatorial 0 = 0
sumatorial n = n + sumatorial (n - 1)

main = print (sumatorial 10)



Tenga en cuenta que la sintaxis para el tipo de una función sumatorial :: Integer ->
Integer dice que sumatorial es una función que toma un Integer como entrada y
produce otro Integer como salida.

Cada cláusula se evalúa en orden, de arriba hacia abajo, comprobando la primera


cláusula de equivalencia. Por ejemplo, sumatorial 0 se evalúa como 0, ya que la
primera cláusula es comprobada. En cambio, sumtorial 3 no coincide con la primera
cláusula (3 no es 0), por lo que es evaluada la segunda cláusula. Una variable como
n coincide con cualquier cosa, por lo que la segunda cláusula coincide y sumtorial 3
se evalúa como 3 + sumtorial (3-1) (que puede ser evaluado más adelante).

Paradigmas y Lenguajes / Paradigmas de Programación


Pag. 103

También podría gustarte