Al leer la Introducción práctica de Mary Rose Cook a la programación funcional , ella da como ejemplo un antipatrón
def format_bands(bands):
for band in bands:
band['country'] = 'Canada'
band['name'] = band['name'].replace('.', '')
band['name'] = band['name'].title()
ya que
- la función hace más de una cosa
- el nombre no es descriptivo
- tiene efectos secundarios
Como solución propuesta, sugiere canalizar funciones anónimas
pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name')])
Sin embargo, esto me parece tener la desventaja de ser aún menos comprobable; al menos format_bands podría tener una prueba unitaria para verificar si hace lo que debe hacer, pero ¿cómo probar la tubería? ¿O es la idea de que las funciones anónimas son tan autoexplicativas que no necesitan ser probadas?
Mi aplicación en el mundo real para esto es tratar de hacer que mi pandas
código sea más funcional. A menudo tendré algún tipo de tubería dentro de una función "munging"
def munge_data(df)
df['name'] = df['name'].str.lower()
df = df.drop_duplicates()
return df
O reescribiendo en el estilo de canalización:
def munge_data(df)
munged = (df.assign(lambda x: x['name'].str.lower()
.drop_duplicates())
return munged
¿Alguna sugerencia para las mejores prácticas en este tipo de situación?
fuente
Respuestas:
Creo que te perdiste probablemente la parte más importante del ejemplo corregido del libro. El cambio más fundamental en el código es desde el método que opera en todos los valores de una lista hasta el que opera en un elemento.
Ya existen funciones como
iter
(en este caso con el nombrepipeline_foreach
) que realizan una operación dada en todos los elementos de una lista. No había necesidad de duplicar eso con unfor
bucle. También el uso de una operación de lista bien conocida aclara su intención. Conmap
ustedes están transformando los valores. Coniter
usted está realizando un efecto secundario con cada elemento. Con elfor
bucle eres ... bueno, realmente no lo sabes hasta que lo miras.El código corregido de ejemplo todavía no es muy funcional, porque (por lo que puedo decir) muta los valores en la lista sin devolverlos, lo que evita la creación de tuberías o funciones adicionales. El método funcionalmente preferido
map
crearía una nueva lista de bandas con las actualizacionescountry
yname
. Luego, podría canalizar esa salida a la siguiente función o componermap
con otra función que tomó una lista de bandas. Coniter
, es como un callejón sin salida.Creo que el código de resultado final tiene pequeñas funciones que son demasiado triviales para molestarse en probar aquí. Después de todo, no debería necesitar escribir pruebas unitarias contra
replace
otitle
. Ahora, tal vez desee componerlos juntos en su propia función y prueba unitaria de que la combinación deseada se logra en un solo elemento. Yo mismo, probablemente hubiera cambiadoformat_bands
aformat_band
singular, soltado el bucle for y llamadopipeline_each(bands, format_band)
. Luego, puede probar format_band para asegurarse de que no olvidó algo.De todos modos, a su código. Su segundo ejemplo de código parece más pipeline-y. Pero eso por sí solo no proporciona los beneficios de la programación funcional. En la práctica, la programación funcional significa garantizar la compatibilidad de las funciones con otras funciones definiendo su compatibilidad solo en términos de sus entradas y salidas. Si hay efectos secundarios ocultos dentro de la función, a pesar de su entrada / salida alineada con otra función, no puede saber si son compatibles hasta el tiempo de ejecución. Sin embargo, si dos funciones son libres de efectos secundarios y coinciden de salida a entrada, puede canalizarlas o componerlas sin preocuparse por los resultados inesperados.
fuente