Sie sind auf Seite 1von 6

Pre-Introducción a la Programación Funcional en

Haskell
Paradigmas de Lenguajes de Programación

20 cuatrimestre 2007

1. Expresiones, valores y tipos


Le ejecución de un programa en Haskell se realiza a través de la evalua-
ción de expresiones (términos sintácticos) que denotan valores (entidades
abstractas que entendemos como respuestas). Ejemplos de expresiones son
’a’
3+4
(5 > 0, 9)
cuyos valores asociados son la letra a, el número 7 y el par ordenado cuya
primera componente es el valor verdadero y al segunda el número 9.
Ası́ como podemos asociar las entidades reales o matemáticas con un
conjunto, las expresiones en Haskell tienen asociado un tipo. El sı́mbolo ::
sirve para indicar explı́citamente el tipo de las expresiones.
’a’ :: Char
3 + 4 :: Integer
(5 > 0, 9) :: (Bool, Integer)
El lenguaje tiene un conjunto de tipos predefinidos, de los cuales ya
mencionamos algunos:

Tipos simples como Integer, Double, Char, Bool, etc.

Tuplas de cualquier longitud. Por ejemplo, (2 ∗ 5 +1, 4 >0, 22.1) es de


tipo (Integer, Bool, Double).

1.1. Funciones
Una de las formas más interesantes de expresiones es la función, cuyo tipo
esta indicado por una flecha: a → b es el tipo de las funciones que reciben un
argumento de tipo a y devuelven un argumento de tipo b. Las funciones en
Haskell son expresiones como cualquier otra: pueden ser argumento de otras
funciones, devolverse como resultado, contenerse en estructuras de datos,
etc.

1
Las sintaxis principal para definir funciones es muy parecida a la notación
matemática: a través de ecuaciones. La aplicación de una función a un valor
f (x) se escribe f x. Por ejemplo, podemos definir la función sucesor de la
siguiente forma:
sucesor n = n + 1
es decir con una ecuación que indica que el sucesor de n es n + 1 para
cualquier n. Los operadores también son funciones, con notación infija.
En el mundo matemático la definición de una función no siempre se
corresponde con una única ecuación, sino que puede estar separada en casos:

signo : R →Z
 −1 si x < 0
signo(x) = 0 si x = 0
1 si x > 0

En Haskell, usamos guardas, que escribimos con el sı́mbolo | : una ecua-


ción de la forma f x | cond =resultado significa que la función evalúa a su
resultado sólo si se cumple cond:
signo :: Double → Integer
signo x | x < 0 = -1
| x == 0 = 0
| x>0 =1
Las funciones también se pueden definir caso por caso:
siguienteVocal :: Char → Char
siguienteVocal ’a’ = ’e’
siguienteVocal ’b’ = ’e’
...
siguienteVocal ’j’ = ’o’
etc.
Ya sea usando guardas o definiendo casos individuales, una función pue-
de definirse a través de más de una ecuación. En ese caso, si evaluamos la
función con un argumento determinado, a este no necesariamente le corres-
ponda una única ecuación: puede corresponderle más de una, en cuyo caso la
que se evalúa es la primera, y puede suceder que no le corresponda ninguna,
en cuyo caso (igual que en matemática) decimos que la función esta indefini-
da en ese argumento. A las funciones definidas en todos sus argumentos las
llamamos totales, y a las que se indefinen para algún argumento, parciales.
ejemplo 1: Programemos la siguiente función:

acotarAUnDigito : Z →
Z
 9 si n > 9
acotarAUnDigito(n) = −9 si n < −9
n en cualquier otro caso

Podemos usar guardas para separar los distintos casos:

2
acotarAUnDigito :: Integer → Integer
acotarAUnDigito n | n > 9 = 9
| n < -9 = -9
| n ≤ 9 && n ≥ -9 = n
Acá las tres condiciones de las guardas son disjuntas, pero la de la última
ecuación es un poco más complicada. Una opción podrı́a ser aprovechar el
orden de evaluación: la única forma de evaluar la última ecuación es que no
se cumpla ninguna de las condiciones anteriores, y si eso sucede, tenemos que
evaluarla para cualquier n. Podemos entonces reemplazar la última condición
por True, que en ese lugar de la definición, tiene el efecto “cualquier otro
caso” que buscamos:
acotarAUnDigito n | n > 9 = 9
| n < -9 = -9
| True = n
Haskell tiene predefinida la palabra otherwise como sinónimo de True
para una lectura más natural de la definición.
acotarAUnDigito n | n > 9 = 9
| n < -9 = -9
| otherwise = n
?

1.1.1. Recursión
Recordemos la definición (matemática) de la función factorial :

factorial : N → N
factorial (0) = 1
factorial (n) = n × factorial (n − 1) si n > 0
Podemos definir esta función en Haskell con una correspondencia directa:
factorial :: Integer → Integer
factorial 0 = 1
factorial n | n > 0 = n ∗ factorial (n-1)
Esta función es recursiva porque su evaluación (en ciertos argumentos)
involucra el llamado a la misma función que se esta definiendo. La recursión
es una herramienta poderosa y usada muy frecuentemente en los programas
en Haskell.

1.1.2. Currificación
También como en matemática, las funciones pueden tener más de un
argumento:

3
potencia : N × R → R
potencia(0, x) = 1
potencia(n, x) = x × potencia(n − 1, x) si n > 0
En Haskell, podemos usar tuplas para contener los argumentos necesa-
rios. En general, cuando una variable no aparece del lado derecho de una
ecuación, puede reemplazarse por un guión bajo (_).
potencia :: (Integer, Double) → Double
potencia (0, _) = 1
potencia (n, x) | n > 0 = x ∗ potencia (n - 1, x)
Otra forma de definirla es a través de la misma función currificada: una
función equivalente de un único argumento, que devuelve una función que
completa el trabajo:
potencia :: Integer → Double → Double
potencia 0 _ = 1
potencia n x | n > 0 = x ∗ potencia (n - 1) x
Acá, potencia es una función que recibe un entero n y devuelve la función
potencia n; esta función recibe a su vez un número x y devuelve el resultado
de elevar x a la n.
La currificación es una correspondencia entre las funciones del primer
estilo (argumentos en una tupla) y el segundo (argumentos yuxtapuestos).
Esta correspondencia siempre existe, y nos permite la definición de funciones
a través de la aplicación parcial. Se podrı́a definir por ejemplo:
cuadrado, cubo, potCuarta :: Double → Double
cuadrado = potencia 2
cubo = potencia 3
potCuarta = potencia 4
y luego usarlas para computar cuadrados, cubos, etc.

1.2. Listas
Otra familia de tipos predefinidos en el lenguaje es el de las listas: secuen-
cias ordenadas de elementos de un mismo tipo, con repeticiones. [Integer]
representa el tipo lista de enteros, [Bool] es una lista de booleanos, etc.
Las expresiones de tipo lista se construyen con [] (que representa la lista
vacı́a) y : (a:as es la lista que empieza con el elemento a y sigue con la lista
as). También pueden escribirse entre corchetes, con los elementos separados
por comas:
[] :: [Bool]
[3] :: [Integer]
’a’ : (’b’ : (’c’ : [])) :: [Char]
[2 > 0, False, ’a’ == ’b’] :: [Bool]
[[], [1], [1,2]] :: [[Integer]]

4
El tipo String es sinónimo de [Char], y las listas de este tipo se pueden
escribir entre comillas: "plp" es lo mismo que [’p’, ’l’, ’p’].

2. Definiendo nuevas expresiones y tipos


Haskell también permite definir nuevos tipos, con la clausula data. De-
finimos un nuevo tipo dando su nombre y describiendo la forma que tienen
los valores de ese tipo.
data Dia = Lun | Mar | Mie | Jue | Vie | Sab | Dom
Esta definición dice que un valor de tipo Dia consiste de uno de siete
posibles constructores, todos ellos sin argumentos.
data FormaGeometrica = Circulo Double | Rectangulo Double Double
Los valores de tipo FormaGeometrica son de la forma Circulo x o bien
Rectangulo x y, donde n y m son números. En este caso los constructores del
tipo reciben argumentos, que por ejemplo pueden representar el radio del
cı́rculo y la base y altura del rectángulo respectivamente.
data BinTree a = Nil | Branch a (BinTree a) (BinTree a)
Esta definición dice que, para cualquier tipo a, los valores BinTree a son
o bien Nil o Branch x t1 t2 donde x es de tipo a y t1 y t2 son a su vez
de tipo BinTree a. Este es un tipo paramétrico, pues su definición incluye
variables de tipo, y es también recursivo, porque algunos de los constructores
tienen argumentos del tipo que se esta definiendo.
Las funciones sobre tipos construidos con la clausula data pueden defi-
nirse por pattern matching. Del lado izquierdo de la ecuación pondremos “la
forma” del argumento que esperamos recibir: un constructor seguido de una
variable distinta por cada argumento que tenga. Del lado derecho, podemos
usar las variables para dar el resultado de la función en el caso que estamos
definiendo.
proximo :: Dia → Dia
proximo Lun = Mar
proximo Mar = Mie
etc.

area :: FormaGeometrica → Double


area (Circulo r) = 2 ∗ pi ∗ r
area (Rectangulo b h) = b ∗ h

sumaNodos :: BinTree Double → Double


sumaNodos Nil = 0
sumaNodos (Branch x t1 t2) = x + sumaNodos t1 + sumaNodos t2
Los tipos que permiten acceder a sus constructores y hacer pattern mat-
ching se llaman tipos algebraicos. ¡Los booleanos, las tuplas y las listas tam-
bién son tipos algebraicos!

5
fst :: (a, b) → a
fst (x, y) = x

length :: [a] → Integer


length [] = []
length (_:xs) = 1 + length xs

data Paridad = Par | Impar


paridades :: [Integer] → [Paridad]
paridades [] = []
paridades (n:ns) = if par n then Par else Impar : paridades ns
where par n = n ‘rem‘ 2 == 0

Referencias
[1] Página de Haskell
www.haskell.org

[2] A tour of the Haskell Prelude, describe y da ejemplos de las funciones


de uso más común
http://undergraduate.csse.uwa.edu.au/units/230.301/
lectureNotes/tourofprelude.html

[3] Haskell report es la especificación completa y oficial del lenguaje.


http://www.haskell.org/onlinereport

[4] A tour of the Haskell Syntax, una descripción más amigable de la


sintaxis de Haskell.
http://cs.anu.edu.au/Student/comp1100/haskell/tourofsyntax.
html

[5] A gentel introduction to Haskell, uno de los tutoriales más famosos y


bien completo. Incluye más temas que los que vamos a ver en la materia.
http://www.haskell.org/tutorial

Das könnte Ihnen auch gefallen