Cursosā€Ž > ā€ŽCursadas Anterioresā€Ž > ā€Ž2010ā€Ž > ā€ŽJueves MaƱanaā€Ž > ā€ŽFuncionalā€Ž > ā€Ž

Paradigma Funcional - Clase 1

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 lenguaje

No 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 lenguaje

En 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 funciones

  • Comenzamos definiendo la función doble, la forma mĆ”s sencilla podrĆ­a ser:
    doble x = 2 * x

  • Una vez hecho esto, podemos preguntar el tipo de doble, y obtenemos Num -> Num

  • Para reforzar aprovechamos a definir tambiĆ©n una función de dos parĆ”metros, a modo de ejemplo:
    esDivisiblePor x y = (y `mod` x) == 0

Ejercitación

  • Antes de continuar podemos hacer algunos ejercicios:
    primerElementoPar lista = even (head lista)

    sumarComponentest tupla = fst tupla + snd tupla

    longMayorQue string long = length string > long

Evaluación de funciones: sustituciones

  • Comenzamos con un ejemplo en el que podemos agregar esta definición de función
    f = 3 + doble x

  • Y nos detenemos a analizar cuĆ”l es el proceso para calcular el valor de f 4:
    f 4
    3 + doble 4
    3 + (2 * 4)
    3 + 8
    11

  • Podemos ver el concepto de sustitución al pasar del segundo paso al tercero, ĀæquĆ© pasa ahĆ­?
    • Tomamos la parte doble 4
    • Vemos que tenemos una definición para resolver eso: doble x = 2 * x
    • Podemos entonces reemplazar doble 4 por 2 * x, poniendo 4 donde decĆ­a x, es decir: 2 * 4.

  • Hacemos notar que no hay que entender 3 + doble 4 como si fuera un texto, el reemplazo se hace en una estructura jerĆ”rquica, la jerarquĆ­a puede entenderse asĆ­:
    • Es decir, el nodo principal es la suma (+), que tiene dos hijos (cada uno de sus operandos).
    • A la izquierda un operando simple (3)
    • A la derecha nos quedarĆ­a (doble 4)
      • Que podemos representarlo como un nodo doble con un Ćŗnico hijo 4.

    En todos los casos, las funciones son los nodos internos del Ôrbol, mientras que los valores simples son sus hojas. Cada función tiene como subÔrboles a cada uno de sus parÔmetros.

Pattern 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 funciones

La 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

  • El hecho de que las funciones sean valores significa que ademĆ”s de aplicarles parĆ”metros o consultar su tipo podemos hacer cualquier cosa que hacemos con los demĆ”s valores, en particular usarlas como parĆ”metro de otras funciones.

  • Esto es anĆ”logo a lo que ya vimos en lógico con respecto a los predicados de orden superior. La diferencia es que en lógico nunca definimos un predicado de orden superior, sólo los usamos, aquĆ­ vamos a animarnos a definir funciones de orden superior.
    (Ojo, no es que en lógico no se pueda, simplemente no lo hicimos por falta de tiempo.)

  • Un ejemplo sencillo de una función que recibe otra por parĆ”metro puede ser:
    aplicaDosVeces f x = f (f x)
  • 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