DEV Community

Václav Zeman
Václav Zeman

Posted on

How Pipeline Operator makes your code cleaner

There's been talks of pipeline operator coming to ECMAScript for at least 2 years so this is no breaking news in any way :). There is a living proposal here with 2 possible variatons: F# and Smart pipelines. Both of them have their own Babel plugins so you can try it anytime.

And what's the motivation to use it? Without pipeline operator, you can either nest your function calls like this:

// Using Ramda for the helper functions but lodash(/fp) would be quite similar
const { get, ... } = require('ramda');
const electronics = [{ title: ' iPhone ', type: 'phone' }];

const phones = trim(toUpper(prop('title', find(propEq('type', 'phone'), electronics)))); 
// => 'IPHONE'

and create unreadable mess or you can assign the function values to variables and end up with readable but verbose code.

Pipeline operator to the rescue! This recreates the above using the new syntax:

const { get, ... } = require('ramda');
const electronics = [{ title: ' iPhone ', type: 'phone' }];

const phones = 
    electronics 
    |> find(propEq('type', 'phone')) 
    |> prop('title') 
    |> toUpper 
    |> trim
// => 'IPHONE'

The code is terser and more readable as a single linear flow.


Side note: this is already achievable in a non-native fashion with e.g. RxJS or ramda/lodash's pipe (or compose for opposite order) as such:

const phones = pipe(
    find(propEq('type', 'phone')),
    prop('title'),
    toUpper,
    trim,
)(electronics)

Topic reference

This is probably the coolest thing about the proposal. Smart pipelines have something called topic reference, currently noted as # (in the end this could also be ? or %) which represents the current value at the specific "step" inside of pipe.

What does this mean? Let's take a look at an example:

Promise.resolve('post title ') 
    |> await # // Promise<resolved> => 'post title '
    |> typeof # === 'string' ? # : 'Untitled' // => 'post title '
    |> #.trim() // => 'post title'
    |> capitalize // => 'Post title'
    |> 'My Blog - ' + # // => 'My blog - Post title'

This is great because you can access the value directly without any need for curried functions. That means you can simply access value #.length, call #.trim() or just use classic lodash functions like endsWith(#, '...'). Just make sure you don't attempt to mutate # (e.g. # = # * 5) - it's unsupported and generally a bad practice.

Real world use case

While this is all applicable very well in a lot of scenarios, one use case that I like the most is validation.

Imagine a function that validates email. With pipeline operator this would look like this:

const gte = min => value => {
    if (value >= min) return value
    throw new Error('Value must be equal or greater than ' + min)
}
const lte = max => value => {
    if (value >= min) return value
    throw new Error('Value must be equal or smaller than ' + max)
}
const email = value => {
    if ("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$".test(value)) return value
    throw new Error('Value is not an email')
}

const validateEmail = value => 
    value 
        |> gte(5)
        |> lte(10)
        |> email

While the gte, lte, email functions are still vanilla, the validateEmail readability is much higher!


This article just summaries thing I found interesting and it doesn't include everything. You might be able to use pipeline operator with prototypes, mixins, as an object decorator as mentioned here.

Top comments (0)