Overview
fnts
is an abbreviation for something like a "functional TypeScript". You could see a similar concept in libraries like fp-ts or ramda, each endorsing their own implementation of the idea.
Here, the philosophy is to give the small set of functions, the possession of which makes it easier to operate the code in a functional style.
The minimal amount of abstractions not present in the TypeScript itself, or being hard to implement and use, (like HKT, typeclasses, overwhelming amount of iterators and transducers) aims to reduce the learning curve of this seemingly "different" style of programming.
Tooling
The library basically provides the tools to handily operate the following concepts: monads, currying, composition, guarding, handling side effects and control flows. Nothing more, nothing less.
Monads
Out of the variety of monads fnts
chooses two presumably most suitable ones: maybe
and either
. Each have their own constructors and operators (as opposed to classes and methods based approach seen commonly).
import maybe from 'fnts/maybe';
import { foldMap } from 'fnts/maybe/operators';
foldMap(
maybe([1, 2, 3].find((n) => n > 2)),
(n) => n === 3
); // true
import either from 'fnts/either';
import { bifoldMap } from 'fnts/either/operators';
bifoldMap(
await either(
() => fetch('https://github.com')
),
(error) => console.error(error),
(data) => data
)
import eitherSync from 'fnts/either';
import { bifoldMap } from 'fnts/either/operators';
bifoldMap(
eitherSync(
() => JSON.parse(localStorage.getItem('context'))
),
(error) => console.error(error),
(data) => data
);
Composition
Composition in fnts
is represented through the compose
and pipe
functions. Both are implemented without the overloads, which causes the types to be rather imposed than inferred.
import compose from 'fnts/compose';
const isTwoDigits = compose(
compose(
(b: boolean) => b ? 'true' : 'false',
(s: string) => s.length === 2 // argument type imposed from the next function
),
(n: number) => `${n}`,
); // will accept only a number as argument
isTwoDigits(5) === 'false';
isTwoDigits(14) === 'true';
import pipe from 'fnts/pipe';
const isTwoDigits = pipe(
pipe(
(n: number) => `${n}`,
(s: string) => s.length === 2
),
(b: boolean) => b ? 'true' : 'false',
);
isTwoDigits(5) === 'false';
isTwoDigits(14) === 'true';
Currying
The curry
function is here to help with auto-currying of variadic or fixed amount of arguments.
import curry from 'fnts/curry';
const sumOfThree = curry(
(a: number, b: number, c: number): number => {
return a + b + c;
}
);
sumOfThree(1, 2, 3) ===
sumOfThree(1, 2)(3) ===
sumOfThree(1)(2, 3) ===
sumOfThree(1)(2)(3);
Application
apply
function calls the functions provided to it on the same set of arguments
and returns the tuple with results.
import apply from 'fnts/apply';
apply(
(a: number, b: number) => a + b,
(a: number, b: number) => a - b,
(a: number, b: number) => a / b,
(a: number, b: number) => a * b,
)(3, 2); // [5, 1, 1.5, 6]
Arguments Permutation
For non-commutative operations or functions that can be applied in the compositional context it is handy to be able to automatically permutate (switch places of) their arguments. For some functions in fnts
this is already implemented:
import { fmap } from 'fnts/maybe/operators';
const mapToNumber = (maybe: Maybe<string>): Maybe<number> => fmap(maybe, (value) => Number(value));
Here, mapToNumber
declaration is equivalent to:
import { fmap } from 'fnts/maybe/operators';
const mapToNumber = fmap<string, number>(Number);
Side effects
For handling side effects there are a couple of functions, the underlying concept of which is to not interfere with the main execution flow:
import inject from 'fnts/inject';
const computeAndLog = inject(
compute,
(...args) => console.log('Computing with args: ', args)
);
computeAndLog(1, 2, 3);
Guarding
In computing there's a pattern called "guard". Specifically, Haskell has a dedicated syntax for that, which fnts
also implemented in a more JavaScripty way:
import guard from 'fnts/guard';
guard<(x: number) => number>(
[(x) => x < 5, (x) => x + 1],
[(x) => x === 5, (x) => x - 1],
() => 1
)(5) // 4
Lenses
There's a pattern in functional programming called "lenses". fnts
brings two necessary functions
for this pattern to work, although in a simplified manner, – get
and set
.
import get from 'fnts/lens/get';
import set from 'fnts/lens/set';
const object = {
a: {
b: {
c: 1
}
}
};
const value = get(object, 'a.b.c'); // 1
const objectCopy = set(object, 'a.b.c', 4);
Alternatively, lens
function which combines both APIs together is available:
import lens from 'fnts/lens';
const object = {
a: {
b: [
{ c: 0 },
{ c: 1 },
{ c: 2 },
{ c: 3 },
]
}
};
const objectLens = lens(object);
const value = objectLens('a.b.1.c'); // 1
const objectCopy = objectLens('a.b.1.c', 4);
For a full overview of the available tools consult with the API reference.