Saltar al contenido principal

¿Qué está pasando con la propuesta del operador pipeline (|>)?

· 7 min de lectura
Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Con el lanzamiento de babel@7.0.0-beta52, introdujimos una nueva bandera de configuración obligatoria en @babel/plugin-proposal-pipeline-operator, un cambio disruptivo para el operador pipeline. Para aclarar cualquier confusión, examinemos la propuesta del pipeline y por qué necesitábamos introducir esta opción de configuración.

Estado actual

El operador pipeline fue introducido originalmente por Gilbert Garza, proporcionando una sintaxis limpia para "simplificar llamadas encadenadas de funciones de manera legible y funcional". Este operador tiene raíces en varios lenguajes como F#, Hack, Elm, Elixir y otros, pero surgieron dos puntos principales de controversia al introducir la nueva sintaxis en JavaScript:

  • Si introducir marcadores de posición y cómo hacerlo

  • Cómo manejar async/await en el pipeline

Marcadores de posición

El primer tema fue la cuestión de los marcadores de posición. Esto fue planteado inicialmente por Kevin Smith en este issue, donde sugirió el pipelining estilo Hack. En Hack, se requiere un marcador de posición para cada lado derecho del pipeline, como en este ejemplo:

namespace Hack\UserDocumentation\Operators\Pipe\Examples\MapFilterCountPiped;

function piped_example(array<int> $arr): int {
return $arr
|> \array_map($x ==> $x * $x, $$)
|> \array_filter($$, $x ==> $x % 2 == 0)
|> \count($$);
}

var_dump(piped_example(range(1, 10)));

Basamos nuestro enfoque en este concepto, ya que un marcador de posición puede usarse fácilmente en expresiones arbitrarias, representando el valor devuelto en el paso anterior. Esto proporciona flexibilidad y potencia adicionales dentro de un paso del pipeline.

La desventaja es la complejidad de introducir un nuevo token. El símbolo de hash (#) es la opción actual y, aunque sigue sujeto a discusión, cualquier token tendría potencialmente múltiples significados. El hash también es utilizado por la propuesta de campos privados, y todas las demás opciones ya se usan de una forma u otra.

Async / Await

La introducción inicial del pipeline incluía esta sintaxis para await:

JavaScript
x |> await f

que se transformaría en

JavaScript
await f(x)

Desafortunadamente, los usuarios podrían esperar esta transformación alternativa:

JavaScript
(await f)(x)

Aunque hubo resistencia a incluir manejo asíncrono en el pipeline, miembros del comité expresaron preocupación sobre un operador pipeline que no manejara async/await. Si bien existen formas de manejar funciones que devuelven Promises sin sintaxis explícita, resultan demasiado engorrosas para ser útiles o requieren funciones auxiliares.

Soluciones propuestas

Como resultado de estas discusiones, surgieron dos propuestas junto con una propuesta mínima base para resolverlas: Pipelines F# y Pipelines Inteligentes. Veamos cómo resuelven los problemas planteados.

Pipelines Mínimos

Esta propuesta cubre la funcionalidad básica del operador pipeline. La propuesta mínima prohíbe await, por lo que no incluye manejo asíncrono ni marcadores de posición. Coincide con el comportamiento del plugin de Babel antes de introducir la configuración y es la especificación actual en el repositorio de la propuesta. Funciona más como un hombre de paja para comparar beneficios y compensaciones de otras propuestas, y es improbable que sea aceptada tal cual sin defectos fatales en ambas alternativas.

Pipelines F#

Sobre la cuestión de los marcadores de posición, Pipelines F# argumentan que no son necesarios. En la propuesta base, las funciones flecha cubren el área que ocuparían los marcadores, requiriendo menos sintaxis nueva y aprovechando una sintaxis que los desarrolladores ya conocen y han usado desde ES2015.

Según la especificación actual, las funciones de flecha deben envolverse entre paréntesis:

JavaScript
let person = { score: 25 };

let newScore = person.score
|> double
|> (_ => add(7, _))
|> (_ => boundScore(0, 100, _));

Se está explorando si sería factible permitir el uso de funciones de flecha sin paréntesis, ya que representan una carga sintáctica significativa.

Respecto al manejo asíncrono, las tuberías F# tratan await similar a una función unaria:

JavaScript
promise |> await

Esto se desugarizaría como:

JavaScript
await promise

permitiendo así su uso en medio de cadenas de funciones más complejas con operaciones asíncronas:

JavaScript
promise
|> await
|> (x => doubleSay(x, ', '))
|> capitalize
|> (x => x + '!')
|> (x => new User.Message(x))
|> (x => stream.write(x))
|> await
|> console.log;

Este tratamiento especial de await podría habilitar otros operadores unarios de manera similar (ej. typeof), aunque las tuberías F# inicialmente no los soportan.

Tuberías Inteligentes

Las Tuberías Inteligentes llevan el concepto de marcador de posición a su conclusión lógica, permitiendo gestionar aplicación parcial y expresiones arbitrarias en la tubería. La cadena anterior se escribiría así:

JavaScript
promise
|> await #
|> doubleSay(#, ', ')
|> # || throw new TypeError()
|> capitalize
|> # + '!'
|> new User.Message(#)
|> await stream.write(#)
|> console.log;

Las Tuberías Inteligentes tienen reglas específicas para los marcadores de posición. Si se proporciona un identificador simple en un paso de la tubería, no se requiere token adicional (estilo "bare"):

JavaScript
x |> a;
x |> f.b;

A diferencia de Hack, las funciones unarias no requieren token de marcador.

Para otras expresiones, se requiere un marcador (llamado "token léxico de tema"), generando un SyntaxError temprano si se omite en el "estilo topic":

JavaScript
10 |> # + 1;
promise |> await #;

Si hay operadores, paréntesis (incluyendo llamadas a métodos), corchetes o cualquier elemento que no sean identificadores o puntuadores de punto, entonces es obligatorio el token de tema. Esto evita errores comunes y elimina ambigüedades.

Las tuberías inteligentes resuelven así el manejo asíncrono de manera integrada, permitiendo incrustar cualquier expresión en la tubería; no solo await, sino también typeof, yield y otros operadores.

El papel de Babel

Al madurar las tres propuestas, reconocimos que el debate difícilmente resolvería sus tensiones inherentes. Decidimos que la mejor forma de decidir sería mediante retroalimentación de desarrolladores usando las propuestas en código real. Dado el rol de Babel, implementamos las tres propuestas en el plugin del operador de tubería.

Como estas propuestas se analizan sintácticamente de forma diferente, fue necesario añadir soporte en @babel/parser (antes babylon), requiriendo configuración según la propuesta objetivo. El plugin necesita así la opción "proposal" para configurar tanto el parser como su transformación.

Trabajamos con un cronograma ajustado, ya que los cambios rupturistas en Babel, @babel/parser y el plugin debían implementarse antes de que babel@7 saliera de beta. También queríamos que el plugin adoptara eventualmente la propuesta estándar, haciendo obsoleta la opción de configuración.

Dadas estas restricciones, introdujimos esta opción de configuración como obligatoria, forzando a los usuarios a elegir qué propuesta usar en sus proyectos. Cuando se estandarice una propuesta, deprecaremos "proposal" y estableceremos el comportamiento por defecto, eliminando las otras en futuras versiones mayores.

Participa

Si estás interesado en involucrarte en la propuesta del operador pipeline, todas las conversaciones son públicas y puedes encontrarlas en el repositorio del operador pipeline. También puedes consultar la presentación de la última reunión del TC39. Por último, puedes contactar a James DiGioia, J. S. Choi o Daniel Ehrenberg en Twitter.

Pero lo más importante es que, una vez completado el trabajo, ¡pruebes el operador pipeline en tus propios proyectos! También estamos trabajando en añadir opciones al REPL, donde podrás probar el código. Necesitamos comentarios y código real para que esto sea útil, así que nos encantaría saber tu opinión. Envíanos un tweet a @babeljs para informarnos.