# Antiutils

[![npm version](https://badge.fury.io/js/antiutils.svg)](https://badge.fury.io/js/antiutils)
[![gzip size](https://badgen.net/bundlephobia/minzip/antiutils?color=green)](https://bundlephobia.com/result?p=antiutils)
[![gzip size](https://badgen.net/bundlephobia/tree-shaking/antiutils)](https://bundlephobia.com/result?p=antiutils)

TypeScript/JavaScript utilities for those who don't like utilities.

- [Minimal API](#minimal-api)

- [Pipeline operator ponyfill](#pipeline-operator-ponyfill)

- [Non-mutating functions for working with objects, arrays, maps, and sets](#objects-arrays-maps-and-sets)

- [Functions for working with native `Iterable`s](#iterables)

- [Comparison functions](#comparison-functions)

- [Reducers](#reducers)

- [Lenses](#lenses)

- [Memoization utilities](#memoization)

- [Functions for downcasting](#functions-for-downcasting)

## Installing

```
yarn add antiutils
```

or

```
npm install antiutils --save
```

## Minimal API

Based on the "only one way to do it" principle, this library provides a utility only when something can't be easily and readably done with vanilla JavaScript. For example, we do not provide a function to get an object's property value, so instead of `get('a')` you would just write `value => value.a`. This is because we see the mental overhead of choosing among multiple ways to write a piece of code as higher cost compared to doing more typing.

That said, we do sometimes provide a shortcut for what would otherwise be a one-liner, such as `lastInIterable` for `reduceIterable((...[, value]) => value)` - not for conciseness or performance, but to improve readability.

## Pipeline operator [ponyfill](https://ponyfill.com/)

The library includes a function `applyPipe` which takes between 1 and 12 arguments: `applyPipe(x, a, b)` is equivalent to `b(a(x))`, or using the pipeline operator, `x |> a |> b`. Type inference works well with this function, and if any one of the proposed flavors of the pipeline operator eventually reaches stage 3 and starts to be supported in TypeScript, it would be straightforward to build a codemod to convert the function to the operator.

The library intentionally doesn't include a `pipe` function that would compose functions without applying the resulting function to an argument, mainly because this would go against "only one way to do it".

## Objects, arrays, maps and sets

The library includes non-mutating functions for working with [objects](https://github.com/ivan7237d/antiutils/tree/master/src/internal/object), [arrays](https://github.com/ivan7237d/antiutils/tree/master/src/internal/array), [maps](https://github.com/ivan7237d/antiutils/tree/master/src/internal/map), and [sets](https://github.com/ivan7237d/antiutils/tree/master/src/internal/set).

> :bulb: TIP
>
> If you use TypeScript 4.1+, make sure you enable strictly checked indexed access using [`--noUncheckedIndexedAccess` compiler flag](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1-rc/#no-unchecked-indexed-access).

## Iterables

[Functions for working with iterables](https://github.com/ivan7237d/antiutils/tree/master/src/internal/iterable) have signatures that try to stay close to corresponding native array methods and RxJS operators.

How-to:

- **Filter an iterable in a way that the type system understands:**

  ```ts
  applyPipe(
    [1, undefined],
    // Equivalent to `filterIterable((value) => value !== undefined)`.
    flatMapIterable((value) => (value !== undefined ? [value] : [])),
  );
  ```

  (type will be inferred as `Iterable<number>`, not `Iterable<number | undefined>` as would be the case if you used `filterIterable`; the same trick works when filtering arrays and observables).

- **Index elements:** `zipIterables(rangeIterable(), yourIterable)` (returns an iterable of `[<element index>, <element>]`).

- **Get a flag indicating if the element is the first element:** `zipIterables(firstIterable, yourIterable)` (returns an iterable of `[boolean, <element>]`).

- **Count elements in an iterable:**: `applyPipe(yourIterable, reduceIterable(countReducer, 0))`.

- **Check if every element in an iterable is true:** `applyPipe(yourIterableOfBooleans, reduceIterable(andReducer, true))` (iteration will not continue unnecessarily because of how [`reduceIterable`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/iterable/reduceIterable.ts) and [`andReducer`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/reducer/andReducer.ts) are defined).

- **Check if some elements in an iterable are true:** `applyPipe(yourIterableOfBooleans, reduceIterable(orReducer, false))`.

- **Find the first element matching a predicate:** `applyPipe(yourIterable, filter(yourPredicate), firstInIterable)`.

- **Yield values while a condition holds:**

  ```ts
  applyPipe(
    [1, 2, 3, 2],
    scanIterable((...[, value]) => (value <= 2 ? value : undefined)),
  );
  ```

  (yields `1`, `2`, see [`scanIterable`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/iterable/scanIterable.ts)).

- **Also yield the value that broke the condition:**

  ```ts
  applyPipe(
    [1, 2, 3, 2],
    scanIterable((accumulator, value) =>
      accumulator <= 2 ? value : undefined,
    ),
  );
  ```

  (yields `1`, `2`, `3`).

## Comparison functions

The library exports types

```ts
type CompareFunction<T> = (to: T, from: T) => number;
type EqualFunction<T> = (from: T, to: T) => boolean;
```

It [provides](https://github.com/ivan7237d/antiutils/tree/master/src/internal/compare) implementations of `CompareFunction` for primitive types and a function `lexicographicCompare` to compose `CompareFunction`s.

It also provides implementations of `EqualFunction` for objects, iterables, maps, and sets, and a function [`deepEqual`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/deepEqual.ts) that recursively delegates to those functions depending on the object type.

## Reducers

The library exports a type

```ts
type Reducer<Accumulator, Element> = (
  accumulator: Accumulator,
  element: Element,
) => Accumulator | undefined;
```

which is like a regular reducer, but can return `undefined` to stop the iteration short, and which is used by functions [`reduceIterable`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/iterable/reduceIterable.ts) and [`scanIterable`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/iterable/scanIterable.ts).

The library includes [basic implementations of this type](https://github.com/ivan7237d/antiutils/blob/master/src/internal/reducer), all of which except the boolean ones (`andReducer` and `orReducer`) can also be used with arrays and observables.

## Lenses

We start by defining a `View` as a combination of a getter and a setter:

```ts
type View<S, A> = { get: () => A; set: (value: A) => S };
```

and define a `Lens` as a function that transforms a view into another view:

```ts
type Lens<S, A, B> = (source: View<S, A>) => View<S, B>;
```

The library provides the following functions:

- [`objectProp`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/object/objectProp.ts): a lens to zoom in on an object's property.

- [`mapProp`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/map/mapProp.ts): a lens to zoom in on a value stored in a `Map`.

- [`setProp`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/set/setProp.ts): a lens to zoom in on presence of an element in a `Set`.

- [`rootView`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/view/rootView.ts): a function that converts a `value` into a view `{ get: () => value, set: <identity function> }`.

Example usage:

```ts
type State = { a: { b: string; c: string } };

/**
 * A reducer that sets the value of `b` in the state to the payload.
 **/
const sampleReducer = (state: State, action: { payload: string }) =>
  applyPipe(
    // Returns `View<State, State>`.
    rootView(state),
    // Transforms values into `View<State, { b: string; c: string }>`.
    objectProp('a'),
    // Transforms values into `View<State, string>`.
    objectProp('b'),
  )
    // `set` takes a value for `b` and returns a new `State`.
    .set(action.payload);

expect(sampleReducer({ a: { b: '', c: '' } }, { payload: 'x' })).toEqual({
  a: { b: 'x', c: '' },
});
```

Example of usage with optional properties:

```ts
// Note the optional `a`.
type State = { a?: { b: string; c: string } };

const sampleReducer = (state: State, action: { payload: string }) =>
  applyPipe(
    rootView(state),
    // Transforms values into `View<State, { b: string; c: string } |
    // undefined>`.
    objectProp('a'),
    // Transforms values into `View<State, { b: string; c: string }>`.
    ({ get, set }) => ({
      get: () => get() ?? { b: '', c: '' },
      set,
    }),
    objectProp('b'),
  ).set(action.payload);

expect(sampleReducer({}, { payload: 'x' })).toEqual({
  a: { b: 'x', c: '' },
});
```

The library also defines

```ts
type StateView<A> = View<void, A>; // = { get: () => A; set: (value: A) => void };
```

where the setter does not return any value, but instead produces a side effect. To learn about how this type is used, please see the docs for [`antiutils-react`](https://github.com/ivan7237d/antiutils-react), a package that provides glue between Antiutils and React.

## Memoization

The library provides utilities [`memoizeWeak`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/memoize/memoizeWeak.ts) and [`memoizeStrong`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/memoize/memoizeStrong.ts) to memoize functions that take a single argument. Internally they cache results in respectively a WeakMap and a Map, with arguments as keys and results as values. `memoizeWeak` has an advantage that retaining a reference to the memoized function will not prevent cached arguments and results from being garbage-collected, but it can only memoize functions that take objects (not primitive values) as arguments, because only objects can be used as keys in a WeakMap.

> :bulb: TIP
>
> You can combine `memoizeWeak` and `memoizeStrong` to memoize a function that takes multiple arguments, some of them primitive values, e.g.
>
> ```ts
> const original = (x: { a: number }, y: number) => x.a + y;
> const memoized = memoizeWeak((x: { a: number }) =>
>   memoizeStrong((y: number) => original(x, y)),
> );
> const withRestoredSignature = (x: { a: number }, y: number) => memoized(x)(y);
> ```

The library also provides a function [`teach`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/memoize/teach.ts) for cases when you need to teach a memoized function to return a result already known from an external source such as persisted storage, and a function [`knows`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/memoize/knows.ts) to check if there is a cached result for a given argument.

## Functions for downcasting

The library provides the following identity functions that cast the argument to a subtype, but unlike the TypeScript's `as`, never make type assertions:

- [`asNever`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asNever.ts): an identity function which has signature `(value: never) => never ` and which throws if called, used to typecheck that a symbol has type `never` and therefore the call site is unreachable. For example, if `a` has type `0 | 1`, you could write `a === 0 ? 'zero' : a === 1 ? 'one' : asNever(a)` to make sure that all possibilities for `a` have been exhausted. If the type of `a` changes to say `0 | 1 | 2`, the type of the argument passed to `asNever` will be inferred as `2`, and this will cause a typechecking error because the only type assignable to `never` is `never` itself.

- [`as`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/as.ts): an identity function with signature `<T>(value: T) => T` that can be used to downcast a value to a non-generic type: `as<YourType>(yourValue)`.

- [`asContext`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asContext.ts): an identity function with signature `<A, B extends A>(value: B) => A` that can be used to infer the type of a value from the context instead of the other way around. For example, the function `firstInIterable` can be implemented as `reduceIterable(() => undefined)`, but writing `applyPipe([0, 1], reduceIterable(() => undefined))` will not typecheck. The reason is that TypeScript will look at the reducer `() => undefined` and infer the type of the accumulator as `undefined`, and then expect the array `[0, 1]` to be `undefined[]`. Enclosing the reducer in `asContext` (`asContext(() => undefined)`) will cause TypeScript to correctly infer the type of the accumulator as `number`.

- [`asCompareFunction`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asCompareFunction.ts), [`asEqualFunction`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asEqualFunction.ts), [`asLens`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asLens.ts), [`asReducer`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asReducer.ts), [`asStateView`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asStateView.ts), [`asView`](https://github.com/ivan7237d/antiutils/blob/master/src/internal/types/asView.ts): identity functions that can be used to downcast values to any of the generic types defined by the library.

---

[Contributing guidelines](https://github.com/ivan7237d/antiutils/blob/master/.github/CONTRIBUTING.md)