Programación funcional para desarrolladores de Android - Parte 1

Últimamente, he pasado mucho tiempo aprendiendo Elixir, un increíble lenguaje de programación funcional que es amigable para los principiantes.

Esto me hizo pensar: ¿por qué no utilizar algunos de los conceptos y técnicas del mundo funcional en la programación de Android?

Cuando la mayoría de las personas escuchan el término Programación funcional, piensan en publicaciones de Hacker News que hablan sobre mónadas, funciones de orden superior y tipos de datos abstractos. Parece ser un universo místico muy alejado de las fatigas del programador diario, reservado solo para los piratas informáticos más poderosos que descienden del reino de Númenor.

Bueno, al diablo con eso ! Estoy aquí para decirte que tú también puedes aprenderlo. Tú también puedes usarlo. Usted también puede crear hermosas aplicaciones con él. Aplicaciones que tienen bases de código elegantes y legibles y tienen menos errores.

Bienvenido a Programación funcional (FP) para desarrolladores de Android. En esta serie, aprenderemos los fundamentos de FP y cómo podemos usarlos en el buen Java antiguo y el nuevo y sorprendente Kotlin. La idea es mantener los conceptos basados ​​en la practicidad y evitar tanta jerga académica como sea posible.

FP es un tema enorme. Aprenderemos solo los conceptos y técnicas que son útiles para escribir código de Android. Es posible que visitemos algunos conceptos que no podemos usar directamente en aras de la exhaustividad, pero intentaré mantener el material lo más relevante posible.

Listo? Vamonos.

¿Qué es la programación funcional y por qué debería utilizarla?

Buena pregunta. El término programación funcional es un paraguas para una variedad de conceptos de programación a los que el apodo no hace justicia. En esencia, es un estilo de programación que trata los programas como una evaluación de funciones matemáticas y evita estados mutables y efectos secundarios (hablaremos de estos muy pronto).

En esencia, FP enfatiza:

  • Código declarativo : los programadores deben preocuparse por el qué y dejar que el compilador y el tiempo de ejecución se preocupen por el cómo .
  • Explicidad : el código debe ser lo más obvio posible. En particular, efectos secundariosdeben estar aislados para evitar sorpresas. El flujo de datos y el manejo de errores se definen explícitamente y se evitan construcciones como declaraciones GOTO y Excepciones ya que pueden poner su aplicación en estados inesperados.
  • Simultaneidad : la mayor parte del código funcional es concurrente de forma predeterminada debido a un concepto conocido como pureza funcional . El acuerdo general parece ser que este rasgo en particular está haciendo que la programación funcional aumente en popularidad, ya que los núcleos de la CPU no se vuelven más rápidos cada año como solían hacerlo (ver la ley de Moore) y tenemos que hacer que nuestros programas sean más concurrentes para aprovecharlos. de arquitecturas de múltiples núcleos.
  • Funciones de orden superior : las funciones son miembros de primera clase, como todas las demás primitivas del lenguaje. Puede pasar funciones como lo haría con una cadena o un int.
  • Inmutabilidad : las variables no se deben modificar una vez que se inicializan. Una vez que se crea una cosa, es esa cosa para siempre. Si quieres que cambie, creas algo nuevo. Este es otro aspecto de la claridad y la evitación de efectos secundarios. Si sabe que una cosa no puede cambiar, tendrá mucha más confianza en su estado cuando la use.

¿Código declarativo, explícito y concurrente que es más fácil de razonar y está diseñado para evitar sorpresas? Espero haber despertado su interés.

En esta primera parte de la serie, comencemos con algunos de los conceptos más fundamentales en FP: Pureza , Efectos secundarios y Orden .

Funciones puras

Una función es pura si su salida depende solo de su entrada y no tiene efectos secundarios (hablaremos sobre el bit de efectos secundarios justo después de esto). Veamos un ejemplo, ¿de acuerdo?

Considere esta función simple que suma dos números. Lee un número de un archivo y el otro número se pasa como parámetro.

Java

int add(int x) { int y = readNumFromFile(); return x + y;}

Kotlin

fun add(x: Int): Int { val y: Int = readNumFromFile() return x + y}

La salida de esta función no depende únicamente de su entrada. Dependiendo de lo que devuelva readNumFromFile () , puede tener diferentes salidas para el mismo valor de x . Se dice que esta función es impura .

Convirtámoslo en una función pura.

Java

int add(int x, int y) { return x + y;}

Kotlin

fun add(x: Int, y: Int): Int { return x + y}

Ahora, la salida de la función solo depende de sus entradas. Para x e y dados , la función siempre devolverá la misma salida. Ahora se dice que esta función es pura . Las funciones matemáticas también operan de la misma manera. La salida de una función matemática solo depende de sus entradas: esta es la razón por la que la programación funcional está mucho más cerca de las matemáticas que el estilo de programación habitual al que estamos acostumbrados.

PS Una entrada vacía sigue siendo una entrada. Si una función no toma entradas y devuelve la misma constante cada vez, sigue siendo pura.

PPS La propiedad de devolver siempre la misma salida para una entrada determinada también se conoce como transparencia referencial y es posible que la vea utilizada cuando se habla de funciones puras.

Efectos secundarios

Exploremos este concepto con el mismo ejemplo de función de suma. Modificaremos la función de suma para escribir también el resultado en un archivo.

Java

int add(int x, int y) { int result = x + y; writeResultToFile(result); return result;}

Kotlin

fun add(x: Int, y: Int): Int { val result = x + y writeResultToFile(result) return result}

Esta función ahora escribe el resultado del cálculo en un archivo. es decir, ahora está modificando el estado del mundo exterior. Ahora se dice que esta función tiene un efecto secundario y ya no es una función pura.

Cualquier código que modifique el estado del mundo exterior (cambia una variable, escribe en un archivo, escribe en una base de datos, elimina algo, etc.) se dice que tiene un efecto secundario.

Las funciones que tienen efectos secundarios se evitan en FP porque ya no son puras y dependen del contexto histórico . El contexto del código no es autónomo. Esto hace que sea mucho más difícil razonar sobre ellos.

Digamos que está escribiendo un fragmento de código que depende de un caché. Ahora, la salida de su código depende de si alguien escribió en la caché, qué se escribió en ella, cuándo se escribió, si los datos son válidos, etc. No puede comprender lo que está haciendo su programa a menos que comprenda todos los estados posibles de la caché de la que depende. Si amplía esto para incluir todas las otras cosas de las que depende su aplicación: red, base de datos, archivos, entrada del usuario, etc., se vuelve muy difícil saber qué está sucediendo exactamente y encajarlo todo en su cabeza a la vez.

¿Significa esto que no usamos redes, bases de datos y cachés? Por supuesto no. Al final de la ejecución, desea que la aplicación haga algo. En el caso de las aplicaciones de Android, generalmente significa actualizar la interfaz de usuario para que el usuario pueda obtener algo útil de nuestra aplicación.

La mayor idea de FP es no renunciar por completo a los efectos secundarios, sino contenerlos y aislarlos. En lugar de tener nuestra aplicación llena de funciones que tienen efectos secundarios, empujamos los efectos secundarios a los límites de nuestro sistema para que tengan el menor impacto posible, haciendo que nuestra aplicación sea más fácil de razonar. Hablaremos de esto en detalle cuando exploremos una arquitectura funcional para nuestras aplicaciones más adelante en la serie.

Ordenar

Si tenemos un montón de funciones puras que no tienen efectos secundarios, entonces el orden en el que se ejecutan se vuelve irrelevante.

Digamos que tenemos una función que llama a 3 funciones puras internamente:

Java

void doThings() { doThing1(); doThing2(); doThing3();}

Kotlin

fun doThings() { doThing1() doThing2() doThing3()}

Sabemos con certeza que estas funciones no dependen unas de otras (ya que la salida de una no es la entrada de otra) y también sabemos que no cambiarán nada en el sistema (ya que son puras). Esto hace que el orden en el que se ejecuten sea completamente intercambiable.

El orden de ejecución se puede reorganizar y optimizar para funciones puras independientes. Tenga en cuenta que si la entrada de doThing2 () fuera el resultado de doThing1 (), entonces estos tendrían que ejecutarse en orden, pero doThing3 () aún podría reordenarse para ejecutarse antes de doThing1 ().

Sin embargo, ¿qué nos aporta esta propiedad de pedido? Simultaneidad, ¡ eso es! ¡Podemos ejecutar estas funciones en 3 núcleos de CPU separados sin preocuparnos por estropear nada!

En muchos casos, los compiladores en lenguajes funcionales puros avanzados como Haskell pueden saber mediante el análisis formal de su código si es concurrente o no, y pueden evitar que se dispare en el pie con interbloqueos, condiciones de carrera y similares. En teoría, estos compiladores también pueden paralelizar automáticamente su código (esto en realidad no existe en ningún compilador que conozca en este momento, pero la investigación está en curso).

Incluso si su compilador no está mirando estas cosas, como programador, es genial poder saber si su código es concurrente con solo mirar las firmas de funciones y evitar desagradables errores de subprocesos que intentan paralelizar el código imperativo que podría estar lleno de efectos secundarios.

Resumen

Espero que esta primera parte te haya intrigado sobre FP. Las funciones puras y libres de efectos secundarios hacen que sea mucho más fácil razonar sobre el código y son el primer paso para lograr la concurrencia.

Sin embargo, antes de llegar a la concurrencia, tenemos que aprender sobre la inmutabilidad . Haremos precisamente eso en la Parte 2 de esta serie y veremos cómo las funciones puras y la inmutabilidad pueden ayudarnos a escribir código concurrente simple y fácil de entender sin recurrir a bloqueos y mutex.

Leer siguiente

Programación funcional para desarrolladores de Android - Parte 2

Si no ha leído la parte 1, léala aquí: medium.com

Si le gustó esto, haga clic en? abajo. Me doy cuenta de cada uno y estoy agradecido por cada uno de ellos.

Para más reflexiones sobre la programación, sígueme para recibir una notificación cuando escriba nuevas publicaciones.