====== Lambdas ======
Módulo perteneciente al curso [[informatica:programacion:cursos:programacion_avanzada_javascript|Programación avanzada con JavaScript y ECMAScript]].
===== Introducción =====
Desde siempre, en JavaScript las funciones han tenido una importancia primordial. Por un lado son el mecanismo para tener visibilidades y ámbitos y además son "ciudadanos de primer orden". Eso significa que en JavaScript se puede, y de hecho se hace constantemente, pasar funciones como parámetros y devolver funciones como resultado.
En JavaScript podemos declarar una función anónima y colocarla en cualquier punto donde se espere un valor:
var value = function() { return 42};
value(); // 42
La variable ''value'' contiene un valor que es... una función. Es por ello que podemos usar el operador ''()'' sobre la variable y que su tipo, si lo consultamos con ''typeof'', es //function//.
Observa que una cosa es la variable ''value'' y otra es el valor que hemos asignado a dicha variable: una función anónima. Dado que la función es anónima no hay manera de llamarla si no es a través de alguna variable que la contenga (como ''value'').
Por supuesto también se pueden declarar en JavaScript funciones tradicionales, es decir con nombre, sin necesidad alguna de almacenarlas en una variable:
function value() { return 42;}
Al igual que antes, si ahora usas ''typeof'' para consultar el tipo de ''value'' obtendrás ''function'', y al igual que antes puedes usar ''value();'' para invocar a la función. Parece pues, que no hay ninguna diferencia, pero **son dos cosas distintas**: en el primer caso tienes una variable ''value'' cuyo valor es una función y en el segundo, ''value'' es directamente la función.
No hay apenas diferencias prácticas entre asignar una función a una variable o declarar la función con nombre, excepto una fundamental que tiene que ver con el //hoisting//. Y es que las declaraciones de funciones en JavaScript tienen //hoisting//, al igual que las declaraciones de variables. Pero recuerda que en el caso de variables, el //hoisting// se aplica solo a la declaración de la variable, no de su asignación.
Así el siguiente código **es correcto, ya que podemos usar una función antes de declararla**:
var v = foo(); // 42
function foo() { return 42};
En cambio el mismo código, pero usando una función anónima asignada a una variable ''foo'' da error:
var v = foo(); // Error foo is not a function
var foo = function() { return 42};
Observa que en este segundo caso, el error en la primera línea no es que ''foo'' no exista: ''foo'' ya existe (gracias al //hoisting//) pero su valor es ''undefined''. Si usamos ''let'' el error puede ser distinto:
var v = foo(); // TypeError: foo is not defined
let foo = function() { return 42};
Decimos que entre usar ''var'' y ''let'' el error "puede ser distinto" porque p. ej. ambos casos en FireFox dan el mismo error, mientras que en NodeJs dan errores distintos. En mi opinión es NodeJs el que se comporta más acorde a la especificación de ECMAScript. Son esas pequeñas diferencias que sin ser muy significativas, existen y está bien tenerlas presentes.
Las siguientes dos imágenes muestran las diferencias entre ambos entornos:
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:11-funcion-var-firefox.png |}}
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:11-funcion-var-nodejs.png |}}
==== Sintaxis "verbose" ====
No deja de ser curioso que siendo JavaScript un lenguaje muy orientado a usar funciones anónimas, la sintaxis para declararlas sea un tanto verbose, o por decirlo con palabras castellanas: tediosa. Observa la sintaxis mínima necesaria para crear una función anónima que devuelva un valor:
function() {return 10}
Visto así no parece tan tediosa esta sintaxis, el problema es que se usa constantemente y al final la legibilidad se ve afectada. Para solucionar esta situación en ECMAScript 2015 se introduce el **operador de "flecha"** (//arrow// o //fat arrow//) que ofrece una sintaxis alternativa, mucho más clara y sencilla para declarar funciones. Vamos a verlo....
===== El operador flecha (arrow, fat arrow) =====
Este operador introduce **una sintaxis nueva, más sencilla para declarar funciones anónimas**. Observa las diferencias:
let foo = function() {return 42}; // Sintaxis tradicional
let foo = () => 42; // Usando el operador flecha
Indudablemente la segunda sintaxis es mucho más clara, sencilla y concisa. Veamos un poco cómo usarla en más escenarios.
==== Funciones que solo devuelven una expresión ====
Este es el escenario óptimo para el operador flecha. La sintaxis es muy sencilla cuando la función básicamente consiste en un ''return''. Es el escenario que se ha presentado como ejemplo:
let foo = () => 42; // Sin argumentos. Se debe usar paréntesis vacíos como argumentos. No se usa return.
let foo2 = x => x + 42; // Con un argumento. No se usa return.
let foo3 = (x) => x + 42; // Idéntico al caso anterior. Los paréntesis en el argumento son opcionales. No se usa return.
let sum = (x,y) => x + y; // Con más de un argumento. Los paréntesis en los argumentos son obligatorios. No se usa return.
En este caso la función ''foo'' no acepta parámetros y devuelve siempre el valor ''42''. Las funciones ''foo2'' y ''foo3'' son idénticas: aceptan un parámetro y devuelven el valor del parámetro sumándole ''42'', mientras que la función ''sum'' suma los dos parámetros que se le pasan y devuelve el valor.
Veamos cómo realmente el uso del **operador flecha mejora la legibilidad** en ciertos escenarios.
''Array.prototype'' tiene una función llamada ''filter'' que filtra los elementos de un array que cumplan un cierto predicado. Este predicado es una función que se pasa como parámetro a ''filter'', que es invocada por cada elemento y que debe devolver true si el elemento cumple el filtro. Así podríamos obtener los elementos pares de un array simplemente con:
[1,2,3,4].filter(function(el) { return el % 2 == 0; });
Este código devuelve el array ''[2,4]'' (es decir los elementos que cumplen el filtro). Pues bien, el mismo código escrito usando el operador flecha es el siguiente:
[1,2,3,4].filter(el => el % 2==0)
Se puede ver que el segundo código es mucho más conciso y fácil de leer que el primero. En este escenario el uso del operador flecha supone una indudable mejora respecto a la sintaxis tradicional.
Cuando la función devuelve un objeto usando //object notation// entonces debemos envolver el valor de retorno entre paréntesis. Esto es para no confundir al //parser//:
let foo = () => {a:30, b: 40}; // Error de sintaxis
let foo = () => ({a:30, b: 40}); // OK
La razón de esta confusión del //parser// tiene que ver con el siguiente escenario del operador flecha: funciones que tienen más de una sentencia.
**parser**: Analizador sintáctico de código
==== Funciones con varias sentencias ====
El operador flecha puede usarse para definir funciones con varias sentencias. En este caso sus ventajas no son tan evidentes (aunque siguen existiendo). Si tenemos un código como el siguiente:
let foo = function() {
console.log('foo!');
return 42;
};
Podemos convertirlo usando el operador flecha para que quede de la siguiente manera:
let foo = () => {
console.log('foo!');
return 42;
};
Se puede observar en este caso como **después del operador flecha se abren llaves**. Estas llaves es lo que indica que la función tiene más de una sentencia. Observa también como ahora **es necesario el uso de ''return''**.
En este caso, en lo que respecta a la legibilidad no hay muchas diferencias entre usar el operador flecha o una declaración de función anónima tradicional (posteriormente veremos otra ventaja de usar el operador flecha, incluso en este caso).
El hecho de que "abrir llave" después del operador flecha indique que empieza una serie de sentencias para la función es lo que hace que debamos usar paréntesis si usamos el operador flecha para definir funciones que tan solo devuelven un objeto en //object notation// (JSON).
Esto es porque el siguiente código:
let foo = () => {a:20, b:30};
El motor de JavaScript lo interpreta como:
let foo = function() {
a:20, b:30
};
Lo que se puede observar es un error de sintaxis clarísimo. El usar paréntesis evita esta ambigüedad, de forma que el código:
let foo = () => ({a:20, b:30});
Es interpretado por el motor de JavaScript como:
let foo = function() {
return {a:20, b:30};
};
Que es precisamente, lo que esperamos.
===== DEMO: Sintaxis y uso del operador flecha =====
Tradicionalmente, para crear funciones anónimas en JavaScript hacíamos algo así:
var a = function(i) {
return i % 2 == 0;
}
El **operador "flecha"** es una sintaxis alternativa. Podríamos definir la misma función que antes, pero con la nueva sintaxis:
var a = (i) => i % 2 == 0;
// Sería válido también esto por haber solo un parámetro en la función:
// var a = i => i % 2 == 0;
}
Veamos ahora un uso real de este nuevo operador y cómo nos ayuda a simplificar la sintaxis. Sintaxis clásica:
// Multiplicaremos los elementos del array por 2
var resultado = [1, 2, 3].map(function(e) {
return e * 2;
})
''map()'' es un método de ''Array.prototype'' disponible desde ECMAScript 2015
Usando el operador flecha:
var resultado = [1, 2, 3].map(e => e * 2);
Vemos que en funciones que esperan como parámetro una función, la sintaxis flecha nos ahorra escritura y además se hace más fácil de leer.
===== La "pérdida" de this (I) =====
El valor que tiene ''this'' en JavaScript es un tema complejo. Incluso desarrolladores con cierta experiencia en el lenguaje no terminan de comprender todas sus casuísticas. Hay una, bastante curiosa, que todo desarrollador se encuentra tarde o temprano y que se conoce como la "pérdida" de ''this''.
Esta casuística se da cuando se observa que el valor de ''this'' dentro de una función anónima no es el mismo que el valor de ''this'' dentro de una función que contiene a la función anónima. Veamos un ejemplo:
var obj = {
values: [4, 8, 15, 16, 23, 42],
filter: function(v) { return v % 2 == 0 || v == 23},
getValues: function() { return this.values},
getFilteredValues: function(cb) {
return this.values.filter(function(v) {
return this.filter(v);
});
}
}
El siguiente código define un objeto ''obj'' que contiene un array de valores y tres funciones miembro. La función ''getValues'' devuelve el array de valores, mientras que la función ''getFilteredValues'' usa la función ''filter'' para filtrar el array de valores usando la función ''filter'' declarada en el propio objeto. Bien, **este código NO funciona correctamente** pero, ¿dónde crees que hay el error y de qué error se trata?
Tómate un tiempo para pensarlo, si quieres. La solución está en el siguiente apartado.
===== La pérdida de this (II) =====
El error que nos da el código anterior es el siguiente (como siempre, el mensaje exacto puede variar según el entorno de JavaScript que uses):
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:11-error-perdida-this.png |}}
El error lo que nos dice es que ''this.filter'' no es una función. Pero la función ''filter'' sí que está definida en el objeto...
La razón principal del error es que **el valor de ''this'' dentro de la función anónima que se pasa como parámetro dentro de la llamada a ''Array.prototype.filter'' no es el mismo que el valor de ''this'' dentro de la función ''getFilteredValues''**.
El siguiente diagrama muestra el valor de ''this'' dentro de cada bloque de código:
{{ :informatica:programacion:cursos:programacion_avanzada_javascript:11-error-perdida-this-02.png |}}
Lo importante es tener presente que dentro de la función anónima que se pasa como parámetro a la llamada a ''Array.prototype.filter'' el valor de ''this'' no es el propio objeto ''obj'', sino que es el contexto global. De ahí que recibamos el error de que ''this.filter()'' no es una función.
==== Solucionando la pérdida de this ====
Existen dos mecanismos para solucionar la pérdida de ''this''.
El primero consiste en **declarar una variable y asignarla a ''this'' (en algún punto donde sabemos que ''this'' tiene el valor que nos interesa) y usar esta variable posteriormente**.
Por convención a esta variable se la suele llamar ''that'' o ''self'', pero por supuesto se puede usar cualquier nombre válido. A continuación se muestra el código aplicando esta técnica:
var obj = {
values: [4, 8, 15, 16, 23, 42],
filter: function(v) { return v % 2 == 0 || v == 23},
getValues: function() { return this.values},
getFilteredValues: function() {
var self = this;
return this.values.filter(function(v) {
return self.filter(v);
});
}
}
La segunda técnica que se suele utilizar para evitar la pérdida de ''this'' es usar ''bind''. El método ''bind'' se aplica sobre una función y retorna otra función con el valor de ''this'' establecido al valor que se pase a ''bind'':
var obj = {
values: [4, 8, 15, 16, 23, 42],
filter: function(v) { return v % 2 == 0 || v == 23},
getValues: function() { return this.values},
getFilteredValues: function() {
return this.values.filter(function(v) {
return this.filter(v);
}.bind(this));
}
}
Observa cómo se aplica ''bind'' a la función anónima y el resultado de aplicar ''bind'' es otra función que es la que realmente se pasa como parámetro a la llamada a ''filter''. **Este método no requiere usar una variable adicional como en el caso anterior**.
===== DEMO: call, bind y apply =====
Ejemplos para modificar el valor de ''this'' dentro de una función.
Si tenemos la siguiente función:
function foo(i) {
this.una_propiedad = i;
}
Y la ejecutamos:
foo(10);
console.log("una_propiedad", una_propiedad); // -> 10
''new'' es una de las posibilidades para cambiar el valor de ''this'' dentro de una función:
var foo = new foo(10);
console.log("una_propiedad", foo.una_propiedad); // -> 'undefined'
Usando ''call'' nos permite invocar a una función con un valor de ''this'' en concreto:
var a = {p: 10};
Lo probamos:
foo.call(a, 10); // -> Object {p: 10, una_propiedad: 10}
Con ''apply'', que funciona como ''call'', pero ''apply'' espera los argumentos como un array:
foo.apply(a, [10]);
Otro método es usar ''bind'' que devuelve otra función con el valor que indiquemos:
var f = foo.bind(a);
''f'' será una función cuyo valor siempre va a ser ''a''.
f(200);
a; // -> Object {p: 10, una_propiedad: 200}
===== this y funciones definidas con el operador flecha =====
Otra de las ventajas de usar el operador flecha para definir funciones, **es que las funciones definidas con éste capturan el valor de ''this'' existente en el momento de definir la función**. Es decir, se evita la "pérdida" de this que tenemos en el caso de funciones anónimas.
Observa cómo queda el código anterior usando una función definida con el operador flecha:
var obj = {
values: [4, 8, 15, 16, 23, 42],
filter: function(v) { return v % 2 == 0 || v == 23},
getValues: function() { return this.values},
getFilteredValues: function() {
return this.values.filter(v => this.filter(v) );
}
}
El código funciona correctamente, porque ahora la función definida por el operador flecha toma para ''this'' el mismo valor que tiene ''this'' en el punto en el cual se define la función (en este caso el interior de la función ''getFilteredValues''), que es el propio objeto.
Solo por esto ya tenemos una ventaja clara de usar el operador flecha.
==== Operador flecha y funciones de primer nivel ====
Supón el siguiente código, que usa el operador flecha para definir una función de un objeto:
var obj={
nick: 'eiximenis',
getUrl: () => 'http://twitter.com/' + this.nick
}
La pregunta es ¿cuál es el resultado de la función ''getUrl()''?
Si has respondido ''%%http://twitter.com/eiximenis%%'', has fallado. No te preocupes, es un error que comete casi todo el mundo cuando ve código como este la primera vez.
La función ''getUrl'' devuelve ''%%http://twitter.com/undefined%%''. Evidentemente, **el código usando la declaración de función tradicional funciona correctamente** y devuelve el valor esperado (''%%http://twitter.com/eiximenis%%''):
var obj={
nick: 'eiximenis',
getUrl: function() { return 'http://twitter.com/' + this.nick}
}
¿Por qué la función declarada con el operador flecha no funciona y la función declarada a la manera tradicional sí? ¿No se supone que básicamente el operador flecha es un sintaxis más cómoda para declarar funciones?
Sí, es cierto que el operador flecha es una sintaxis más cómoda para declarar funciones, pero recuerda que, tal y como hemos visto antes, **las funciones declaradas con el operador flecha capturan el valor de ''this'' existente en el punto de su declaración**. Y esta es la clave: el valor de dentro de ''obj'' pero fuera de cualquier función es el contexto global. Observa sino el siguiente código:
var test = {
nick: 'eiximenis',
url: 'http://twitter.com/' + this.nick
}
El valor de ''test.url'' no es ''%%http://twitter.com/eiximenis%%'', sino que es ''%%http://twitter.com/undefined%%'', ya que aunque ''url'' está definido dentro del objeto ''test'', su código está fuera de cualquier función (es una propiedad, no una función), y por esto el valor de ''this'' es el contexto global. ''this'' solo pasa a tener el valor del propio objeto dentro de una función (como en el código que usa ''getUrl'').
Ahora bien, cuando usas el operador flecha para definir la función estás capturando el valor de ''this'' que existe en este punto y que es el contexto global. Es por eso que la versión que usa el operador flecha no funciona correctamente. **A todos los efectos es como si la versión que usa el operador flecha hiciese lo siguiente**:
var obj={
nick: 'eiximenis',
getNick: function() { return 'http://twitter.com/' + this.nick}.bind(this)
}
En esta versión usamos ''bind'' para forzar la captura del valor de ''this'', que es justamente lo que hace por nosotros el operador flecha y se puede comprobar que tampoco funciona correctamente.
En resumen: **no uses el operador flecha para definir las propias funciones de un objeto. Úsalo para definir aquellas funciones anónimas que pasas como parámetros a otras funciones**.
==== El operador flecha y bind ====
Se ha visto como el operador flecha captura el valor de ''this'' existente y lo propaga como contexto de la función que define. Pero es lógico preguntarse qué sucede si aplicamos ''bind'' a una función definida con el operador flecha:
var demo = (() => this.nick).bind({nick: 'eiximenis'});
var nick = demo();
En este código definimos una función con el operador flecha y usamos ''bind'' para obtener otra función con su contexto (su valor de ''this'') establecido al objeto ''{nick: 'eiximenis'}''. Es esta otra función devuelta por ''bind'' la que guardamos en la variable demo. Es de esperar que el valor de ''demo()'' sea pues '''eiximenis'''.
Pero la realidad es que el valor de ''demo()'' es ''undefined''. La razón es que **el operador flecha ya ha capturado el contexto para la función anónima que define y el contexto, una vez capturado ya no puede cambiarse**. Es por esto que la llamada a ''bind'' no modifica el contexto, por lo que el valor de ''this'' se mantiene al que había capturado el operador flecha (el contexto global).
Eso mismo ocurre si usas ''bind'' sobre una función devuelta por otra llamada a ''bind'' (el contexto no se modifica por segunda vez):
var demo = function() { return this.nick}.bind({nick:'eiximenis'});
var demo2 = demo.bind({nick:'campusmvp'});
var nick = demo2(); // eiximenis
===== DEMO: La propagación de this en el operador flecha =====
Consideremos el siguiente objeto:
var stuff = {
mult: 2,
data: [1, 2, 3, 4],
log: function() {
console.log("mult -> ", this.mult, "data -> ", this.data)
}
};
stuff; // -> Object {mult: 2, data: Array[4], log: stuff.log() }
stuff.log(); // -> mult-> 2 data -> Array [1, 2, 3, 4 ]
Ahora crearemos una función que devolverá un array con la multiplicación de los elementos del array ''data'':
var stuff = {
mult: 2,
data: [1, 2, 3, 4],
log: function() {
console.log("mult -> ", this.mult, "data -> ", this.data)
},
multData: function() {
return this.data.map(function(e) {
return e * this.mult;
});
}
};
Ejecutamos:
stuff.mult = 10;
var md = stuff.multData();
md; // -> Array[NaN, NaN, NaN, NaN]
Esto pasa porque en el ''return e * this.mult;'', ''this'' ha perdido su valor. Podemos comprobarlo añadiendo un ''console.log'':
var stuff = {
mult: 2,
data: [1, 2, 3, 4],
log: function() {
console.log("mult -> ", this.mult, "data -> ", this.data)
},
multData: function() {
return this.data.map(function(e) {
console.log(this);
return e * this.mult;
});
}
};
// Ejecutamos:
stuff.multData(); // -> Window, Window, Window, Window. Es el contexto global.
Esto es lo que se conoce como **pérdida de ''this'' en JavaScript**.
Una de las soluciones es buscar dónde tiene ''this'' un valor correcto y lo guardamos:
var stuff = {
mult: 2,
data: [1, 2, 3, 4],
log: function() {
console.log("mult -> ", this.mult, "data -> ", this.data)
},
multData: function() {
var self = this;
return this.data.map(function(e) {
return e * self.mult;
});
}
};
// Ejecutamos
stuff.multData(); // -> Array [2, 4, 6, 8]
Otra forma es utilizando la función ''bind'':
var stuff = {
mult: 2,
data: [1, 2, 3, 4],
log: function() {
console.log("mult -> ", this.mult, "data -> ", this.data)
},
multData: function() {
return this.data.map(function(e) {
return e * this.mult;
}.bind(this));
}
};
// Ejecutamos
stuff.multData(); // -> Array [2, 4, 6, 8]
Las funciones definidas con el operador flecha también pueden ser usadas para esto:
var stuff = {
mult: 2,
data: [1, 2, 3, 4],
log: function() {
console.log("mult -> ", this.mult, "data -> ", this.data)
},
multData: function() {
return this.data.map(e => e * this.mult);
}
};
// Ejecutamos
stuff.multData(); // -> Array [2, 4, 6, 8]
===== Funciones lambda =====
Hasta ahora se ha usado la expresión "funciones declaradas con el operador flecha". En cierta literatura puedes encontrar el término //arrow function// (que se traduciría por "función flecha") pero lo más normal es que **mucha gente se refiera a las funciones declaradas con el operador flecha como funciones lambda (o incluso expresiones lambda)**. De hecho, ésta será la nomenclatura que se usará de ahora en adelante en el curso. Esta nomenclatura proviene del [[https://es.wikipedia.org/wiki/C%C3%A1lculo_lambda|cálculo lambda]].
Esta sección es puramente informativa y se incluye por completitud. El cálculo lambda es un sistema formal que permite definir el concepto de "función computable". Aunque no tiene nada que ver explícitamente con JavaScript, esta sección incluye un poco de historia y cultura básica de la informática. De todos modos, si el tema te parece tedioso, puedes saltártela sin problemas.
==== Computabilidad ====
El cálculo lambda es un sistema formal que se usa para verificar la computabilidad, es decir determinar qué problemas son solucionables mediante un algoritmo con una máquina de Turing. La máquina de Turing es un dispositivo muy simple, que básicamente manipula una serie de símbolos escritos sobre una tira de cinta de acuerdo a un sistema de reglas. Es más un dispositivo imaginario que real (aunque se puede construir), ideado por el matemático [[https://es.wikipedia.org/wiki/Alan_Turing|Alan Turing]].
Lo importante sobre la máquina de Turing en nuestro contexto no es cómo funciona, sino el hecho de que **computacionalmente una máquina de Turing es equivalente al ordenador más potente que podamos construir hoy en día**. Incluso los posiblemente futuros ordenadores cuánticos son equivalentes computacionalmente a una máquina de Turing. Ten presente que "computacionalmente equivalente" no implica que puedan resolver el mismo problema en el mismo tiempo, implica que ambos (la máquina de Turing y el ordenador) pueden resolver el problema. Es decir, finalizarán la ejecución del algoritmo con un resultado final.
Puede parecer que cualquier posible problema va a poder ser resuelto, con un tiempo arbitrario (y un consumo arbitrario de ciertos recursos, tales como cinta en una máquina de Turing o memoria en un ordenador actual). Pero la realidad es que esto no es así y **por raro que pueda parecer existen problemas que no pueden ser resueltos con ningún algoritmo, incluso contando con tiempo y recursos ilimitados**. Detectar qué problemas son esos es uno de los objetivos de la teoría de la computabilidad.
==== El problema de la parada ====
El problema de la parada es, probablemente, el problema no computable más famoso (aunque no el único).
Su definición es muy simple: dado un programa (a ejecutar por una máquina de Turing, o un ordenador) y su entrada inicial, se trata de decidir si dicho programa (con la entrada suministrada) terminará su ejecución en algún momento o por el contrario estará ejecutándose eternamente (p. ej. en un bucle infinito). El propio Turing demostró que este problema es no computable, es decir, es imposible crear algoritmo alguno que pueda resolverlo.
==== Cálculo lambda ====
El cálculo lambda es una herramienta que permite decidir la computabilidad o no de una función determinada. No entraremos aquí en el cómo (echa un vistazo a la [[https://es.wikipedia.org/wiki/Tesis_de_Church-Turing|tesis de Church-Turing]] para más información). Pero simplemente quiero que veas, muy por encima, la notación del cálculo lambda y sus consideraciones principales.
Imagina dos funciones. La primera, llamada "Identidad", devuelve el mismo argumento que se le pasa y la segunda, llamada "Suma", devuelve la suma de los dos argumentos que recibe. Matemáticamente las podríamos definir:
* Identidad: ''I(x) = x''
* Suma: ''S(x, y) = x + y''
El cálculo lambda realiza algunas consideraciones sobre esas funciones. La primera es que **las funciones no necesitan ser explícitamente nombradas**. Así la función "Suma" puede ser reescrita como una función anónima: ''x,y -> x + y'' y la función "Identidad" puede ser reescrita como ''x -> x''.
Otra consideración es que el nombre de los parámetros no tiene importancia alguna. Así las funciones ''x -> x'' e ''y -> y'' son la misma.
Una tercera consideración interesante es que **toda función que tenga dos parámetros y devuelva un valor puede ser reescrita como una función que tenga un solo parámetro, pero que devuelva otra función, que acepta un parámetro y devuelve un valor**. Este proceso se llama **currificación** y es una de las bases de la programación funcional (por cierto, ¡es posible implementar currificación en JavaScript!).
En el cálculo lambda, las funciones se definen mediante "expresiones lambda" que indican qué debe hacerse con su argumento. Se les llama "expresiones lambda" porque se usa la letra griega lambda para definir la lista de argumentos. Así la "expresión lambda" ''λx. x + 2'' representa a la función anónima ''x -> x + 2''.
Observa como la notación del operador flecha es casi calcada a la notación de las funciones anónimas (no así a la notación de las propias "expresiones lambda") aunque son notaciones equivalentes.
No vamos a contar más cosas sobre el cálculo lambda (ahora ya entraríamos en un territorio mucho más complejo). Tan solo comentar que como sistema formal tiene exactamente la misma potencia computacional que una máquina de Turing, es decir, cualquier algoritmo calculable por una máquina de Turing (lo que significa decir cualquier algoritmo posible) es representable en cálculo lambda.
La importancia del cálculo lambda en el desarrollo de la informática como ciencia es capital (de hecho se podría considerar el cálculo lambda como un (el primer) lenguaje de programación). El cálculo lambda ejerce una gran influencia, especialmente en el desarrollo de lenguajes funcionales tales como LISP o Haskell.
Simplemente quería que supieses de dónde proviene el hecho de que muchos desarrolladores llamemos a las funciones declaradas con el operador flecha, funciones o expresiones lambda.