Introducción- CaracterĆsticas del paradigma:
declarativo, basado en funciones,no efecto de lado, si son funciones devuelven un único resultado, nohay órdenes: sólo expresiones.
- CaracterĆsticas del lenguaje:
fuertemente tipado
- CaracterĆsticas del entorno de trabajo:
al igual que en prolog tenemos programa por un lado y un entorno de consultas por otro.
Elementos bÔsicos del lenguajeNo hay muchas cosas novedosas, asà que rÔpidamente podemos enumerar qué herramientas aparecen en el nuevo lenguaje: - Números, que se pueden sumar, restar, comparar, sqrt
- Booleanos, que podemos operar con: && || not
- Listas, son muy parecidas a las de prolog aunque la sintaxis puede ser un poco diferente: [1,2,3], [1..3], [1,3..11]
- por ahorapodemos saber longitud, concatenar, sum, !!, elem, head, tail.Ā
- Al no haber efecto de lado, obviamente las listas no se pueden modificar
- Tuplas con fst y snd.
- Para todos vale comparar con == y /=
Luego hablamos de funciones: - Algunos ejemplos de funciones que mencionamos antes son length y sum
- Ojo, tambiƩn + y ++.
- La única diferencia es que se escriben en forma infija, en contraposición a las anteriores que utilizan forma prefija.
- Cualquier operador es una función, y es posible definir nuestras propias funciones haciendo eso.
- Analizamos la cantidad de parƔmetros de diferentes funciones, repasamos el concepto de aridad.
- Definimos el concepto de aplicación y mostramos que a diferencia de lo que suele pasar, en Haskell no se utiliza paréntesis para aplicar
Elementos bƔsicos del lenguajeEn la mƔquina podemos ver los tipos de algunas cosas, por ejemplo: - 1 tiene tipo Num (por ahora vamos a obviar el Int a => a, lo consideramos equivalente a Int.
- True tiene tipo Bool
- [1,2,3] tiene tipo [Num]
- ("Juan", 23) tiene tipo ([Char], Int)
TambiƩn de las funciones podemos conocer su tipo, por ejemplo: - not tiene tipo Bool -> Bool
- + tiene tipo Num -> Num -> Num (por ahora podemos considerarlo equivalente a (Num x Num) -> Num, mƔs adelante se verƔ la diferencia).
El hecho de que podamos preguntarnos por el tipo de una función, nos muestra que también las funciones son valores. es decir, se rompe la frontera entre la función y el valor. Definición de funcionesEjercitaciónEvaluación de funciones: sustitucionesPattern Matching- El pattern matching nos permite dos cosas:
- Poner mÔs de una definición de una función para que el matching determine cuÔl de las dos versiones se debe usar en cada caso.
- Al recibir un valor compuesto usar el encabezado de la función para separar sus componentes, esto vale tanto para listas como para tuplas.
- Esto es similar a lo que ya hicimos en lógico, con la salvedad de que en lógico, al trabajar con relaciones, todas las variantes de un predicado producen resultados, en cambio en funcional siempre se evaluarÔ sólo una de las definiciones de la función.
- Ejemplos bƔsicos:
esPar 0 = True esPar 1 = False esPar x = esPar (x - 2)
distancia (x1,y1) (x2,y2) = sqrt ((x1-x2)**2 + (y1-y2)**2)
- En una función que recibe tuplas, podemos recibirlas tanto con una variable como con un patrón mÔs complejo, por ejemplo:
sumarComponentes (a,b) = a + b
es equivalente a:
sumarComponentes t = fst t + snd t Es importante manejar ambas posibilidades y elegir cuƔl conviene mƔs en cada caso.
- Cuando usamos un patrón para desarmar un valor compuesto que recibimos por parÔmetro, podemos usar _ para designar las partes del valor compuesto que no nos interesan, por ejemplo si representÔramos fechas con una terna (dia, mes, año), podriamos hacer la función:
diaDelMes (dia,_,_) = dia
Guardas
- Las guardas también nos permiten establecer múltiples definiciones de una función. Una guarda es una expresión booleana que define si esa definición se puede utilizar, por ejemplo:
puntosLocal (golesLocal, golesVisitante) | golesLocal >Ā golesVisitante = 3 | golesLocal == golesVisitante = 1 | otherwiseĀ Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā = 0
La expresión otherwise nos permite definir una rama que se evaluarÔ en caso que todas las anteriores fallen.
- Al igual que en el caso anterior, es importante destacar que al estar hablando de funciones, sólo una de las ramas puede ser evaluada.
- Muy importante:
No debemos usar las guardas en reemplazo de las expresiones booleanas, por ejemplo la siguiente definición la consideraremos errónea:
primeraComponenteIgual t1 t2 | fst t1 == fst t2 = True | otherwiseĀ Ā Ā Ā Ā Ā Ā = False
La forma correcta es usar la expresión booleana:
primeraComponenteIgual t1 t2 = fst t1 == fst t2
O bien:
primeraComponenteIgual (x1,_) (x2,_) = x1 == x2
Normalmente el uso de guardas en una función que devuelve un booleano es causa de este tipo de error, y muestra una forma procedural de pensamiento.
Manipulación de funcionesLa caracterĆstica principal del paradigma funcional es la operación con funciones. Para eso vamos a definir trĆ©s formas bĆ”sicas de operar con funciones: - Composición
- Aplicación parcial
- Funciones de orden superior
En esta clase nos concentraremos en las dos primeras. Composición- La composición es similar a la que hemos visto en matemÔtica y nos permite definir una función fog que entendemos como "la composición de f con g" y se define (en matemÔtica) como (fog) (x) = f (g (x))
En Haskell vamos a utilizar un "." en lugar del famoso "cerito" (o). Podemos usarlo para definir una función como la composición de otras dos, por ejemplo    isUpper . head
Nos permite construir una función que recibe un String y me dice si la primera letra es mayúscula.
- Para poder aplicarle parÔmetros a esa función, es importante poner la composición entre paréntesis, ya que la aplicación de funciones tiene mayor precedencia que la composición (esto es importante, no te quedes sin entenderlo!):
Ā Ā Ā > (isUpper . head) "Hola" Ā Ā Ā True
- TambiĆ©n podemos ponerle un nombre a la función compuesta, asĆ:
Ā Ā Ā empiezaConMayĆŗscula = isUpper . head
Y luego la podrĆamos utilizar asĆ: Ā Ā Ā > empiezaConMayĆŗscula "hola" Ā Ā Ā False
Nótese que esta forma de definir funciones es sutilmente distinta a la que estĆ”bamos acostumbrados. Con lo que sabĆamos hasta ahora podrĆamos haber hecho: Ā Ā Ā empiezaConMayĆŗscula unString = isUpper (head unString)
Cuando definimos funciones por composición, podemos definir directamente la función sin necesidad de hablar de los parÔmetros.
- Podemos ver que la composición nos da una forma de definir una nueva función sin necesidad de ponerle un nombre ni ponerla entre nuestras definiciones de funciones.
Es decir, sqrt . doble, es una función como cualquier otra y puedo hacer con ella lo mismo que hago con las demÔs funciones, por ejemplo:
- Aplicarle parƔmetros (como en el ejemplo anterior).
- Consultar su tipo.
Para trabajar un poco mƔs con los tipos de funciones compuestas podemos analizar el tipo de even . length
- La composición no parece un asunto complicado, sin embargo es importante destacar algunos detalles para evitar errores muy comunes:
- que sólo (f.g) x se considera composición. Si yo hago f(g x) no estoy utilizando composición.
- la composición puede hacerse únicamente entre funciones, es decir, yo puedo componer sqrt con doble pero no sqrt con 4 (porque 4 es un número y no una función) y tampoco sqrt con doble 8 (doble 8 también es un número).
- (por lo anterior) los paréntesis aquà si son necesarios.
Si yo pongo, sin paréntesis, sqrt . doble 8, eso se entiende como composición entre sqrt y doble 8, debo utilizar los paréntesis para indicar que mi intención es componer sqrt con doble y luego a la función resultante aplicarle el parÔmetro 8.
Orden superior- Podemos ver algunos ejemplos de orden superior sencillitos
tup f g (x,y) = (f x, g y) aplicarATupla fun (x,y) = (fun x, fun y) tuplizar fun1 fun2 x = (fun1 x, fun2 x) comparar op (x,y) = x `op` y cuarta = f . f
Para despuƩs de la clase
|