Explicación de la programación funcional en JavaScript: aplicación parcial y currificación

Introducción
Con la adopción de la biblioteca Redux de JavaScript, la extensión de sintaxis y la cadena de herramientas Reason, y el marco de trabajo Cycle de JavaScript, la programación funcional con JavaScript está adquiriendo cada vez mayor relevancia. Dos ideas importantes con raíces en el pensamiento funcional son la currificación, que transforma una función de múltiples argumentos en una serie de llamadas a funciones, y la aplicación parcial, que fija el valor de algunos de los argumentos de una función sin evaluar completamente la función. En este artículo, exploraremos algunos ejemplos de estas ideas en acción, así como también identificaremos algunos lugares en los que aparecen y que podrían sorprenderte.
Después de leer esto, podrás:
- Defina la aplicación parcial y la curación y explique la diferencia entre ambas.
- Utilice la aplicación parcial para fijar argumentos a una función.
- Las funciones de Curry facilitan la aplicación parcial.
- Funciones de diseño que faciliten la aplicación parcial.
Ejemplo sin aplicación parcial
Como ocurre con muchos patrones, la aplicación parcial es más fácil de entender con el contexto.
Considere esta buildUri
función:
function buildUri (scheme, domain, path) { return `${scheme}://${domain}/${path}`}
Llamamos así:
buildUri('https', 'twitter.com', 'favicon.ico')
Esto produce la cadena https://twitter.com/favicon.ico
.
Esta es una función útil si estás creando muchas URL. Sin embargo, si trabajas principalmente en la web, rara vez usarás algo scheme
distinto de http
o https
:
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')const googleHome = buildUri('https', 'google.com', '')
Observe el punto en común entre estas dos líneas: ambas pasan https
como argumento inicial. Preferiríamos eliminar la repetición y escribir algo más parecido a esto:
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Hay un par de formas de hacer esto. Veamos cómo lograrlo con una aplicación parcial.
Aplicación parcial: fijación de argumentos
Acordamos que, en lugar de:
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')
Preferiríamos escribir:
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Conceptualmente, buildHttpsUri
hace exactamente lo mismo que buildUri
, pero con un valor fijo para su scheme
argumento.
Podríamos implementarlo buildHttpsUri
directamente de la siguiente manera:
function buildHttpsUri (domain, path) { return `https://${domain}/${path}`}
Esto hará lo que queremos, pero aún no ha resuelto nuestro problema por completo. Estamos duplicando buildUri
, pero codificando de forma rígida https
como su scheme
argumento.
La aplicación parcial nos permite hacer esto, pero aprovechando el código que ya tenemos en buildUri
. Primero, veremos cómo hacer esto usando una biblioteca de utilidades funcional llamada Ramda. Luego, intentaremos hacerlo a mano.
Usando Ramda
Usando Ramda, la aplicación parcial se ve así:
// Assuming we're in a node environmentconst R = require('ramda')// R.partial returns a new function (!)const buildHttpsUri = R.partial(buildUri, ['https'])
Después de esto, podemos hacer:
const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Analicemos lo que pasó aquí:
- Llamamos a
partial
la función de Ramda y pasamos dos argumentos: primero, una función, llamadabuildUri
, y segundo, una matriz que contiene un"https"
valor. - Luego, Ramda devuelve una nueva función, que se comporta como
buildUri
, pero con"https"
como primer argumento.
Pasar más valores en la matriz corrige más argumentos:
// Bind `https` as first arg to `buildUri`, and `twitter.com` as secondconst twitterPath = R.partial(buildUri, ['https', 'twitter.com'])// Outputs: `https://twitter.com/favicon.ico`const twitterFavicon = twitterPath('favicon.ico')
Esto nos permite reutilizar el código general que hemos escrito en otro lugar configurándolo para casos especiales.
Aplicación parcial manual
En la práctica, utilizará utilidades como partial
cuando necesite utilizar una aplicación parcial. Pero, a modo de ejemplo, intentemos hacerlo nosotros mismos.
Veamos primero el fragmento y luego lo diseccionemos.
// Line 0function fixUriScheme (scheme) { console.log(scheme) return function buildUriWithProvidedScheme (domain, path) { return buildUri(scheme, domain, path) }}// Line 1const buildHttpsUri = fixUriScheme('https')// Outputs: `https://twitter.com/favicon.ico`const twitterFavicon = buildHttpsUri('twitter.com', 'favicon.ico')
Vamos a desglosar lo que pasó.
- En la línea 0, definimos una función llamada
fixUriScheme
. Esta función acepta unscheme
, y devuelve otra función. - En la línea 1, guardamos el resultado de la llamada
fixUriScheme('https')
en una variable llamadabuildHttpsUri
, que se comporta exactamente igual que la versión que construimos con Ramda.
Nuestra función fixUriScheme
acepta un valor y devuelve una función. Recuerde que esto la convierte en una función de orden superior o HOF. Esta función devuelta solo acepta dos argumentos: domain
y path
.
Tenga en cuenta que, cuando llamamos a esta función devuelta, solo pasamos explícitamente domain
y path
, pero recuerda lo que scheme
pasamos en la línea 1. Esto se debe a que la función interna, buildUriWithProvidedScheme
, tiene acceso a todos los valores en el alcance de su función principal, incluso después de que la función principal haya regresado. Esto es lo que llamamos cierre.
Esto se generaliza. Cada vez que una función devuelve otra función, la función devuelta tiene acceso a cualquier variable inicializada dentro del ámbito de la función principal. Este es un buen ejemplo de cómo usar el cierre para encapsular el estado.
Podríamos hacer algo similar usando un objeto con métodos:
class UriBuilder { constructor (scheme) { this.scheme = scheme } buildUri (domain, path) { return `${this.scheme}://${domain}/${path}` }}const httpsUriBuilder = new UriBuilder('https')const twitterFavicon = httpsUriBuilder.buildUri('twitter.com', 'favicon.ico')
En este ejemplo, configuramos cada instancia de la UriBuilder
clase con un scheme
. Luego, podemos llamar al método, que combina el y buildUri
deseado por el usuario con nuestro .preconfigurado para producir la URL deseada.domain
path
scheme
Generalizando
Recordemos el ejemplo con el que comenzamos:
const twitterFavicon = buildUri('https', 'twitter.com', 'favicon.ico')const googleHome = buildUri('https', 'google.com', '')
Hagamos un pequeño cambio:
const twitterHome = buildUri('https', 'twitter.com', '')const googleHome = buildUri('https', 'google.com', '')
Esta vez hay dos puntos en común: el esquema, "https"
en ambos casos, y la ruta, aquí la cadena vacía.
La partial
función que vimos antes se aplica parcialmente desde la izquierda. Ramda también ofrece partialRight, que nos permite aplicar parcialmente de derecha a izquierda.
const buildHomeUrl = R.partialRight(buildUri, [''])const twitterHome = buildHomeUrl('https', 'twitter.com')const googleHome = buildHomeUrl('https', 'google.com')
Podemos llevar esto más allá:
const buildHttpsHomeUrl = R.partial(buildHomeUrl, ['https'])const twitterHome = buildHttpsHomeUrl('twitter.com')const googleHome = buildHttpsHomeUrl('google.com')
Una consideración de diseño
Para fijar los argumentos scheme
y en , primero tuvimos que usar y luego usar en el resultado.path
buildUrl
partialRight
partial
Esto no es ideal. Sería mejor si pudiéramos usar partial
(o partialRight
), en lugar de ambos en secuencia.
Veamos si podemos solucionar esto. Si redefinimos buildUrl
:
function buildUrl (scheme, path, domain) { return `${scheme}://${domain}/${path}`}
Esta nueva versión pasa primero los valores que probablemente conozcamos de antemano. El último argumento, domain
, es el que probablemente queramos modificar. Organizar los argumentos en este orden es una buena regla general.
También podemos utilizar únicamente partial
:
const buildHttpsHomeUrl = R.partial(buildUrl, ['https', ''])
Esto demuestra que el orden de los argumentos es importante. Algunos órdenes son más convenientes para la aplicación parcial que otros. Tómese un tiempo para pensar en el orden de los argumentos si planea usar sus funciones con una aplicación parcial.
Currying y aplicación parcial conveniente
Ahora lo hemos redefinido buildUrl
con un orden de argumentos diferente:
function buildUrl (scheme, path, domain) { return `${scheme}://${domain}/${path}`}
Tenga en cuenta que:
- Los argumentos que probablemente queramos corregir aparecen a la izquierda. El que queremos modificar está a la derecha.
buildUri
es una función de tres argumentos. En otras palabras, necesitamos pasar tres cosas para que se ejecute.
Hay una estrategia que podemos utilizar para aprovechar esto:
const curriedBuildUrl = R.curry(buildUrl)// We can fix the first argument...const buildHttpsUrl = curriedBuildUrl('https')const twitterFavicon = buildHttpsUrl('twitter.com', 'favicon.ico')// ...Or fix both the first and second arguments...const buildHomeHttpsUrl = curriedBuildUrl('https', '')const twitterHome = buildHomeHttpsUrl('twitter.com')// ...Or, pass everything all at once, if we have itconst httpTwitterFavicon = curriedBuildUrl('http', 'favicon.ico', 'twitter.com')
La función curry toma una función, la curra y devuelve una nueva función, de forma similar a partial
.
Currying es el proceso de transformar una función que llamamos de una sola vez con múltiples variables, como buildUrl
, en una serie de llamadas de función, donde pasamos cada variable de una en una.
curry
No corrige los argumentos inmediatamente. La función devuelta toma tantos argumentos como la función original.- Si pasa todos los argumentos necesarios a la función currada, se comportará como
buildUri
. - Si pasa menos argumentos de los que tomó la función original, la función currada devolverá automáticamente lo mismo que obtendría al llamar a
partial
.
Currying nos ofrece lo mejor de ambos mundos: aplicación parcial automática y la capacidad de utilizar nuestras funciones originales.
Tenga en cuenta que la currificación facilita la creación de versiones parcialmente aplicadas de nuestras funciones. Esto se debe a que las funciones currificadas son convenientes para aplicarlas parcialmente, siempre y cuando hayamos sido cuidadosos con el orden de nuestros argumentos.
Podemos llamar curriedbuildUrl
de la misma manera que llamaríamos buildUri
:
const curriedBuildUrl = R.curry(buildUrl)// Outputs: `https://twitter.com/favicon.ico`curriedBuildUrl('https', 'favicon.ico', 'twitter.com')
También podemos llamarlo así:
curriedBuildUrl('https')('favicon.ico')('twitter.com')
Tenga en cuenta que esto curriedBuildUrl('https')
devuelve una función que se comporta como buildUrl
, pero con su esquema fijado a "https"
.
Luego, llamamos inmediatamente a esta función con "favicon.ico"
. Esto devuelve otra función, que se comporta como buildUrl
, pero con su esquema fijado a "https"
y su ruta fijada a la cadena vacía.
Finalmente, invocamos esta función con "twitter.com"
. Como este es el último argumento, la función se resuelve en el valor final de: http://twitter.com/favicon.ico
.
La conclusión importante es que curriedBuldUrl
se puede llamar como una secuencia de llamadas de función, donde pasamos solo un argumento con cada llamada. Es el proceso de convertir una función de muchas variables pasadas "todas a la vez" en una secuencia de "llamadas de un solo argumento" a lo que llamamos currificación.
Conclusión
Resumamos las conclusiones principales:
- La aplicación parcial nos permite fijar los argumentos de una función, lo que nos permite derivar nuevas funciones, con un comportamiento específico, a partir de otras funciones más generales.
- La currificación transforma una función que acepta múltiples argumentos "todos a la vez" en una serie de llamadas de función, cada una de las cuales involucra solo un argumento a la vez. Las funciones currificadas con un orden de argumentos bien diseñado son convenientes para aplicarlas parcialmente.
- Ramda ofrece utilidades
partial
,partialRight
, ycurry
. Otras bibliotecas populares similares son Underscore y Lodash.
Deja una respuesta