Primeros pasos con la programación funcional

Funciones puras

Las funciones puras son independientes y no tienen efectos secundarios. Dado el mismo conjunto de entradas, una función pura siempre devolverá el mismo valor de salida.

La siguiente función es pura:

function pure(data) {
    return data.total + 3;
}

Sin embargo, esta función no es pura ya que modifica una variable externa:

function impure(data) {
    data.total += 3;
    return data.total;
}

Ejemplo:

data = {
    total: 6
};

pure(data);   // outputs: 9
impure(data); // outputs: 9 (but now data.total has changed)
impure(data); // outputs: 12

Funciones de orden superior

Las funciones de orden superior toman otras funciones como argumentos y/o las devuelven como resultados. Forman los componentes básicos de la programación funcional. La mayoría de los lenguajes funcionales tienen algún tipo de función de filtro, por ejemplo. Esta es una función de orden superior, que toma una lista y un predicado (función que devuelve verdadero o falso) como argumentos.

Las funciones que no hacen nada de esto se denominan a menudo “funciones de primer orden”.

function validate(number,predicate) {
    if (predicate) {    // Is Predicate defined
        return predicate(number);
    }
    return false;
}

Aquí “predicado” es una función que probará alguna condición que involucre sus argumentos y devolverá verdadero o falso.

Un ejemplo de llamada para lo anterior es:

validate(someNumber, function(arg) {
    return arg % 10 == 0;
    }
);

Un requisito común es agregar números dentro de un rango. Mediante el uso de funciones de orden superior podemos ampliar esta capacidad básica, aplicando una función de transformación en cada número antes de incluirlo en la suma.

Desea sumar todos los números enteros dentro de un rango dado (usando Scala)

def sumOfInts(a: Int, b: Int): Int = {
  if(a > b) 0
  else a + sumOfInts(a+1, b)
}

Quieres sumar cuadrados de todos los enteros dentro de un rango dado

def square(a: Int): Int = a * a

def sumOfSquares(a: Int, b: Int): Int = {
  if(a > b) 0
  else square(a) + sumOfSquares(a + 1, b)
}

Observe que estas cosas tienen 1 cosa en común, que desea aplicar una función en cada argumento y luego agregarlos.

Vamos a crear una función de orden superior para hacer ambas cosas:

def sumHOF(f: Int => Int, a: Int, b: Int): Int = {
  if(a > b) 0
  else f(a) + sumHOF(f, a + 1, b)
}

Puedes llamarlo así:

def identity(a: Int): Int = a

def square(a: Int): Int = a * a

Tenga en cuenta que sumOfInts y sumOfSquare se pueden definir como:

def sumOfInts(a: Int, b: Int): Int = sumHOF(identity, a, b)

def sumOfSquares(a: Int, b: Int): Int = sumHOF(square, a, b)

Como puede ver en este ejemplo simple, las funciones de orden superior brindan soluciones más generalizadas y reducen la duplicación de código.

He utilizado Scala By Example - de Martin Odersky como referencia.

curry

Currying es el proceso de transformar una función que toma múltiples argumentos en una secuencia de funciones que cada una tiene un solo parámetro. El curry está relacionado, pero no es lo mismo, con la aplicación parcial.

Consideremos la siguiente función en JavaScript:

var add = (x, y) => x + y

Podemos usar la definición de curry para reescribir la función de suma:

var add = x => y => x + y

Esta nueva versión toma un solo parámetro, x, y devuelve una función que toma un solo parámetro, y, que finalmente devolverá el resultado de sumar x e y.

var add5 = add(5)
var fifteen = add5(10) // fifteen = 15

Otro ejemplo es cuando tenemos las siguientes funciones que ponen corchetes alrededor de las cadenas:

var generalBracket = (prefix, str, suffix) => prefix + str + suffix

Ahora, cada vez que usamos generalBracket tenemos que pasar los corchetes:

var bracketedJim = generalBracket("{", "Jim", "}") // "{Jim}"
var doubleBracketedJim = generalBracket("{{", "Jim", "}}") // "{{Jim}}"

Además, si pasamos las cadenas que no son corchetes, nuestra función aún arrojará un resultado incorrecto. Arreglemos eso:

var generalBracket = (prefix, suffix) => str => prefix + str + suffix
var bracket = generalBracket("{", "}")
var doubleBracket = generalBracket("{{", "}}")

Tenga en cuenta que tanto bracket como doubleBracket ahora son funciones que esperan su parámetro final:

var bracketedJim = bracket("Jim") // "{Jim}"
var doubleBracketedJim = doubleBracket("Jim") // "{{Jim}}"

Inmutabilidad

En los lenguajes tradicionales orientados a objetos, x = x + 1 es una expresión simple y legal. Pero en Programación Funcional, es ilegal.

Las variables no existen en la Programación Funcional. Los valores almacenados todavía se llaman variables solo debido a la historia. De hecho, son constantes. Una vez que x toma un valor, es ese valor de por vida.

Entonces, si una variable es una constante, ¿cómo podemos cambiar su valor?

La programación funcional se ocupa de los cambios en los valores de un registro haciendo una copia del registro con los valores modificados.

Por ejemplo, en lugar de hacer:

var numbers = [1, 2, 3];
numbers[0] += 1; // numbers = [2, 2, 3];

Tú haces:

var numbers = [1, 2, 3];
var newNumbers = numbers.map(function(number) {
    if (numbers.indexOf(number) == 0)
        return number + 1
    return number
});
console.log(newNumbers) // prints [2, 2, 3]

Y no hay bucles en la Programación Funcional. Usamos funciones recursivas o de orden superior como map, filter y reduce para evitar bucles.

Vamos a crear un bucle simple en JavaScript:

var acc = 0;
for (var i = 1; i <= 10; ++i)
    acc += i;
console.log(acc); // prints 55

Todavía podemos hacerlo mejor cambiando el tiempo de vida de acc de global a local:

function sumRange(start, end, acc) {
    if (start > end)
        return acc;
    return sumRange(start + 1, end, acc + start)
}
console.log(sumRange(1, 10, 0)); // 55

Sin variables ni bucles significa un código más simple, más seguro y más legible (especialmente al depurar o probar; no necesita preocuparse por el valor de x después de una serie de declaraciones, nunca cambiará).