https://github.com/biggyspender/ts-functional-pipe
Heavily overloaded functions (pipe/compose) for type-safe function composition in TypeScript
https://github.com/biggyspender/ts-functional-pipe
compose composed-functions function-composition javascript pipe point-free type-safe typescript
Last synced: 3 months ago
JSON representation
Heavily overloaded functions (pipe/compose) for type-safe function composition in TypeScript
- Host: GitHub
- URL: https://github.com/biggyspender/ts-functional-pipe
- Owner: biggyspender
- License: mit
- Created: 2019-04-04T19:29:44.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2023-02-27T19:50:24.000Z (over 2 years ago)
- Last Synced: 2025-01-30T01:02:45.916Z (9 months ago)
- Topics: compose, composed-functions, function-composition, javascript, pipe, point-free, type-safe, typescript
- Language: TypeScript
- Homepage: https://biggyspender.github.io/ts-functional-pipe
- Size: 2.63 MB
- Stars: 25
- Watchers: 2
- Forks: 3
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: code-of-conduct.md
Awesome Lists containing this project
README
# Type-safe function composition for Typescript
## A micro-library for functional composition
In the absence of `|>` (the pipe operator) it's useful to have a type-safe pipe function that can compose an a large (up to 64) number of unary functions. This minimal library contains a few different helper functions for this purpose.
[](https://npmjs.org/package/ts-functional-pipe "View this project on npm")
[](https://travis-ci.org/biggyspender/ts-functional-pipe)> NOTE
> ---
>
> Versions <=2.x erroneously used the term `compose` for left-to-right function composition. v3 is a major overhaul of this library and contains several breaking changes, both in the code, and in the meaning of `compose`.
>
> These are version >=3 documents. Please find v2.x documentation [here](https://github.com/biggyspender/ts-functional-pipe/tree/v2.1.1)## Requirements
This library makes use of [leading/middle rest elements](https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/#non-trailing-rests), introduced in **Typescript version 4.2**
## Usage
Suppose we have the following unary functions:
```typescript
const dinosaurify = (name:string) => `${name}-o-saurus`
const sayHello = (name:string) => `Hello, ${name}!`
```We can compose these functions into a single function using the compose function:
```typescript
const sayHelloToDinosaur = compose(sayHello, dinosaurify)
```and call it
```typescript
sayHelloToDinosaur("mike") // "Hello, mike-o-saurus!"
```Note that with `compose`, function composition occurs from *right-to-left*.
The `pipe` function composes its parameters from *left-to-right*, so the equivalent `pipe` version of the code above would be:
```typescript
const sayHelloToDinosaur_withPipe = pipe(dinosaurify, sayHello)
```### The `applyArgs` helper
Alternatively, we could have called the `applyArgs` helper, which is useful for ensuring that type inference flows inutitively through the composed functions. This makes more sense later when we start using it with (apparently) untyped arrow functions.
```typescript
applyArgs("mike").to(pipe(dinosaurify, sayHello)) // "Hello, mike-o-saurus!"
```or, less verbosely:
```typescript
applyArgs("mike")(pipe(dinosaurify, sayHello)) // "Hello, mike-o-saurus!"
```### `pipeInto` function
This is shorthand to combine the `applyArgs` helper with `pipe`, reducing the amount of boilerplate. Using `pipeInto` we can rewrite the above as:
```typescript
pipeInto("mike", dinosaurify, sayHello)
```## In depth
Pipes work with **unary**-functions, using the return value of one function as the only parameter to the next function.
### Defining higher-order unary map and filter functions
Say we create our own versions the Array map and filter functions to work over `Iterable`
```typescript
// helper function for making iterables from generator functions
const toIterable = IterableIterator>(f: TF) => ({
[Symbol.iterator]: f
})const _map = (src: Iterable, selector: (v: T, i: number) => TOut): Iterable =>
toIterable(function*() {
let c = 0
for (const v of src) {
yield selector(v, c++)
}
})const _filter = (src: Iterable, pred: (v: T, i: number) => boolean): Iterable =>
toIterable(function*() {
let i = 0
for (const x of src) {
if (pred(x, i++)) {
yield x
}
}
})
```Here, the `_map` and `_filter` are not unary functions so cannot be used in a pipe/compose.
### Convert functions to unary with `deferP0`
We can use the provided `deferP0` method to transform these functions into functions that return a unary function (that takes a single parameter that was the first parameter of the original source function)
So it turns functions of the form
(src: TSrc, b: B, c: C, d: D) => R
into functions of the form(b: B, c: C, d: D) => (src: TSrc) => R
### Functions that return unary functions
So, to make a composable `map` function:
```typescript
const map = deferP0(_map)
```Here, we transform the `_map` function with type
(src: Iterable, selector: (v: T, i: number) => TOut): Iterable
into the generated `map` function which has the type(selector: (v: T, i: number) => TOut) => (src: Iterable): Iterable
As can be seen, we end up with a function that generates a **unary** function.
We can do the same with `_filter`
```typescript
const filter = deferP0(_filter)
```Now the `map` and `filter` functions that we generated above **return** unary functions and can be used in a pipe/compose with type inference "flowing" through the composed functions.
### Composing `map` and `filter` with `pipe`
Let's use them with the `pipe` and the `applyArgs` helper (so that type information propagates through all the function parameters)
```typescript
const transformed =
applyArgs([1, 2, 3]).to(
pipe(
filter(x => x % 2 === 1), // x is inferred as number
map(x => x * 2) // x is inferred as number
)
) // iterable with values [2, 6]
```When using "untyped" arrow functions, as above, by using the `applyArgs` helper, we can see how types are propagated through the functions without needing to provide types for any function parameters. However, we might just want a re-useable function composed of multiple functions, so we can use `compose(...unaryFuncs)` or `pipe(...unaryFuncs)` on their own... but we'll need to supply type-information, usually in just one place, so that typescript can infer other types successfully:
```typescript
const oddNumbersMultipliedByTwo =
// pipe is inferred as (src:Iterable)=>Iterable
pipe(
// typescript can infer all other types when
// we provide this input type annotation (number)
filter(x:number => x % 2 === 1),
map(x => x.toString()), // x is inferred as number
map(x => x + " " + x) // x is inferred as string
)
```So `oddNumbersMultipliedByTwoPipe` has the inferred type
(src: Iterable) => Iterable
and when we use it...
```typescript
const r = oddMultipliedByTwo([1, 2, 3])
// arr has type string[]
const arr = [...r] // ["1 1", "2 2"]
````arr` has type string[]
### acknowledgements
Created using the wonderful [https://github.com/alexjoverm/typescript-library-starter](https://github.com/alexjoverm/typescript-library-starter).