A library for piping values through transformations, in a type-safe way.
pipe from lodash and Ramda, but with partial application for better type safety. It also includes an asynchronous pipe function for dealing with promises.
npm i pipeout --save# oryarn add pipeout
For these examples, we'll imagine we're building a set of functions for counting and filtering marbles. For the full example, see readme.test.ts.
pipe work with synchronous data (which is not wrapped in a promise).
Here's an example trying to find how many red marbles are in a list.
pipe is a basic pipe, like
|> in Haskell or Elixir. The first value passed in is a value to transform. After that you can pass a series of transformer functions by using the
thru method. Each one will transform the value returned from the previous function, and return an object you can call
.thru on again to pipe again, or call
.value() on to get the transformed value.
pipe is a pretty common function name in libraries
pipe is aliased as
pip for convenience.
pipe in Lodash or Ramda works a little differently. Instead of passing in the value to transform immediately, it just takes a list of functions, and returns a function that will run the transformations when called. In functional programming this is called a "point-free function", and is good for situations where you're defining a function that will be run later.
If you want the same thing in
pipeout, then instead of calling
pipe.thru. You still chain together functions with
.thru, but now calling the resulting function will run the pipeline.
.thru returns a new function, so if you have a reference to a pipe, calling
.thru on it won't mutate the original function.
There are also asynchronous variants,
These will always result in a promise, and will work whether your values and functions are synchronous or asynchronous.
All transformer functions should take a value, and can return a value OR a promise.
The starting value can be a promise or a value.
For this example, we'll imagine that getting the user's marbles and getting the user's favorite color are asynchronous API operations.
pipeA chains together promises, you can handle promise errors as normal.
const redCounter = pipeA;const redCount = await ;
Or handle them with async/await and try/catch:
const redCounter = pipeA;tryawait ;catch error;
Why another pipe function?
pipe function in a variadic function that takes any number of unary transformer functions, and returns a function that pipes a value through each transformer.
It usually looks a little like this:
That works pretty well! But creating TypeScript typings for it is a pain, as you have to declare a separate overload for every possible arity, like these Ramda types:
What a pain to maintain! Pipeout takes a different approach.
pipe is mostly useful for curried functions - so why not curry
pipeout.pipe.thru is essentially a recursive curried function. It takes a single function, and returns a version of
pipe that already has the first function in memory. So you can keep calling
.thru, passing in more transformers. When you're done setting up functions, you call
.run with an argument, and it passing your value through all the functions.
That means we can write the same thing like this:
It's type-safe, no matter how many functions you add in. And the type is nice and simple, instead of the long overloaded type from Ramda. Every call to
pipe.thru just returns this same recursive type:
It describes how you can call the function to transform
U, or call
.thru to get a
V pipeline instead. All the intermediate transformations aren't relevant, so they don't have to show up in the type. Simple!
yarn installyarn testyarn lintyarn formatyarn build