introalg:taller09_8
Diferencias
Muestra las diferencias entre dos versiones de la página.
Próxima revisión | Revisión previa | ||
introalg:taller09_8 [2009/05/15 01:03] – creado laura | introalg:taller09_8 [2018/08/10 03:03] (actual) – editor externo 127.0.0.1 | ||
---|---|---|---|
Línea 11: | Línea 11: | ||
</ | </ | ||
- | Este tipo de generalización es posible en haskell porque haskell tiene una propiedad llamada **alto orden** que permite que una función sea tomada como argumento de otra función. | + | Este tipo de generalización es posible en haskell porque haskell tiene una propiedad llamada **alto orden** que permite que una función sea tomada como argumento de otra función. Pero el alto orden nos permite no solamente tomar una función como argumento, sino también devolver una función como resultado. Esto resulta especialmente útil, porque nos permite usar funciones generales para definir funciones más concretas, concretando alguno de sus argumentos, como recién vimos en el caso de '' |
- | Vamos a ver ahora cómo generalizamos los tres diferentes tipos de funciones recursivas en listas. De hecho, el preludio estándar de haskell ya tenemos definidas las funciones generales '' | + | Vamos a ver ahora cómo generalizamos los tres diferentes tipos de funciones recursivas en listas. De hecho, el preludio estándar de haskell ya tenemos definidas las funciones generales '' |
Algo importante a tener en cuenta es que cuando definimos las versiones generalizadas de aplicaciones, | Algo importante a tener en cuenta es que cuando definimos las versiones generalizadas de aplicaciones, | ||
- | ==== Aplicaciones (map) ==== | + | ==== Tomar una función como argumento ==== |
+ | |||
+ | === Aplicaciones (map) === | ||
¿No estamos ya cansados de escribir siempre lo mismo? | ¿No estamos ya cansados de escribir siempre lo mismo? | ||
Línea 57: | Línea 59: | ||
Ahora podemos reescribir todas las aplicaciones usando '' | Ahora podemos reescribir todas las aplicaciones usando '' | ||
< | < | ||
- | duplicar' | + | duplicar :: [Int] -> [Int] |
duplicar xs = aplica (*2) xs | duplicar xs = aplica (*2) xs | ||
- | veintePorCiento' | + | veintePorCiento :: [Float] -> [Float] |
- | veintePorCiento xs = veintePorCiento | + | veintePorCiento xs = aplica |
</ | </ | ||
De esta forma, las funciones quedan mucho más compactas, más legibles y mucho más fáciles de mantener, ya que al tener que escribir menos, tenemos menos posibilidades de equivocarnos. Fíjense que ni siquiera tenemos que definir caso base y caso recursivo para cada una de las nuevas funciones que definimos con la función '' | De esta forma, las funciones quedan mucho más compactas, más legibles y mucho más fáciles de mantener, ya que al tener que escribir menos, tenemos menos posibilidades de equivocarnos. Fíjense que ni siquiera tenemos que definir caso base y caso recursivo para cada una de las nuevas funciones que definimos con la función '' | ||
- | ==== Filtros (filter) | + | === Filtros (filter) === |
Veamos funciones de //tipo filtro// que hemos creado: | Veamos funciones de //tipo filtro// que hemos creado: | ||
Línea 113: | Línea 115: | ||
Observemos que en el caso de los filtros, los elementos de la lista resultado tienen que ser del mismo tipo que los elementos de la lista que damos como argumento ('' | Observemos que en el caso de los filtros, los elementos de la lista resultado tienen que ser del mismo tipo que los elementos de la lista que damos como argumento ('' | ||
- | ==== Acumuladores (foldr) | + | === Acumuladores (foldr) === |
Más y más funciones fueron acumuladores, | Más y más funciones fueron acumuladores, | ||
Línea 132: | Línea 134: | ||
</ | </ | ||
- | ¿Cuál es la estructura común de todas estas funciones? Para poder verla bien, debemos saber que todo operador que usemos entre sus dos argumentos, como //+//, //:// o //++//, se puede usar también con la misma sintaxis que cualquier otra función, es decir, delante de sus dos argumentos. Para ello hay que poner al operador entre paréntesis, como hemos visto en el último de los ejemplos de arriba con el operador //++//. De esta forma, el caso recursivo de la función sumatoria también se puede escribir así: | + | ¿Cuál es la estructura común de todas estas funciones? Para poder verla bien, debemos saber que todo operador que usemos entre sus dos argumentos, como //+//, //:// o //++//, se puede usar también con la misma sintaxis que cualquier otra función, es decir, delante de sus dos argumentos. Para ello hay que poner al operador entre paréntesis. De esta forma, el caso recursivo de la función sumatoria también se puede escribir así: |
sumatoria (x:xs) = (+) x (sumatoria xs) | sumatoria (x:xs) = (+) x (sumatoria xs) | ||
- | Donde el primer número a sumar es //x// y el segundo es //sumatoria xs//, es decir, | + | Donde el primer número a sumar es '' |
Entonces, la estructura común de todas estas funciones es: | Entonces, la estructura común de todas estas funciones es: | ||
+ | < | ||
+ | fRecursiva :: [a] -> b | ||
+ | fRecursiva [] = neutro | ||
+ | fRecursiva (x:xs) = fNoRecursiva x (fRecursiva xs) | ||
+ | </ | ||
- | _________ | + | Observemos que hay dos diferencias principales con las aplicaciones y los filtros. Primero, tenemos un elemento más que debemos tratar con una variable y que por lo tanto tenemos que convertir en un argumento de la función: el neutro, que damos como resultado en el caso base. Segundo, la función que vamos a tomar como argumento es binaria, no unaria como en el caso de los filtros y las aplicaciones, |
- | _________ [] = _____ | + | |
- | _________ (x:xs) = _____ x (_________ xs) | + | |
+ | Por lo tanto, la generalización de los acumuladores quedaría como sigue: | ||
+ | < | ||
+ | acumula :: (a -> b -> b) -> b -> [a] -> b | ||
+ | acumula f n [] = n | ||
+ | acumula f n (x:xs) = f x (acumula f n xs) | ||
+ | </ | ||
- | Como el resultado de la función | + | Donde el primer argumento '' |
- | Por lo tanto, la generalización de los acumuladores | + | Ahora podemos escribir todas las funciones que son acumuladores |
+ | < | ||
+ | sumatoria xs = acumular (+) 0 xs | ||
+ | productoria xs = acumular (*) 1 xs | ||
+ | concatenaInt xs = acumular (++) [] xs | ||
+ | </ | ||
- | acumular :: (a -> b -> b) -> b -> [a] -> b | + | ==== Devolver una función como resultado ==== |
- | acumular f n [] = n | + | |
- | acumular f n (x: | + | |
- | Ahora podemos escribir todas las funciones | + | El alto orden nos permite no solamente tomar una función como argumento, sino también devolver una función como resultado. Esto resulta especialmente útil, porque nos permite usar funciones |
- | sumatoria xs = acumular (+) 0 xs | + | Nosotros ya hemos visto algunos ejemplos de este uso con las generalizaciones '' |
- | | + | < |
- | | + | multiplicar :: Int -> ( [Int] -> [Int] ) |
+ | </ | ||
+ | Y así la podemos usar para definir '' | ||
+ | < | ||
+ | duplicar :: [Int] -> [Int] | ||
+ | duplicar = multiplicar 2 | ||
+ | </ | ||
+ | Fíjense que '' | ||
- | O bien aplicando secciones, es decir, sin escribir los argumentos obvios: | + | Si una función no pudiera devolver como resultado otra función, no podríamos esperar que '' |
- | sumatoria | + | ===== Currificación ===== |
- | | + | |
- | | + | Todo lo que hemos estado explicando hasta ahora sobre alto orden está fundamentado en un trabajo muy serio y sistemático sobre matemática y computación. Vamos a comentar brevemente lo que más toca al tema de alto orden, la // |
+ | |||
+ | Hace mucho mucho tiempo hubo un hombre llamado Haskell Curry que pensó mucho en las funciones y se le ocurrió pensar una función n-aria como una composición de n funciones unarias. La primera de estas funciones unarias toma como argumento el primer argumento de la función n-aria original y devuelve como resultado una función que toma como argumento el argumento el segundo argumento de la función n-aria original y devuelve como resultado una función que toma como argumento el tercer argumento de la función n-aria original... y así hasta que llegamos a una función que toma como argumento el último argumento de la función n-aria original y devuelve como resultado el resultado de la función n-aria original. Podríamos expresarlo así: | ||
+ | < | ||
+ | f :: a -> b -> c -> d -> f -> g | ||
+ | >>>>> | ||
+ | f :: a -> ( b -> ( c -> ( d -> ( f -> g ) ) ) ) | ||
+ | </ | ||
+ | |||
+ | Así en abstracto puede resultar difícil de entender. Veamos un ejemplo concreto. Tomemos como ejemplo la función '' | ||
+ | < | ||
+ | porEncimaDe :: Int -> Int -> [Int] -> [Int] | ||
+ | </ | ||
+ | La podemos currificar de la siguiente manera: | ||
+ | < | ||
+ | porEncimaDe :: Int -> ( Int -> [Int] -> [Int] ) | ||
+ | </ | ||
+ | Esto significa que la función tiene un argumento que es un entero y devuelve un resultado que es una función con el resto de argumentos, con signatura de tipos '' | ||
+ | < | ||
+ | porEncimaDe :: Int -> ( Int -> ( [Int] -> [Int] ) ) | ||
+ | </ | ||
+ | Ahora, la función buscará otro entero, y luego se ocupará de resolver la función que queda, que busca una lista para devolver una lista. Esta forma de funcionar nos permite usar la función '' | ||
+ | < | ||
+ | porEncimaDeRestando5 :: Int -> [Int] -> [Int] | ||
+ | porEncimaDeRestando5 | ||
+ | </ | ||
+ | O también | ||
+ | < | ||
+ | porEncimaDe20Restando5 :: [Int] -> [Int] | ||
+ | porEncimaDe20Restando5 = porEncimaDe 5 20 | ||
+ | </ | ||
+ | |||
+ | En haskell se asume que todas las funciones están currificadas, | ||
+ | |||
+ | Si quieren saber más sobre el tema, pueden leer sobre [[http:// | ||
+ | |||
+ | ===== Ejercicios para pensar ===== | ||
+ | |||
+ | ==== Conexión entre dos ciudades ==== | ||
+ | |||
+ | Dada la siguiente base de conocimiento: | ||
+ | < | ||
+ | conectadas(rosario, | ||
+ | conectadas(rosario, | ||
+ | conectadas(mendoza, | ||
+ | conectadas(rioNegro, | ||
+ | conectadas(santaCruz, | ||
+ | </ | ||
+ | |||
+ | Tratemos de averiguar si se puede ir de una ciudad a otra, es decir, si hay alguna conexión entre dos ciudades. Definimos el predicado '' | ||
+ | |||
+ | < | ||
+ | hay_conexion(X, | ||
+ | hay_conexion(X, | ||
+ | </ | ||
+ | |||
+ | Ahora traten de ampliar esta función para que el intérprete nos diga el itinerario por el que hay que ir de una ciudad a otra. Otra posible ampliación es que el intérprete nos diga con qué medios de transporte podemos desplazarnos de una ciudad a otra; para que se pueda contestar a eso habrá que modificar los hechos. | ||
+ | |||
+ | ==== Recomendar amigos en una red social ==== | ||
+ | |||
+ | Este problema es complejo y por lo tanto se puede abordar de muy distintas formas. Acá les proponemos el principio de una, para que ustedes vayan trabajando en el problema. El próximo día vamos a ver algunas soluciones posibles para este problema. | ||
+ | |||
+ | En primer lugar, pensemos qué información necesita nuestra función para trabajar. Primero de todo, necesita saber quién es la persona a quien vamos a recomendar nuevos amigos, y cuáles son los amigos que ya tiene. Luego necesita tener acceso a la base de datos en la que se guarda la información sobre todas las personas que están en la red social en cuestión. Por lo tanto, necesita dos argumentos: la persona y el listado de todas las personas. | ||
+ | |||
+ | Cómo se representa la información sobre una persona en este problema? Hay muchas posibilidades. Yo les propongo la siguiente: una persona se representa como una tresupla donde el primer elemento es un entero que representa el número único de identificación de esa persona en la red social, el segundo elemento es un String que representa el nombre de la persona, y el tercer elemento es una lista de enteros que representa los identificadores de los amigos de esta persona en la red social. El resultado de la función podría ser un listado de nuevos amigos, representados por tuplas '' | ||
+ | < | ||
+ | recomendarNuevosAmigos :: (Int, | ||
+ | </ | ||
+ | |||
+ | Ahora que sabemos con qué estructuras de datos vamos a trabajar, pensemos en lo que tenemos que hacer con ellas: | ||
+ | | ||
+ | - descartar de esa lista todos los que ya son amigos de '' | ||
+ | - descartar de esa lista a '' | ||
+ | - quedarnos solamente con los amigos que son amigos por lo menos de dos amigos de '' | ||
+ | |||
+ | La función principal '' | ||
+ | < | ||
+ | recomendarNuevosAmigos (id, | ||
+ | </ | ||
+ | |||
+ | ===== Ejercicios ===== | ||
+ | |||
+ | * Redefinan las funciones propuestas en los ejercicios de la clase anterior, ahora usando las generalizaciones '' | ||
+ | |||
+ | * Definan una función que calcule el porcentaje de estudiantes que aprobó o promocionó la materia, definiendo una función '' | ||
+ | |||
+ | * Definan una función que determine si Clara es mayor que Elena, dada la siguiente base de conocimiento: | ||
+ | < | ||
+ | mayor(clara, | ||
+ | mayor(esteban, | ||
+ | mayor(paula, | ||
+ | mayor(marcos, | ||
+ | </ |
introalg/taller09_8.1242349398.txt.gz · Última modificación: 2018/08/10 03:03 (editor externo)