Funciones de orden superior

logo

Funciónes de orden superior en javascript

La base de la programación funcional.

Las funciónes de orden superior, son funciones capaces de:

  • Tomar una función como parametro
  • Retornar una función.

Una función se dice que es funcional si toma una funcion como argumento.
Las funciónes de orden superior forman la columna vertebral de la programación funcional.

Creando closures.

El dominio de los closures es clave para dominar JavaScript y la programación funcional en este lenguaje. Los closures estan por todas partes en JavaScript, y no se podria hacer mucho de una manera funciónal sin ellas. Veamos un ejemplo:

1
2
3
4
5
6
7
8
var showName = function() {
var name = "FP JavaScript"
return function() {
console.log(name)
}
}()
showName() //==>> FP JavaScript
console.log(name) //==>> Reference Error

Hemos creado y ejecutado inmediatamente una función anonima.

1
2
3
function() {
}()

y asignado el valor de retorno a showName. showName es asignado a la función.

1
2
3
function() {
console.log(name)
}

Tambien hemos creado un nombre de variable en el mismo scope que la función anterior. La variable es visible dentro de la función, por eso la función puede imprimir su valor.

Fuera de la función que se ejecuto inmediatamente, la variable name es undefined. Obtendremos un error al tratar de acceder a ella. Sin embargo, podemos imprimir el nombre, llamando a showName. A pesar de que la función que ha creado la variable nombre a sido ejecutada y retornada, name sigue siendo accesible desde dentro de showName.

En efecto, hemos creado un closure con una variable name para la función showName. Podemos hacer lo que queramos con showName. Pasarlo a otra función, o lo que sea que deseemos. podemos contar con nuestro closure fielmente.

Para los que vienen de lenguajes como C o c++, un closure es básicamente un puntero, que la función lleva consigo. Este puntero apunta a una tabla de todas las variables que fueron creadas en ese scope.

Un closure se crea cada vez que una función retorna otra función definida dentro de ella.

la creación de closures debe ser algo natural en un programador JavaScript. De no ser asi, deberia seguir practicando closures, ya que vamos a hacer una gran cantidad de closures en este articulo y en posteriores. Usted debe ser capaz de reconocerlos facilmente, puesto que no siempre los mencionaremos expicitamente.

Fabrica de funciónes

Considere el siguiente codigo:

1
2
3
4
5
6
7
8
9
10
function add(x) {
return function(y) {
return x + y
}
}
var add2 = add(2)
var add3 = add(3)
add2(4) //==>> 6
add3(6) //==>> 9

La función add toma una simple variable x y retorna una función, que a su vez toma una variable y y retorna la suma de x e y.

add es una fabrica de funciónes. Dado un valor, se crea una función que acepta un argumento, y añade el valor de su argumento a un valor previamente almacenado en su clousure.

¿Entonces, porque no hemos escrito simplemente add(x,y)? Esto seguramente seria mas simple. Escribir funciones como “fabrica de funciones” nos permite componer fácilmente nuevas funciones. Veremos mas sobre este tema más adelante, cuando hablemos sobre la composición funcional.

Vamos a ver algunas de las funciones comunes que se escriben usando el patron function factory.

get

1
2
3
4
5
function get(prop) {
return function(obj) {
return obj[prop]
}
}

Lo interesante de esta función, es que no “obtiene” por asi decirlo. Esto simplemente retorna otra función.

1
var getName = get(“name”)

getName se define como una función retornada por get. Pero antes de retornar nada, get crea un closure con la variable prop (de property) para getName. Y la propiedad se establece en nombre.

1
2
3
4
var book = {
name: “Functional JavaScript”
}
getName(book) //==>> Functional JavaScript

Podemos usar get con arrays tambien.

1
get(1)([1, 2, 3]) //==>> 2

Esta es la forma en la que consiguen trabajar los lenguajes funcionales. Esto es muy util cuando componemos esto con otras funciónes. Tambien es util si necesitamos acceder a la misma propiedad en un conjunto de objetos. Veremos todo esto en acción mas adelante.

map

Vamos a reescribir la función de arrays JavaScript map para que trabaje de manera mas funcional.

1
2
3
4
5
6
7
function map(fn) {
return function(arr) {
return Array.prototype.map.call(arr, fn)
}
}
map(function(x) {return x * x}) ([1, 2, 3]) //==>> [ 1, 4, 9 ]

Note que el orden de los argumentos han sido cambiados. La función viene primero y luego el array. Tambien hemos utilizado Array.prototype.map.call en lugar de simplemente haber llamado a arr.map. Esto es asi para que podamos utilizar nuestra función map con arrays al igual que con objetos, argumentos y el DOMNodeList.

Digamos que queremos conseguir todos los emails desde una lista como esta en el array:

1
2
3
4
5
var people = [ {name:”John”, email:”john@example.com”},
{name:”Bill”, email:”bill@example.com”} ]
map(get(‘email’)) (people)
//===>> [ ‘john@example.com’, ‘bill@example.com’ ]

Usted es capaz de ver la ventaja de escribir get de esta forma. Podemos pasar esto como argumento a otra función. Hemos compuesto get con map. A continuacion vamos a ver algunas ventajas de escribir map de esta manera.

pluck

El patron map(get()) que vimos anteriormente, es tan comun que tenemos una función para esto llada pluck.

1
2
3
4
5
6
function pluck(prop) {
return map(get(prop))
}
pluck(‘email’)(people)
//===>> [ ‘john@example.com’, ‘bill@example.com’ ]

pluck es una composición entre las funciones map y get(prop). la composición trabaja de derecha a izquierda.

map retorna una función que requiere un array. Pero tambien ha establecido que su función requerida sera get(prop) en su closure.

forEach

1
2
3
4
5
function forEach(fn) {
return function(arr) {
Array.prototype.forEach.call(arr, fn)
}
}

Esto funciona igual que nuestra función map anterior. excepto que simplemente itera sobre un array, y no retorna ningun valor. Esto tambien funcióna con un DOMNodeList.

un patron común que nos encontramos cuando desarrollamos del lado del cliente es la iteración a trabes de un NodeList retornada por document.querySelectorAll. Para mejorar esto podemos escribir una función de orden superior generica para que itere a trabes de un NodeList dado un selector.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html><body>
<div>Ocultame</div>
<div>Ocultame tambien.</div>
<script>
function forEach(fn) {
return function(arr) {
Array.prototype.forEach.call(arr, fn)
}
}
var displayNone = forEach(function(elem) {
elem.style.display = “none”
})
displayNone(document.querySelectorAll(“div”))
</script>
</body></html>

take

take toma un numero n y retorna una función, a la que debe pasar un array, para obtener los primeros n elementos del array.

1
2
3
4
5
function take(n) {
return function(arr) {
return arr.slice(0, n)
}
}

flip

flip toma una función de 2 o mas argumentos y retorna una función con el primero y segundo argumentos cambiados.

1
2
3
4
5
6
function flip(fn) {
return function(first, second) {
var rest = [].slice.call(arguments, 2)
return fn.apply(null, [second, first].concat(rest))
}
}

flip es especialmente util, cuando se necesita aplicar parcialmente una función dada y ordenar sus argumentos en el orden deseado.

memoize

La memorización es el proceso en el que una función cachea sus resultados. y retorna esos resultados desde la cache, si la función fue llamada previamente con los mismos argumentos.

1
2
3
4
5
6
function memoize(fn) {
var cache = {}
return function(arg) {
return (arg in cache) ? cache[arg] : (cache[arg] = fn(arg))
}
}

Esto es memoize para una función que toma un unico argumento. Esto retorna una función con un objeto cache en su closure. Cada vez que la función es llamada, checkea la cache para el argumento. Si lo encuentra, entonces retorna el valor correspondiente. En cambio si no lo encuentra, establece la cache con el argumento y el resultado, y retorna el resultado.

memoize es realmente muy poderoso para optimizar calculos largos, o operaciones recursivas caras.

La serie de fibonacci, es un ejemplo muy usado para demostrar la función memoize. Debido a que el computo involucrado crece exponencialmente cuanto mayor sea el numero a calcular en la serie.

Cada numero en la serie de fibonacci es la suma de los 2 numeros previos ej: eg. 1,1,2,3,5,8,13,21 .. etc. Y podemos escribir esto como:

1
2
3
function fib(n) {
return n < 2 ? 1 : fib(n — 1) + fib(n — 2)
}

Y si corremos algunas pruebas, obtenemos…:

1
2
3
4
5
6
7
8
var fibmemo = memoize(fib)
var start = new Date()
fibmemo(20)
console.log(new Date() — start) //==>> 11 ms en mi maquina
start = new Date()
fibmemo(20)
console.log(new Date() — start) //==>> 0 ms

Observe que la ejecución de fibomemo(20), la segunda vez tomo solo o ms, ya que solo devuelve el valor almacenado en la cache.

Tenemos que modificar memioze para manejar las funciones con multiples argumentos. Por fortuna esto es bastante sencillo.

1
2
3
4
5
6
7
8
function memoize(fn) {
var cache = {}
return function() {
var args = Array.prototype.slice.call(arguments)
return (args in cache) ? cache[args] :
(cache[args] = fn.apply(null, args))
}
}

Primero convertimos los argumentos pasados a un array en args. Entonces args es un array y es usado en el contexto de una clave de objeto (hash), esto sera coaccionado dentro de un string. Segundo, ahora podemos aplicar la matriz a la función .

once

oncee crea una función que solo puede ejecutarse una unica vez. Y las invocaciones posteriores, siempre retornaran el primer resultado.

1
2
3
4
5
6
7
function once(fn) {
var cache = {}
return function() {
return (“once” in cache) ? cache[“once”] :
(cache[“once”] = fn.apply(null, arguments))
}
}

Espero sinceramente, que el articulo haya sido de utilidad, para aquellas personas que no conocian el poder de este tipo de funciónes. Saludos :)

Comentarios