CC 1002 Introduccin a la Programacin Clase 12

  • Slides: 34
Download presentation
CC 1002: Introducción a la Programación Clase 12 : Abstracción Funcional Nelson Baloian, José

CC 1002: Introducción a la Programación Clase 12 : Abstracción Funcional Nelson Baloian, José A. Pino

Evitar repeticiones • Muchas de las definiciones (datos o funciones) son parecidas. • Ejemplo,

Evitar repeticiones • Muchas de las definiciones (datos o funciones) son parecidas. • Ejemplo, lista de strings y lista de números sólo se diferencian en el nombre • Una función que busca un string en una lista de strings es similar a una que busque un número en una lista de números. • Repeticiones son causa de errores de programación -> evitarlos • Al desarrollar distintas funciones de una misma plantilla, podemos encontrar similitudes. Y tratar de eliminarlas lo más posible. • En el mundo real, las funciones son leídas y modificadas por muchas otras personas • Debemos aprender a editar funciones en forma conveniente. • La eliminación de repeticiones es el paso más importante en el proceso de edición de un programa.

Similitud en funciones

Similitud en funciones

Parametrizar las funciones Abstracción funcional: encontrar similitudes y fundir dos o más funciones en

Parametrizar las funciones Abstracción funcional: encontrar similitudes y fundir dos o más funciones en una sola

Un caso un poco más difícil Aquí la diferencia es cambiar un < por

Un caso un poco más difícil Aquí la diferencia es cambiar un < por un >

¿Operador como parámetro? • En Python (y en muchos lenguajes) no es posible pasar

¿Operador como parámetro? • En Python (y en muchos lenguajes) no es posible pasar un operador como parámetro (no confundir con pasar carácter ‘>’ o ‘<‘ como parámetro) • La estrategia que podemos usar es crear una función que realiza la operación ya que nombres de funciones sí se pueden pasar como parámetros # menor. Que : numero -> booleano # devuelve True si el 1 er argumento menor que 2 do # y False en el caso contrario # Ejemplo : menor. Que (4 , 2) -> False def menor. Que (x, y): return x < y

“Filtrar” elementos de una lista • Para aplicar esta función, debemos proporcionar tres argumentos:

“Filtrar” elementos de una lista • Para aplicar esta función, debemos proporcionar tres argumentos: un operador relacional R que compara dos números, una lista L de números, y un numero N. • La función extrae los elementos i en L para las cuales R(i, N) evalúa True.

Similitudes en definición de datos Ambas definen un tipo de listas. La de la

Similitudes en definición de datos Ambas definen un tipo de listas. La de la izquierda es la definición de un dato que representa una lista de números; la derecha describe una lista de registros de inventario (RI), que representaremos con una estructura de dato compuesto. La definición de la estructura está dada como sigue:

Similitudes en definición de datos: RIs import estructura. crear(“ri”, “nombre precio”)

Similitudes en definición de datos: RIs import estructura. crear(“ri”, “nombre precio”)

Similitudes en definición de datos La función de la izquierda filtra números de una

Similitudes en definición de datos La función de la izquierda filtra números de una lista de números. La de la derecha inf-ri, extrae los registros de inventario de una lista cuyo precio sea menor que un cierto número. Excepto por el nombre de la función, que es arbitraria, ambas definiciones difieren solo en un punto: el operador relacional.

Similitudes en definición de datos • Si abstraemos las dos funciones, obtenemos la función

Similitudes en definición de datos • Si abstraemos las dos funciones, obtenemos la función filtro. Entonces podemos escribir inf-ri en términos de filtro: def inf-ri(una. Lista, n): return filtro(<ri, una. Lista, n) • Debe haber otros usos para filtro : ya argumentamos que abstracción funcional fomenta el re-uso de funciones para distintos propósitos. • Acá podemos ver que filtro no solamente filtra listas de números, sino que cosas arbitrarias. • Esto se cumple siempre y cuando podamos definir una función que compare estas cosas arbitrarias con números. • Necesitamos función que pueda comparar elementos de una lista con elementos que pasamos a filtro como segundo argumento.

Uso inesperado de filtro • Función que detecta si una lista de registros de

Uso inesperado de filtro • Función que detecta si una lista de registros de inventario contiene o no un cierto nombre: # encontrar: lista(RI) str -> bool # determina si una. Lista. RI contiene un registro de s # ejemplo: encontrar(crear. Lista(ri(“auto”, 10), #lista. Vacia), ”auto”) devuelve True def encontrar(una. Lista. RI, s): return not vacia(filtro(igual. RI? , una. Lista. RI, s)) # igual. RI? : RI str -> bool # comparar el nombre de un ri y p #ejemplo: igual. RI? (ri(“auto”, 10), ”auto”) devuelve True def igual. RI? (ri, p): return ri. nombre == p

Formalizar la abstracción a partir de ejemplos • En los ejemplos vistos anteriormente, partimos

Formalizar la abstracción a partir de ejemplos • En los ejemplos vistos anteriormente, partimos de dos definiciones concretas de funciones, las comparamos, marcamos las diferencias, y realizamos la abstracción. Ahora, formularemos una receta para realizar todos estos pasos. Esto se cumple siempre y cuando podamos definir una función que compare estas cosas arbitrarias con números.

Comparación • Para definiciones de funciones casi idénticas, salvo pocas diferencias en los nombres,

Comparación • Para definiciones de funciones casi idénticas, salvo pocas diferencias en los nombres, se comparan las funciones y se marcan las diferencias, encerrándolas en una caja. Si las cajas solo contienen valores, se puede hacer la abstracción.

Abstracción

Abstracción

Abstracción • Note que se reemplazaron los nombres en la cajas con f, y

Abstracción • Note que se reemplazaron los nombres en la cajas con f, y se agregó f como parámetro. Ahora se reemplazan convertir. CF y nombres con un nuevo nombre de función, obteniéndose la función abstracta mapa: def mapa(f, una. Lista ): if vacia( una. Lista ): return lista. Vacia else : return crear. Lista(f(cabeza(una. Lista)), mapa(f, cola(una. Lista))) • Usamos el nombre mapa (transformación) para la función resultante en nuestro ejemplo, dado que es el nombre tradicional en los lenguajes de programación que tiene esta función específica.

Test • Ahora debemos validar que la nueva función es una abstracción correcta de

Test • Ahora debemos validar que la nueva función es una abstracción correcta de las funciones originales. La definición misma de abstracción sugiere que se puede validar definiendo las funciones originales en términos de la función abstracta, probando las nuevas versiones con los ejemplos de test originales. • Esto se puede hacer con ayuda de una función adicional :

Test • Para cada valor (correcto) v, f-desde-abstracta(v) debiera producir el mismo resultado que

Test • Para cada valor (correcto) v, f-desde-abstracta(v) debiera producir el mismo resultado que f-original(v). Para nuestro ejemplo, las definiciones serían: • Para asegurarse que estas dos definiciones son equivalentes a las originales, y de paso mostrar que mapa es una abstracción correcta, se aplican estas dos funciones a los ejemplos especificados en los contratos originales de convertir. CF y nombres.

Formulando contratos generales • Para aumentar la utilidad de una función abstracta, se debe

Formulando contratos generales • Para aumentar la utilidad de una función abstracta, se debe formular un contrato que describa su aplicabilidad en los términos más generales posibles. • Para esto, se requiere realizar una abstracción de los contratos, para lo cual se debe seguir la misma receta usada para abstraer funciones. • Se comparan y contrastan los contratos originales, luego se reemplazan las diferencias con variables. • Veamos un ejemplo con los contratos de las funciones convertir. CF y nombres: • # convertir. CF : lista(num) -> lista(num) • # nombres : lista(RI) -> lista(str)

Formulando contratos generales • Veamos al aplicar mapa: • # mapa : (num ->

Formulando contratos generales • Veamos al aplicar mapa: • # mapa : (num -> num) lista(num) -> lista(num) • # mapa : (RI -> str) lista(RI) -> lista(str) • Estos contratos sugieren un patrón • Fijándonos en el segundo contrato, si se reemplaza RI y str con variables, se obtiene un contrato abstracto, que de hecho es el contrato formulado para la función mapa: • # mapa : (X -> Y) lista(X) -> lista(Y)

Contrato • # mapa : (X -> Y) lista(X) -> lista(Y) • Este contrato

Contrato • # mapa : (X -> Y) lista(X) -> lista(Y) • Este contrato indica que mapa produce una lista de Ys a partir de una lista de Xs y una función de X a Y, independiente de lo que signifique X e Y. • Una vez que tenemos la abstracción de dos o más funciones, debemos verificar si hay otros usos para la función abstracta. • En muchos casos, una función abstracta es útil en una amplia gama de contextos que van más allá de lo que uno inicialmente había anticipado. Esto hace que las funciones sean mucho más fáciles de leer, entender y mantener. • Por ejemplo, ahora podemos usar la función mapa cada vez que necesitemos una función que produzca una lista nueva a partir del procesamiento de los elementos de una lista ya existente.

Otro ejemplo de función abstracta: fold • Sumar/multiplicar todos los valores de una lista

Otro ejemplo de función abstracta: fold • Sumar/multiplicar todos los valores de una lista • Concatenar todas las palabras de una lista Podemos abstraer a una función fold (reducir), que recibe una lista, un valor inicial y una función de dos argumentos, procesa los elementos de la lista y devuelve un valor. Pre-condición: lista debe contener al menos un valor. # Función de dos argumentos def sumar(x, y): return x+y Ahora escribimos sumar. Valores. Lista:

Otro ejemplo de función abstracta: fold # sumar. Valores. Lista: lista -> num #

Otro ejemplo de función abstracta: fold # sumar. Valores. Lista: lista -> num # suma los valores dentro de la lista y retorna numero # ejemplo: si una. Lista es lista(10, lista(20, lista(30, # lista. Vacia))), entonces sumar. Valores(una. Lista) # retorna 60 def sumar. Valores. Lista(una. Lista): return fold(sumar, 0, una. Lista)

Para multiplicar valores en una lista # Función de dos argumentos def multiplicar(x, y):

Para multiplicar valores en una lista # Función de dos argumentos def multiplicar(x, y): return x*y # multip. Valores. Lista: lista -> num # mult los valores dentro de la lista y retorna numero # ejemplo: si una. Lista es lista(5, lista(3, # lista. Vacia))), entonces multip. Valores(una. Lista) # retorna 45 def multip. Valores. Lista(una. Lista): return fold(multiplicar, 1, una. Lista)

Función fold (versión preliminar) def fold(f, init, una. Lista): if cola(una. Lista)== lista. Vacia:

Función fold (versión preliminar) def fold(f, init, una. Lista): if cola(una. Lista)== lista. Vacia: #un solo valor return f(init, cabeza(una. Lista)) else: return fold(f, f(init, cabeza(una. Lista)), cola(una. Lista))

Funciones anónimas • Al usar funciones en llamadas a otras funciones, podemos querer evitar

Funciones anónimas • Al usar funciones en llamadas a otras funciones, podemos querer evitar construir funciones para un solo uso: justamente sólo como parámetros • Usamos la instrucción lambda: lambda id 1, id 2, …: expresion donde expresion es el resultado de la función. Por ejemplo: lambda x, y: x+y Otro: lambda x: x < 5

Funciones anónimas def resume (f, w, z): return f(w, z) resume(lambda x, y: x*y,

Funciones anónimas def resume (f, w, z): return f(w, z) resume(lambda x, y: x*y, 5, 3) obtiene como resultado: 15

Resumiendo • Para encontrar contratos generales se requiere comparar los contratos de los ejemplos

Resumiendo • Para encontrar contratos generales se requiere comparar los contratos de los ejemplos que tengamos para crear abstracciones. • Reemplazando valores distintos en posiciones correspondientes por variables, una a la vez, se logra hacer un contrato mas genérico en forma gradual. • Para validar que la generalización del contrato es correcta, se debe verificar que el contrato describe correctamente las instancias específicas de las funciones originales

Función filtro # filtro: (X -> bool) lista(X) -> lista(X) # devuelve lista con

Función filtro # filtro: (X -> bool) lista(X) -> lista(X) # devuelve lista con los valores que operador da True def filtro(operador, una. Lista): if vacia(una. Lista): return lista. Vacia else: if operador(cabeza(una. Lista)): return lista(cabeza(una. Lista), filtro (operador, cola(una. Lista))) else: return filtro(operador, cola(una. Lista))

Función filtro # tests valores= lista(6, lista(4, lista(8, lista. Vacia))) assert filtro(lambda x: x<5,

Función filtro # tests valores= lista(6, lista(4, lista(8, lista. Vacia))) assert filtro(lambda x: x<5, valores) == lista(4, lista. Vacia) valores=lista(“a”, lista(“b”, lista(“c”, lista(“d”, lista. Vacia)))) assert filtro(lambda x: x >=“b” and x<“d”, valores) == lista(“b”, lista”c”, lista. Vacia))

Función mapa # mapa: (X -> Y) lista(X) -> lista(Y) # devuelve lista con

Función mapa # mapa: (X -> Y) lista(X) -> lista(Y) # devuelve lista con funcion aplicada a todos nodos def mapa (f, una. Lista): if vacia(una. Lista): return lista. Vacia else: return lista(f(cabeza(una. Lista)), mapa(f, cola(una. Lista)))

Función mapa # tests valores= lista(1, lista(2, lista(3,  lista(4, lista. Vacia)))) assert mapa(lambda

Función mapa # tests valores= lista(1, lista(2, lista(3, lista(4, lista. Vacia)))) assert mapa(lambda x: 10*x, valores) == lista(10, lista(20, lista(30, lista(40, lista. Vacia)))) valores=lista(“pedro”, lista(“juan”, lista(“diego”, lista. Vacia))) assert mapa(lambda x: x. upper(), valores == lista(“PEDRO”, lista(“JUAN”, lista(“DIEGO”, lista. Vacia)))

Función fold # from lista import * # fold: (X X->X) X lista(X) ->

Función fold # from lista import * # fold: (X X->X) X lista(X) -> X # procesa la lista con funcíon f y devuelve un valor # el valor init se usa como valor inicial para # procesar primer valor de la lista y como acumulador # de resultados parciales # pre-condicion: lista debe contener al menos un valor def fold(f, init, una. Lista): if cola(una. Lista)== lista. Vacia: #un solo valor return f(init, cabeza(una. Lista))

Función fold (cont. ) else: return fold(f, f(init, cabeza(una. Lista)), cola(una. Lista)) #tests valores=

Función fold (cont. ) else: return fold(f, f(init, cabeza(una. Lista)), cola(una. Lista)) #tests valores= lista(1, lista(2, lista(3, lista(4, lista. Vacia)))) assert fold(lambda x, y: x+y, 0, valores)== 10 valores=lista(“pedro”, lista(“juan”, lista(“diego”, lista. Vacia))) assert fold(lambda x, y: x+y, ””, valores)== “pedrojuandiego”