jafl
v3.1.0
Published
Just Antoher Functional Library
Downloads
256
Maintainers
Readme
Just Another Functional Library (jafl)
As the title said, it is just another functional programming library. I always end up writing the same "utils" file with a couple of functions that helps me write the code in a more declarative-first way. Using mainly but not only function composition.
Notes:
I'm working on a mayor (v4) update, it probably includes a pipe refactor, and a couple of new features like
sleep
function and some initial work forImmutable
objects. Also improved documentation and tests are a personal goal. Thanks for being patience.
Introduction
This library allows you to write functional and declarative code having the KISS principle in mind.
It provide flow-control utils like, pipe, conditional, or tap. Functions wraping helpers, that could be used as adapters, like curry, pick or applier.
Install
You are good to go with:
yarn add jafl
or:
npm i jafl
Usage
pipe
It takes N functions as parameter, and return a single function that when is called will invoke the first function with the input, and then passthrough the output to the next function input, creating a pipe.
tap
It takes a function (fn), and return and async function that when is invoked with and input will excecute the original function over the input (fn(input)) but instead of returning the function invocation output, returns the original input
conditional
It takes two functions, a conditionalCheckFn, and a conditionalAppliedFn, and return a new function that takes and input and check if the resulto of apply conditionalCheckFn to input is true, then return conditionalAppliedFn(input), and if it is false, it will return the input without applying any function
pick
It pick a key that could be a string like 'name', or a chained access like: 'address.number' and return a function that takes an object and return the value of that key.
In addition it can take multiple of this keys as multiple arguments, and in that case it will return an array with every value in the same order as requested i.e:
const obj = { name: 'Some', address: { number: 33, isReal: false }};
pick('name')(obj); // -> 'Some'
pick('address.number')(obj) // -> 33
pick('address.number', 'id', 'name')(obj) // -> [33, undefined, 'Some']
curry
This curry implementation works thanks to @cbroeren code (thank you!)
Currying is a bit hard to understand but sometime when you are using a pipe you most likely will need to "trick" some function to be prefilled with a first argument and have a function that only receive the rest of the parameters, well that when curry kicks in.
const sendEmail = ; // we have this implemented and has the following signature: (to: string, topic: string, message: string)
const sendEmailWithCurry = curry(sendEmail);
const sendElonWithCurry = sendEmailWithCurry(_, _, 'You are fired!'); // now this function is prefilled with the third argument, message.
const sendElonMsg = (users: [User]) => {
const process = pipe(
conditional(IHaveTheRightMood, pipe(
tap(logEmailSendtTriggered),
pick('emailAddress'),
sendElonWithCurry(_, 'I Have a lovely message for you') // Now is filled with message, and topic, so it will return a function that receive a "to" and excecute the original function
))
);
await Promise.all(users.map(process));
}
applier
It takes a function (fn) and return a function that receive a list of arguments in an array-like format and call fn with those args as it was a non array receiving function i.e:
const greeter = (greetMsg: string, userName: string) => `Hey, ${greetMsg} ${userName}!`;
applier(greeter)(['hello there', 'fellow friend']); // -> 'Hey, hello there fellow friend!'
Integration Sample
So let asume you have something like:
const findUser = async () => User //ie: { name: 'John', mail: { address: [email protected], foo: true } }
const checkOnboarding = async () => //...
const trackEventToCloud = async () => //...
const isFirstTimeCompletingOnboarding = (user) => true; // implement a real one
const sendEmail = curry(_sendEmail);// where _sendEmail = (to, topic, msg) => {...};
You could write your composition like this:
const process = pipe(
findUser, // we get the User from DB
checkOnboarding, //We check onboarding status
conditional(isFirstTimeCompletingOnboarding, pipe( //as it is the first time, so this pipe wil run
tap(trackEventToCloud), // we track the event but return the same input using tap
pick(['mail.adress', 'name']),// we only need the mail adress and the name for the user
applier(sendEmail(_, _, "Welcome!")) // applier will give sendEmail the two remains parameters from the input array (the pick's output)
))
);
await process(user);
And what we are doing in here is telling in a declarative way:
- Check the onboarding process
- Check if is the first time completing onboarding (
conditional
) - If yes, then:
- track the event in some logs but return the input user as output instead of the traking result (
tap
) - send an email
- track the event in some logs but return the input user as output instead of the traking result (
- If no, then return the same input.
This whole process happens in a pipe, where input flow from left to right. So if we got: A, B and C, and then we define pipe as P where P = P(A, B, C), P will be a function that given and input it will excecute A, then the result will be the input to execute B, and finally the result will be the input of C, so the finall result would be C(B(A(input))) equivalent.
Changelog
Latest Release:
v3.1.0
- Updated dev dependencies
- moved from
yarn
tonpm
package manager - move CI from node v18 to v20
- updated some docs typos
- deprecated old versions ( < 3.0.1 )
You can view full changelog here: -> CHANGELOG <-