¿ PHP Funcional ? Bueno, PHP no es un lenguaje funcional, pero algunas técnicas funcionales se pueden utilizar para mejorar nuestro código: mejor legibilidad, más fácil de mantener => código más barato.


Durante muchos años, PHP se escribió proceduralmente, todo en un solo archivo con funciones en todas partes. Después de la versión 5. *, las aplicaciones se han escrito utilizando el paradigma orientado a objetos (Object Oriented paradigm , OO). Pero pocas veces pensamos en PHP y paradigma funcional. Por supuesto, PHP no es un lenguaje funcional, pero deberíamos poder usar lo mejor de cada paradigma. No se trata de OO contra la programación funcional (FP), o definir cuál es mejor.


Debido a que PHP es un entorno de OO bastante “clásico”, sugerimos un enfoque de “fusión”. Nuestro enfoque va en la dirección de lo que usamos en los lenguajes multiparadigma, como Scala, Kotlin, pero también en Typescript: el que llamamos “Object-Functional”. De ahí PHP Funcional.
Seguimos creando clases y objetos pero, en este caso, utilizando una mentalidad funcional. En lugar de decirle al ordenador cómo resolver los problemas (también conocida como programación imperativa), comenzamos por decirle al ordenador lo que queremos lograr (programación declarativa).

 

Programación funcional y PHP funcional

En pocas palabras, la programación funcional:

  • Evita la mutación del estado
    Una vez que se crea un objeto, nunca cambia. No hay variables como las usamos en PHP.
  • Utiliza un sistema de tipos complejo
    Un tipo es un clasificador para un valor. Proporciona información sobre cuál será ese valor en tiempo de ejecución, pero se indica en tiempo de compilación. Producen, por ejemplo, funciones con más detalle (se puede ver lo que espera la función, cuál será el resultado), y como recompensa por eso, pueden detectar errores en tiempo de compilación, tan pronto como en su IDE mientras codifica . PHP tiene su propio “tiempo de compilación”, generalmente etiquetado como “tiempo de análisis sintáctico”, pero en realidad es similar, si no es un sinónimo, especialmente cuando se utilizan herramientas como OpCache para almacenar en caché el resultado del análisis sintáctico. Vale la pena mencionar que los tipos en las versiones actuales de PHP son algo “más débiles” que otros lenguajes funcionales, o incluso cuando se compara con otros lenguajes OO “clásicos”.
  • Funciones como valores de primera clase
    Las funciones se pueden usar como entradas o salidas de otras funciones que permiten la composición de funciones. No son las funciones que conocemos en PHP. Son funciones matemáticas: la misma entrada produce siempre la misma salida (no importa cuántas veces se la llame). Se llaman funciones puras.

 

Funciones puras o impuras

Funciones puras

  • no hay globales; los valores no se pasan por referencia
  • no tiene efectos secundarios (una propiedad muy interesante !!)
  • no use ningún tipo de bucles (for, foreach, while …)

Funciones impuras

  • Mutan el estado global
  • Pueden modificar sus parámetros de entrada
  • Pueden arrojar excepciones
  • Pueden realizar cualquier operación de E / S: necesitan hablar con recursos externos como bases de datos, red, sistema de archivos …
  • Pueden producir resultados diferentes incluso con los mismos parámetros de entrada.
  • Pueden tener efectos secundarios

 

Be functional my friend – PHP Funcional

Veamos algunos ejemplos prácticos para ver como podemos ser más funcionales:
Evitar variables temporales
Sin variables temporales en nuestro código, evitamos tener un estado local que puede producir resultados no deseados.
En el siguiente ejemplo, mantenemos el estado hasta que se devuelve al final.

function isPositive(int $number) {
    if ($number > 0) {
        $status = true;
    } else {
        $status = false;
    }
    return $status;
}

Podemos eliminar las variables temporales y devolver directamente el estado:

unction isPositive(int $number) {
    if ($number > 0) {
        return true;
    }
    return false;
}

El último refactor: eliminar el if. Finalmente, tenemos una versión más funcional:

function isPositive(int $number) {
    return $number > 0;
}

Funciones pequeñas

  • Deben seguir el principio de responsabilidad única (SRP): hacer solo una cosa
  • Eso significa que son más fáciles de testear
  • Podemos componer otras funciones
function sum(int $number1, int $number2) {
    return $number1 + $number2;
}

echo sum(sum(3, 4), sum(5, 5));
// 17

Fijémonos en que podríamos cambiar la suma (3, 4) por 7 y el resultado sería el mismo:

echo sum(7, sum(5, 5));
// 17

Esto se llama transparencia referencial: una función puede ser reemplazada por su resultado y el resultado final no cambia en absoluto.

 

Eliminar el estado

Esto puede ser bastante difícil cuando estamos acostumbrados a escribir de manera imperativa. En este ejemplo, calcula el producto de todos los valores en un array. Para hacerlo, usamos algunos agregadores y un bucle para iterar sobre él.

unction productImperative(array $data) {
    if (empty($data)) {
        return 0;
    }
    $total = 1;
    $i = 0;
    while ($i < count($data)) {
        $total *= $data[$i];
        $i++;
    }
    return $total;
}

En casos como este, cuando hay alguna acción repetitiva, el estado puede eliminarse con recursión:

function product(array $data) {
    if (empty($data)) {
        return 0;
    }
    if (count($data) == 1) {
        return $data[0];
    }
    return array_pop($data) * product($data);
}

Por supuesto, este ejemplo es bastante simple y quizá sería mejor resolverlo a través de reduce:

echo array_reduce([5, 3, 2], function($total, $item) {
   return $total * $item;
}, 1);
// 30

Empujar las funciones impuras hacia los límites

Nuestras aplicaciones PHP a menudo necesitan alguna entrada de recursos externos y producen algunos resultados.
Eso significa que puede ser difícil tener funciones puras. En estos casos, debemos dividir nuestro código impuro de el puro.
Por ejemplo, usando el código de php.net sobre la lectura de un archivo:

the HTML source of a URL.
$lines = file('https://www.google.com/');
// Loop through our array, show HTML source as HTML source
foreach ($lines as $line_num => $line) {
    echo htmlspecialchars($line) . "
\n";
}

Lee el contenido de un sitio externo (entrada externa) y muestra los resultados en la consola (salida externa). ¿Podemos hacer algunos cambios funcionales aquí? Claro, recorta el código en diferentes funciones.

function getFileContent($file) {
    return file($file);
}

function formatLines($lines) {
    return array_map(function($line) {
        return htmlspecialchars($line) . "\n";
    }, $lines);
}
print_r(formatLines(getFileContent('https://www.google.com/')));

El resultado final es el mism pero ahora tenemos:

  • Una función impura (getFileContent) que es fácilmente mockeable en nuestros tests
  • Una función pura (formatLines) que siempre devuelve el mismo resultado y que es fácilmente testeable.

No uses bucles para iterar sobre arrays
Los bucles son imperativos, usan algunas variables temporales y no son muy legibles.

Mapa
Necesitamos iterar sobre una matriz para modificar cada uno de sus contenidos.
Ejemplo: recibimos una lista de usuarios de la base de datos y necesitamos devolver el modelo de dominio que nuestra aplicación espera.
De una manera imperativa, haríamos esto: uso de un foreach diciéndole al ordernador qué hacer:

function getUsers() {
    return [
        ["firstname" => "john", "surname1" => "doe", "location" => "Barcelona", "numpets" => 2],
        ["firstname" => "david", "surname1" => "ee", "location" => "Girona", "numpets" => 10],
        ["firstname" => "jane", "surname1" => "qwerty", "location" => "Barcelona", "numpets" => 1],
    ];
}

function findUsers()
{
    $users = getUsers();
    if (empty($users)) {
        return false;
    }
    $usersDTO = [];
    foreach ($users as $user) {
        $usersDTO[] = new UserDTO($user);
    }
    return $usersDTO;
}

PHP Funcional sería:

function findUsersMap()
{
    return array_map("convertUser", getUsers());
}

function convertUser(array $user) {
    return new UserDTO($user);
}

Usamos array_map en lugar de foreach y creamos una función pura que su único propósito es convertir el formato del usuario.
Esta versión es mucho más legible que la anterior.

Filtrar
Iterando sobre un array pero devolviendo solo los resultados que pasan algunas condiciones.
Ahora que tenemos la lista de usuarios, queremos mostrar solo a los que viven en Barcelona.
Una vez más, necesitamos revisar el array y verificar una por una su ciudad.

function getUsersFromBcn(array $users) {
    $bcnUsers = [];
    foreach ($users as $user) {
        if ($user->getCity() == "Barcelona") {
            $bcnUsers[] = $user;
        }
    }
    return $bcnUsers;
}

O podríamos preguntar solo por los usuarios de Barcelona:

function getUsersFromBcn(array $users) {
    return array_filter($users, "isFromBcn");
}

function isFromBcn(UserDTO $user) {
    return $user->getCity() == "Barcelona";
}

Este código es mucho más simple y fácil de probar sin variables temporales ni foreach + if loops.

 

Reducir / fold
Reducir un array para devolver un valor.

Ahora, queremos calcular el promedio de mascotas en la ciudad de Barcelona.
Vamos a recorrer los usuarios de Barcelona calculando el número de mascotas:

function getAvgPets(array $users) {
    $numPets = 0;
    foreach ($users as $user) {
        $numPets += $user->getPets();
    }
    return $numPets / count($users);
}

O podríamos sumar el número de mascotas:

function getAvgPets(array $users) {
    return array_reduce($users, "getTotalPets", 0) / count($users);
}

function getTotalPets($total, UserDTO $user) {
    return $total + $user->getPets();
}

Usando pipelines para PHP Funcional
Agrupemos todas las condiciones juntas:

echo getAvgPetsReduce(getUsersFromBcn(findUsers()));

Si tenemos múltiples condiciones, podríamos terminar con una oración muy larga que puede ser muy difícil de leer. Desafortunadamente, para resolver este problema, no lo podemos hacer en PHP nativo.
Afortunadamente, hay algunas buenas bibliotecas para usar tuberías.

 

Laravel Collectton

El framework Laravel tiene una gran biblioteca para trabajar con arrays, a los que llaman colecciones.
Se puede usar fuera de laravel si lo instalamos a través de composer

composer require tightenco/collect

Usando esta biblioteca, los objetos son inmutables y el código es legible, más declarativo.
Nuestro ejemplo del promedio de mascotas en Barcelona sería:

$collection = collect(getUsers());
echo $collection->map("convertUser")
                ->filter('isFromBcn')
                ->map("getListPets")
                ->average();

El orden de las acciones es muy claro

  1. Convierte los usuarios
  2. Filtra solo los de Barcelona
  3. Obtener la lista de mascotas por usuario
  4. Obtener el promedio (método de la biblioteca que hace la reducción + cálculo del promedio)

Conclusión: PHP funcional

Sabemos que PHP no es 100% funcional. Y cambiar la forma en que programamos de manera funcional no es una tarea fácil. Pero podemos comenzar a aplicar algunos de estos enfoques sencillos que simplifican nuestro código, lo hacen mucho más legible, más testeable y con menos efectos secundarios. Podemos empezar a pensar de una manera más declarativa y paso a paso seremos más funcionales.
Continuaremos hablando sobre PHP funcional en próximos artículos, haciendo que nuestro PHP sea cada vez más funcional.


Si estás interesado en recibir más artículos sobre PHP funcional, no te olvides de suscribirse a nuestro boletín mensual aquí.

Si este artículo sobre PHP funcional te gustó, te puede interesar:

 

Tendencias en aplicaciónes móviles

Patrón MVP en iOS

Debugging con Charles Proxy en Android emulator

Por qué Kotlin?   

Integración Continua en iOS usando Fastlane y Jenkins  

Meetups de arquitectura de software  

MVPP en Android