Reflexiones funcionales con Julia Language — Parte 2

Jan 05 2023
Explorando la programación funcional con Julia (Parte 2) En mi artículo anterior Reflexiones funcionales con el lenguaje Julia — Parte 1, introduje algunos conceptos básicos para comenzar a pensar funcionalmente con Julia. Ahora continuamos esa exploración con algunos conceptos más.

Explorando la programación funcional con Julia (Parte 2)

En mi artículo anterior Reflexiones funcionales con Julia Language — Parte 1 , introduje algunos conceptos básicos para comenzar a pensar funcionalmente con Julia. Ahora continuamos esa exploración con algunos conceptos más.

Para la segunda parte de nuestras reflexiones funcionales con Julia, profundizaremos en algunos de los conceptos avanzados de programación funcional que se pueden implementar en Julia. Un concepto que se usa a menudo en la programación funcional es la recursividad, que implica definir una función en términos de sí misma.

recursividad

Un Universo recursivo…

Consideremos primero la siguiente función, escrita como imperativa, sin uso de recursividad:

La función anterior funciona como se esperaba. Sin embargo, están sucediendo algunas cosas que quiero señalar: El ciclo for muta la variable `i`. Variables a, be incluso la propia matriz se muta en cada iteración. Estos se consideran efectos secundarios, que hemos cubierto en la primera parte de este artículo. En la programación funcional, queremos lograr cosas devolviendo valores de las funciones, en lugar de modificar el estado.

Entonces, ¿cómo podemos escribir la función anterior de una manera más funcional? Podemos usar la recursividad:

Otro concepto del que no se habla mucho, pero que es fundamental para comprender y programar funciones correctamente: ramificaciones if-then-else y evaluación de argumentos de función.

Un giro funcional

Considere la siguiente función factorial, escrita recursivamente:

Si llamamos a esta función, regresa como se esperaba. También podemos verificar el valor devuelto ya que es la primera vez que lo usamos:

Ahora aquí está el giro. Quiero usar mi propia función if-then-else, así que decido escribir:

A primera vista, la función if anterior parece estar bien, aunque un poco tonta e ingenua. Parece que es solo un envoltorio alrededor de la rama if normal.

Voy a modificar la función factorial anterior para usar mi función if. Se verá más conciso y mejor de esta manera (creo):

Cuando trato de ejecutar con el mismo argumento que el anterior, obtengo lo siguiente en mi máquina:

¿Que esta pasando aqui?

En Julia, y en la mayoría de los lenguajes de programación, cuando llamamos a una función, los argumentos de la función se evalúan antes de que se evalúe el cuerpo de la función. Cada argumento de la función se evalúa exactamente una vez.

Compare esto con una rama if-then-else. Con estas bifurcaciones, solo se evalúan las bifurcaciones verdaderas o falsas, según la lógica, pero no ambas.

La función factorial_bad anterior nunca regresa porque cuando llamó a myif_bad , se evaluaron todos los argumentos de la función, incluidas las expresiones verdadera y falsa, lo que resultó en llamadas recursivas factorial_bad que nunca terminan . Para que esto funcione correctamente, solo se puede evaluar uno u otro, pero no ambos.

Entonces, ¿cómo podemos hacer que la función myif_bad funcione con nuestro factorial_bad ?

Considere las siguientes revisiones a myif y las funciones factoriales que se muestran arriba :

Espera un minuto... ¿qué está pasando aquí? ¿Cómo funciona este truco?

Reemplacé los argumentos para expresiones verdaderas y falsas con funciones de cero argumentos en el nuevo myif_not_too_bad . Las llamadas de función de argumento cero solo evalúan el cuerpo cuando realmente se llaman. Dado que no hay argumentos para esta función, solo se evalúa el argumento de expresión de condición, y se evalúa muy rápidamente.

En la nueva función factorial_not_too_bad , he creado expresiones lambda que no toman argumentos. Por lo tanto, no se ejecutarán hasta que la rama correcta realmente llame a estas expresiones lambda. La bifurcación si-entonces correcta llama a la expresión lambda y todo se ejecuta según lo previsto:

Si no desea que algo se ejecute de inmediato, ya sea porque no desea evaluar todos los argumentos de la función antes que el cuerpo o porque puede haber costos computacionales elevados asociados con la ejecución de esto todo el tiempo, póngalo en un cero -argumente la expresión lambda y guárdela: cree el cierre correcto, luego llámelo cuando necesite los resultados, no antes.

Medita en esto. Hay varias construcciones de programación funcional que se basan en los conceptos anteriores: evaluaciones perezosas, cierres, flujos inmutables y muchas otras ideas funcionales.