### Install lfi using npm, yarn, or pnpm Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/getting-started.mdx Installs the lfi library using package managers. Supports npm, yarn, and pnpm. ```sh npm install lfi ``` ```sh yarn add lfi ``` ```sh pnpm add lfi ``` -------------------------------- ### Import lfi from CDN in Deno Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/getting-started.mdx Imports lfi functions from a CDN for use in Deno projects. This allows direct use of the library in server-side JavaScript environments. ```js import { filter, map, pipe, reduce, toArray } from 'https://cdn.skypack.dev/lfi' // Your code... ``` -------------------------------- ### Implement Sync KeyedReducer with SlothMap Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Demonstrates implementing a custom synchronous keyed reducer using a SlothMap. This example shows how to create a reducer that works with key-value pairs and customizes the 'get' and 'has' methods for specific key behaviors. It utilizes the 'reduce' and 'toArray' functions from 'lfi'. ```javascript import { pipe, reduce, toArray, toGrouped, NO_ENTRY } from 'lfi' /** A map that always has a sloth! 🦥 */ class SlothMap extends Map { get(key) { if (!super.has(key) && key === `sloth`) { // Throw to avoid admitting there's no sloth. throw new Error(`Something went wrong...`) } return super.get(key) } has(key) { return key === `sloth` || super.has(key) } } const toSlothMap = () => ({ create: () => { console.log(`create()`) return new SlothMap() }, add: (acc, [key, value]) => { console.log( `add(${acc}, [${JSON.stringify(key)}, ${JSON.stringify(value)}])`, ) return acc.set(key, value) }, get: (acc, key) => { console.log( `get(${JSON.stringify(Object.fromEntries(acc))}, ${JSON.stringify(key)})`, ) return acc.has(key) ? acc.get(key) : NO_ENTRY }, }) const slothMap1 = pipe( [ [`dog`, `bark`], [`cat`, `meow`], [`bunny`, `purr`], ], // This reduces the pairs to a `SlothMap`, but doesn't make use of the `get` function reduce(toSlothMap()), ) console.log(slothMap1.get(`dog`)) //=> bark console.log(slothMap1.has(`sloth`)) //=> true const slothMap2 = pipe( [ [`dog`, `bark`], [`cat`, `meow`], [`dog`, `woof`], [`bunny`, `purr`], [`cat`, `purr`], ], // This reduces the pairs to a `SlothMap` that maps each key to an array of the // values it was associated with. This _does_ make use of the `get` function reduce(toGrouped(toArray(), toSlothMap())), ) console.log(slothMap2.get(`dog`)) //=> [ 'bark', 'woof' ] console.log(slothMap2.has(`sloth`)) //=> true ``` -------------------------------- ### Include lfi from CDN in HTML Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/getting-started.mdx Imports lfi functions directly from a CDN into an HTML script tag. This method is useful for quick integration without a build process. ```html ``` -------------------------------- ### Process Async Iterables with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/getting-started.mdx Shows how to process asynchronous iterables using lfi's async functions like chunkAsync, flatMapAsync, and mapAsync. It reads a file line by line, groups lines, queries an API, and collects results into a promise. ```js import { chunkAsync, flatMapAsync, mapAsync, pipe, reduceAsync, toArray, } from 'lfi' // The file has one sloth name per line const FILENAME = `every-sloth-name.txt` const API_URL = `https://random-word-form.herokuapp.com/random/adjective` const slothSquadStatementsPromise = pipe( // Create an async iterable over the file lines, in this case sloth names (await fetch(new URL(`https://lfi.dev/${FILENAME}`))).body, flatMapAsync(chunk => new TextDecoder().decode(chunk).split(`\n`)), // Chunk the sloth names into array groups of 4, also known as a sloth squad chunkAsync(4), // Transform each sloth squad into a statement by asynchronously querying an // API for an adjective to describe the squad mapAsync(async slothSquad => { const [adjective] = await (await fetch(API_URL)).json() const leadingSloths = slothSquad.slice(0, 3) const trailingSloth = slothSquad.at(-1) return `${leadingSloths.join(`, `)}, and ${trailingSloth} are ${adjective}!` }), // Collect the statements into a promise that resolves to an array reduceAsync(toArray()), ) console.log(await slothSquadStatementsPromise) //=> [ //=> 'strawberry, max, bitsy, and tommy jolly!', //=> 'penny, millie, coco, and polly are kindhearted!', //=> 'betty, bubbles, pearl, and sid are stylish!', //=> 'ava, brooke, lottie, and jeremiah are beloved!' //=> ] ``` -------------------------------- ### Curried vs. Uncurried Function Example Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/currying.mdx Demonstrates the difference between a standard function and its curried equivalent. The standard function requires all arguments at once, while the curried version allows partial application, returning new functions for subsequent arguments. ```javascript const sum = (a, b, c) => a + b + c; // Curried version (conceptual, not actual implementation) // const curriedSum = (a) => (b) => (c) => a + b + c; // Example calls: // sum(1, 2, 3) // curriedSum(1)(2)(3) // curriedSum(1, 2)(3) // This syntax is not directly supported by the simple sum function // curriedSum(1)(2, 3) // This syntax is not directly supported by the simple sum function ``` -------------------------------- ### Process Sync Iterables with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/getting-started.mdx Demonstrates processing synchronous iterables using lfi's map, filter, and reduce functions. It transforms an array of sloth diary entries by cleaning and filtering them. ```js import { filter, map, pipe, reduce, toArray } from 'lfi' const messySlothDiaryEntries = [ [`Strawberry`, `slept`], [`max`, `ate `], [`max`, ``], [`STRAWBERRY`, `climbed`], [`Bitsy`, `ate`], [`bitsy`, `strolled`], [`strawberry`, `Slept`], [`Bitsy`, ` `], ] const cleanSlothDiaryEntries = // Pass the entries through each operation in order, and then return the last // operation's result pipe( messySlothDiaryEntries, // Transform each entry by trimming and lowercasing strings map(([sloth, activity]) => [ sloth.toLowerCase(), activity.trim().toLowerCase(), ]), // Remove each entry that has an empty activity filter(([, activity]) => activity.length > 0), // Collect the clean entries into an array reduce(toArray()), ) console.log(cleanSlothDiaryEntries) //=> [ //=> [ 'strawbery', 'slept' ], //=> [ 'max', 'ate' ], //=> [ 'strawbery', 'climbed' ], //=> [ 'bitsy', 'ate' ], //=> [ 'bitsy', 'strolled' ], //=> [ 'strawbery', 'slept' ] //=> ] ``` -------------------------------- ### Reduce Iterable to Multiple Results with toMultiple (Async) Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/recipes.mdx Illustrates using `toMultiple` with asynchronous iterables. This example fetches parts of speech for a list of words and then reduces these results into a Set, a count, and a joined string. It demonstrates the integration of asynchronous operations with the multi-result reduction capability. ```javascript import { asAsync, flatMapAsync, pipe, reduceAsync, toCount, toJoin, toMultiple, toSet, } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` const getPartsOfSpeech = async word => { const response = await fetch(`${API_URL}/${word}`) const [{ meanings }] = await response.json() return meanings.map(meaning => meaning.partOfSpeech) } console.log( await pipe( asAsync([`sloth`, `lazy`, `sleep`]), flatMapAsync(getPartsOfSpeech), reduceAsync(toMultiple([toSet(), toCount(), toJoin(`,`)])), ), ) //=> [ //=> Set(3) { 'noun', 'verb', 'adjective' }, //=> 6, //=> 'noun,verb,noun,verb,adjective,verb' //=> ] console.log( await pipe( asAsync([`sloth`, `lazy`, `sleep`]), flatMapAsync(getPartsOfSpeech), reduceAsync( toMultiple({ set: toSet(), count: toCount(), string: toJoin(`,`), }), ), ), ) //=> { //=> set: Set(3) { 'noun', 'verb', 'adjective' }, //=> count: 6, //=> string: 'noun,verb,noun,verb,adjective,verb' //=> } ``` -------------------------------- ### Process Async Iterable Concurrently with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/getting-started.mdx This JavaScript code snippet demonstrates how to process an async iterable concurrently using the lfi library. It converts an async iterable of sloth names into a concurrent iterable, chunks them into squads, maps each squad to a descriptive statement by querying an API concurrently, and finally collects all statements into an array. Dependencies include the 'lfi' library. Inputs are an async iterable derived from a file, and an external API. Outputs are an array of descriptive statements. ```javascript import { asConcur, chunkAsync, flatMapAsync, mapConcur, pipe, reduceConcur, toArray, } from 'lfi' // The file has one sloth name per line const FILENAME = `every-sloth-name.txt` const API_URL = `https://random-word-form.herokuapp.com/random/adjective` const slothSquadStatementsPromise = pipe( // Create an async iterable over the file lines, in this case sloth names (await fetch(new URL(`https://lfi.dev/${FILENAME}`))).body, flatMapAsync(chunk => new TextDecoder().decode(chunk).split(`\n`)), // Chunk the sloth names into array groups of 4, also known as a sloth squad chunkAsync(4), // Convert the async iterable into a concur iterable. Every subsequent // operation will process the sloth squads concurrently without waiting on // other squads asConcur, // Transform each sloth squad into a statement by asynchronously querying an // API for an adjective to describe the squad, all concurrently with other // squads mapConcur(async slothSquad => { const [adjective] = await (await fetch(API_URL)).json() const leadingSloths = slothSquad.slice(0, 3) const trailingSloth = slothSquad.at(-1) return `${leadingSloths.join(`, `)}, and ${trailingSloth} are ${adjective}` }), // Collect the statements into a promise that resolves to an array. Note that // the concurrency means that the order of statements in the array may not // match the order of the sloths in the file reduceConcur(toArray()), ) console.log(await slothSquadStatementsPromise) // NOTE: This order may change between runs //=> [ //=> 'strawberry, max, bitsy, and tommy jolly!', //=> 'ava, brooke, lottie, and jeremiah are beloved!', //=> 'betty, bubbles, pearl, and sid are stylish!', //=> 'penny, millie, coco, and polly are kindhearted!' //=> ] ``` -------------------------------- ### Using Pipe with Curried Functions in lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/currying.mdx Illustrates how lfi's `pipe` function leverages curried functions to create a readable data processing pipeline. This example filters animals, maps them to age-name pairs, and groups them by age, showcasing a functional approach to data transformation. ```javascript import { filter, flatMap, map, pipe, reduce, toGrouped, toMap, toSet, } from 'lfi' import zoo from 'lfi:zoo' const getSlothNamesByAgeWithCurrying = () => pipe( zoo.exhibits, flatMap(exhibit => exhibit.animals), filter(animal => animal.species === `sloth`), map(sloth => [sloth.age, sloth.name]), reduce(toGrouped(toSet(), toMap())), ) console.log(getSlothNamesByAgeWithCurrying()) //=> Map(3) { //=> 7 => Set(2) { 'strawberry', 'bitsy' }, //=> 19 => Set(1) { 'max' }, //=> 24 => Set(1) { 'tommy' } //=> } ``` -------------------------------- ### Reduce Iterable to Multiple Results with toMultiple (Sync) Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/recipes.mdx Shows how to use the `toMultiple` reducer to collect an iterable into several different result types simultaneously. This example demonstrates reducing an iterable of word lengths into a Set, a count, and a comma-separated string in a single pass. It highlights the flexibility of `toMultiple` with both array and object configurations. ```javascript import { map, pipe, reduce, toCount, toJoin, toMultiple, toSet } from 'lfi' console.log( pipe( [`sloth`, `lazy`, `sleep`], map(word => word.length), reduce(toMultiple([toSet(), toCount(), toJoin(`,`)])), ), ) //=> [ //=> Set(2) { 5, 4 }, //=> 3, //=> '5,4,5' //=> ] console.log( pipe( [`sloth`, `lazy`, `sleep`], map(word => word.length), reduce( toMultiple({ set: toSet(), count: toCount(), string: toJoin(`,`), }), ), ), ) //=> { //=> set: Set(2) { 5, 4 }, //=> count: 3, //=> string: '5,4,5' //=> } ``` -------------------------------- ### Asynchronous OptionalReducer with `add` and `finish` functions Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Illustrates the asynchronous OptionalReducer (`reduceAsync`) which supports async `add` and `finish` functions for processing async iterables. It includes examples for calculating sums and generating messages from async data streams. ```javascript import { asAsync, mapAsync, orAsync, pipe, reduceAsync } from 'lfi' const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) const sumSquares = async numbers => { console.time(`sumSquares`) const sum = await pipe( // This is an async iterable, but async reducers also work with concur iterables asAsync(numbers), mapAsync(number => number * number), // This behaves identically to passing an async function reducer reduceAsync({ add: async (number1, number2) => { console.log(`add(${number1}, ${number2})`) await delay(100) return number1 + number2 }, }), orAsync(() => 0), ) console.timeEnd(`sumSquares`) return sum } console.log(await sumSquares([1, 2, 3])) //=> add(4, 1) //=> add(5, 9) // NOTE: This duration may vary slightly between runs //=> sumSquares: 200 ms //=> 14 console.log(await sumSquares([])) // NOTE: This duration may vary slightly between runs //=> sumSquares: 0 ms //=> 0 const sumSquaresMessage = async numbers => { console.time(`sumSquaresMessage`) const message = await pipe( // This is an async iterable, but async reducers also work with concur iterables asAsync(numbers), mapAsync(number => number * number), // This behaves identically to passing an async function reducer, except the // final value is transformed using `finish` reduceAsync({ add: async (number1, number2) => { console.log(`add(${number1}, ${number2})`) await delay(100) return number1 + number2 }, finish: async sum => { console.log(`finish(${sum})`) await delay(1000) return `The sum of the squares is ${sum}` }, }), orAsync(() => `There were no numbers!`), ) console.timeEnd(`sumSquaresMessage`) return message } console.log(await sumSquaresMessage([1, 2, 3])) //=> add(4, 1) //=> add(5, 9) //=> finish(14) // NOTE: This duration may vary slightly between runs //=> sumSquaresMessage: 1200 ms //=> The sum of the squares is 14 console.log(await sumSquaresMessage([])) // NOTE: This duration may vary slightly between runs //=> sumSquaresMessage: 0 ms //=> There were no numbers! ``` -------------------------------- ### Reduce Iterable to Multiple Results with toMultiple (Concurrent) Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/recipes.mdx Presents an example of using `toMultiple` with concurrently processed asynchronous iterables. It fetches parts of speech for words concurrently and then reduces these results into a Set, a count, and a joined string. This showcases efficient parallel processing combined with multi-result aggregation. ```javascript import { asConcur, mapConcur, pipe, reduceConcur, toCount, toJoin, toMultiple, toSet, } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` const getPartsOfSpeech = async word => { const response = await fetch(`${API_URL}/${word}`) const [{ meanings }] = await response.json() return meanings.map(meaning => meaning.partOfSpeech) } console.log( await pipe( asConcur([`sloth`, `lazy`, `sleep`]), mapConcur(getPartsOfSpeech), reduceConcur(toMultiple([toSet(), toCount(), toJoin(`,`)])), ), ) // NOTE: This order may change between runs //=> [ //=> Set(3) { 'noun', 'verb', 'adjective' }, //=> 6, //=> 'noun,verb,noun,verb,adjective,verb' //=> ] console.log( await pipe( asConcur([`sloth`, `lazy`, `sleep`]), mapConcur(getPartsOfSpeech), reduceConcur( toMultiple({ set: toSet(), count: toCount(), string: toJoin(`,`), }), ), ), ) // NOTE: This order may change between runs //=> { //=> set: Set(3) { 'noun', 'verb', 'adjective' }, //=> count: 6, //=> string: 'noun,verb,noun,verb,adjective,verb' //=> } ``` -------------------------------- ### First Concurrent Result with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/recipes.mdx Demonstrates using `firstConcur` to get the result of the first promise that resolves, serving as an alternative to `p-race` and `p-any`. It maps words to their phonetic representations and returns the first one found. ```javascript import { asConcur, firstConcur, mapConcur, orConcur, pipe } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` console.log( await pipe( asConcur([`sloth`, `lazy`, `sleep`]), mapConcur(async word => { const response = await fetch(`${API_URL}/${word}`) return (await response.json())[0].phonetic }), firstConcur, orConcur(() => `not found!`), ), ) // NOTE: This word may change between runs //=> /ˈleɪzi/ ``` -------------------------------- ### Async Optional Operations with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/optional.mdx This example showcases asynchronous operations on lfi optionals using async iterables. It fetches data from an API to find a word with a specific part of speech, transforms it to uppercase, and provides an async default value. It requires the 'lfi' library for asAsync, findAsync, mapAsync, orAsync, and pipe, along with the fetch API. ```javascript import { asAsync, findAsync, mapAsync, orAsync, pipe } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` const findWordWithPartOfSpeech = partOfSpeech => pipe( asAsync([`sloth`, `lazy`, `sleep`]), findAsync(async word => { const response = await fetch(`${API_URL}/${word}`) const [{ meanings }] = await response.json() return meanings.some(meaning => meaning.partOfSpeech === partOfSpeech) }), // This works on the async optional because it's just an async iterable mapAsync(word => word.toUpperCase()), orAsync(() => `no ${partOfSpeech}???`), ) console.log(await findWordWithPartOfSpeech(`noun`)) //=> SLOTH console.log(await findWordWithPartOfSpeech(`verb`)) //=> SLOTH console.log(await findWordWithPartOfSpeech(`adjective`)) //=> LAZY console.log(await findWordWithPartOfSpeech(`adverb`)) //=> no adverb??? ``` -------------------------------- ### Manually Filtering and Mapping Concur Iterables Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/concurrent-iterable.mdx Shows how to manually filter and map a concurrent iterable. This example fetches random adjectives and combines them with verbs, demonstrating asynchronous operations within the concurrent processing. ```javascript import { asConcur, concurIteratorSymbol } from 'lfi' const API_URL = `https://random-word-form.herokuapp.com/random/adjective` const concurIterable = asConcur([`sloth`, `lazy`, `sleep`]) const transformedConcurIterable = { [concurIteratorSymbol]: apply => concurIterable[concurIteratorSymbol](async verb => { const [adjective] = await (await fetch(API_URL)).json() if (adjective.length <= 5) { // Too short! return } await apply(`${adjective} ${verb}`) }), } await transformedConcurIterable[concurIteratorSymbol](console.log) // NOTE: This order may change between runs //=> educated sloth //=> favorite sleep ``` -------------------------------- ### Generate values from a seed in JavaScript Source: https://context7.com/tomeraberbach/lfi/llms.txt Generates an infinite iterable by applying a function to a seed value to produce subsequent values. This is useful for creating sequences based on a starting point and a transformation rule. Requires the 'lfi' library. ```javascript import { generate, pipe, reduce, take, toArray } from 'lfi' console.log( pipe( 1, generate(n => n * 2), take(5), reduce(toArray()), ), ) //=> [ 1, 2, 4, 8, 16 ] ``` -------------------------------- ### Sync Reducer Value Combination in lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Demonstrates how sync reducers combine values in iteration order. The first value becomes the accumulator, and subsequent values are combined sequentially. A final transformation can be applied. ```javascript // Assume this gets the next value in the iterable const next = () => { /* ... */ } // The first value in the iterable becomes the accumulator let acc = next() // Each subsequent value in the iterable combines with the current accumulator // to become the new accumulator acc = add(acc, next()) acc = add(acc, next()) // ... // Then the accumulator goes through a final transformation, if applicable acc = finish(acc) ``` -------------------------------- ### Extract Value from Optional with Fallback or Throw using lfi Source: https://context7.com/tomeraberbach/lfi/llms.txt Extracts a value from an optional iterable, providing a fallback if the iterable is empty or throwing an error if `get` is used on an empty iterable. Uses `find`, `or`, and `get` from the 'lfi' library. ```javascript import { find, get, or, pipe } from 'lfi' // With fallback console.log( pipe( [`sloth`, `lazy`], find(word => word.startsWith(`z`)), or(() => `not found`), ), ) //=> not found // Throws if empty console.log(get([`only value`])) //=> only value ``` -------------------------------- ### Async Reducer Value Combination in lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Illustrates how async reducers combine values in iteration order, similar to sync reducers. It involves awaiting the next value and then combining it with the accumulator. ```javascript // Assume this gets the next value in the async iterable const next = async () => { /* ... */ } // The first value in the async iterable becomes the accumulator let acc = await next() // Each subsequent value in the async iterable combines with the current // accumulator to become the new accumulator acc = add(acc, await next()) acc = add(acc, await next()) // ... // Then the accumulator goes through a final transformation, if applicable acc = finish(acc) ``` -------------------------------- ### Get the first or last value from an iterable in JavaScript Source: https://context7.com/tomeraberbach/lfi/llms.txt Retrieves the first or last element of an iterable, returning an optional value. If the iterable is empty, a default value can be provided using `or`. Requires the 'lfi' library. ```javascript import { first, last, or, pipe } from 'lfi' console.log(pipe([`sloth`, `lazy`, `sleep`], first, or(() => `empty`))) //=> sloth console.log(pipe([`sloth`, `lazy`, `sleep`], last, or(() => `empty`))) //=> sleep ``` -------------------------------- ### Optimized Async Reducer Value Combination in lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Shows an optimized approach for async reducers where async operations (like awaiting `add`) are not sequentially awaited, maximizing concurrent work. This prevents unnecessary waiting and improves performance. ```javascript // Assume this gets the next value in the async iterable const next = async () => { /* ... */ } let acc = await next() // What if awaiting this `add` call takes a long time? 🤔 acc = await add(acc, await next()) acc = await add( acc, // We're unnecessarily waiting to kick off this async work! await next(), ) // ... acc = await finish(acc) ``` -------------------------------- ### Find Min/Max by Custom Comparison with lfi Source: https://context7.com/tomeraberbach/lfi/llms.txt Finds the minimum or maximum element in an array based on a custom comparison function. It uses `pipe` to chain operations and `minBy`/`maxBy` for comparison, with `get` to extract the final value. Requires the 'lfi' library. ```javascript import { get, maxBy, minBy, pipe } from 'lfi' console.log( pipe( [`eating`, `sleeping`, `yawning`], minBy((a, b) => a.length - b.length), get, ), ) //=> eating console.log( pipe( [`eating`, `sleeping`, `yawning`], maxBy((a, b) => a.length - b.length), get, ), ) //=> sleeping ``` -------------------------------- ### Comparing lfi with p-map and p-filter (Independent Item Processing) Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/concurrent-iterable.mdx Illustrates the performance difference between lfi and p-map/p-filter by simulating delays. lfi processes items independently, leading to faster execution compared to p-map/p-filter which can be bottlenecked by sequential processing. ```javascript import { asConcur, filterConcur, mapConcur, pipe, reduceConcur, toArray, } from 'lfi' import pFilter from 'p-filter' import pMap from 'p-map' // Hypothetical delays for each item const mapDelays = [5, 1, 1] const filterDelays = [1, 1, 5] const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) const mapFn = i => delay(mapDelays[i] * 1000).then(() => i) const filterFn = i => delay(filterDelays[i] * 1000).then(() => true) // Takes 6 seconds! Each item flows through the operations independently of other items console.time(`with lfi`) const withLfi = await pipe( asConcur([0, 1, 2]), mapConcur(mapFn), filterConcur(filterFn), reduceConcur(toArray()), ) console.timeEnd(`with lfi`) // NOTE: This duration may vary slightly between runs //=> with lfi: 6000 ms // Takes 10 seconds! The first item is a bottleneck because `p-map` waits for all callbacks console.time(`without lfi`) const withoutLfi = await pFilter(await pMap([0, 1, 2], mapFn), filterFn) console.timeEnd(`without lfi`) // NOTE: This duration may vary slightly between runs //=> without lfi: 10000 ms ``` -------------------------------- ### Concurrent data fetching and limiting results with takeConcur Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/recipes.mdx Demonstrates fetching data concurrently from an API and limiting the number of results using `takeConcur`. This pattern is useful for scenarios where you need a subset of results from multiple asynchronous operations. It requires the `lfi` library and `fetch` API. ```javascript import { asConcur, mapConcur, orConcur, pipe, reduceConcur, takeConcur, toArray, } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` console.log( await pipe( asConcur([`sloth`, `lazy`, `sleep`]), mapConcur(async word => { const response = await fetch(`${API_URL}/${word}`) return (await response.json())[0].phonetic }), takeConcur(2), reduceConcur(toArray()), ), ) // NOTE: This word may change between runs //=> [ '/ˈleɪzi/', '/slɑθ/' ] ``` -------------------------------- ### Limit Concurrency of Async Iterables with limit-concur Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/recipes.mdx Demonstrates how to limit the number of concurrent asynchronous operations when processing an iterable. It uses the `limitConcur` utility to cap the number of simultaneous API requests, preventing overload and managing resource usage. The example shows tracking pending requests to visualize the concurrency limit. ```javascript import { asConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi' import limitConcur from 'limit-concur' const API_URL = `https://random-word-form.herokuapp.com/random/adjective` let pendingRequests = 0 console.log( await pipe( asConcur([`strawberry`, `max`, `bitsy`, `tommy`]), mapConcur( // At most 2 requests at a time limitConcur(2, async sloth => { console.log(++pendingRequests) const [adjective] = await (await fetch(API_URL)).json() console.log(--pendingRequests) return `${adjective} ${sloth}` }), ), reduceConcur(toArray()), ), ) //=> 1 //=> 2 //=> 1 //=> 2 //=> 1 //=> 2 //=> 1 //=> 0 // NOTE: This order may change between runs //=> [ //=> 'kind strawberry', //=> 'humble max', //=> 'great bitsy', //=> 'beautiful tommy' //=> ] ``` -------------------------------- ### Synchronous Reduce with Custom Reducer in JavaScript Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Demonstrates how to use the synchronous 'reduce' function in LFI with a custom reducer object. This reducer defines 'create', 'add', and optionally 'finish' functions to aggregate values from an iterable. The accumulator type can differ from the iterable's value type. ```javascript import { map, pipe, reduce } from 'lfi' const sumSquares = numbers => pipe( numbers, map(number => number * number), reduce( { create: () => { console.log(`create()`) return 0 }, add: (sum, number) => { console.log(`add(${sum}, ${number})`) return sum + number }, }, ), ) console.log(sumSquares([1, 2, 3])) console.log(sumSquares([])) const meanSquares = numbers => pipe( numbers, map(number => number * number), reduce( { create: () => { console.log(`create()`) return { sum: 0, count: 0 } }, add: ({ sum, count }, number) => { console.log(`add({ sum: ${sum}, count: ${count} }, ${number})`) return { sum: sum + number, count: count + 1 } }, finish: ({ sum, count }) => { console.log(`finish({ sum: ${sum}, count: ${count} })`) return count === 0 ? NaN : sum / count }, }, ), ) console.log(meanSquares([1, 2, 3])) console.log(meanSquares([])) ``` -------------------------------- ### Synchronous OptionalReducer with `add` and `finish` functions Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx Demonstrates the synchronous OptionalReducer which accepts an `add` function for aggregation and an optional `finish` function to transform the final result. It handles both populated and empty iterables, providing default values via `or`. ```javascript import { map, or, pipe, reduce } from 'lfi' const sumSquares = numbers => pipe( numbers, map(number => number * number), // This behaves identically to passing a function reducer reduce({ add: (number1, number2) => { console.log(`add(${number1}, ${number2})`) return number1 + number2 }, }), or(() => 0), ) console.log(sumSquares([1, 2, 3])) //=> add(1, 4) //=> add(5, 9) //=> 14 console.log(sumSquares([])) //=> 0 const sumSquaresMessage = numbers => pipe( numbers, map(number => number * number), // This behaves identically to passing a function reducer, except the final // value is transformed using `finish` reduce({ add: (number1, number2) => { console.log(`add(${number1}, ${number2})`) return number1 + number2 }, finish: sum => { console.log(`finish(${sum})`) return `The sum of the squares is ${sum}` }, }), or(() => `There were no numbers!`), ) console.log(sumSquaresMessage([1, 2, 3])) //=> add(1, 4) //=> add(5, 9) //=> finish(14) //=> The sum of the squares is 14 console.log(sumSquaresMessage([])) //=> There were no numbers! ``` -------------------------------- ### Sync Optional Operations with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/optional.mdx This snippet shows how to use lfi's synchronous functions with optionals represented as iterables. It demonstrates finding a word, mapping it to uppercase, and providing a default value if not found. It relies on the 'lfi' library for pipe, find, map, and or functions. ```javascript import { find, map, or, pipe } from 'lfi' const findWordContaining = string => pipe( [`sloth`, `lazy`, `sleep`], find(word => word.includes(string)), // This works on the optional because it's just an iterable map(word => word.toUpperCase()), or(() => `no ${string}???`), ) console.log(findWordContaining(`lot`)) //=> SLOTH console.log(findWordContaining(`la`)) //=> LAZY console.log(findWordContaining(`ep`)) //=> SLEEP console.log(findWordContaining(`lfi`)) //=> no lfi??? ``` -------------------------------- ### toMultiple Reducer for Parallel Reduction (JavaScript) Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/reducer.mdx The `toMultiple` reducer allows for parallel reduction of an iterable using an array or object of reducers. It can be used with synchronous, asynchronous, and concurrent operations. Dependencies include various `lfi` functions like `pipe`, `reduce`, `map`, `toSet`, `toCount`, and `toJoin`. Inputs are iterables, and outputs are either arrays or objects containing the results from each parallel reducer. ```javascript import { map, pipe, reduce, toCount, toJoin, toMultiple, toSet } from 'lfi' console.log( pipe( [`sloth`, `lazy`, `sleep`], map(word => word.length), reduce(toMultiple([toSet(), toCount(), toJoin(',')])), ), ) //=> [ //=> Set(2) { 5, 4 }, //=> 3, //=> '5,4,5' //=> ] console.log( pipe( [`sloth`, `lazy`, `sleep`], map(word => word.length), reduce( toMultiple({ set: toSet(), count: toCount(), string: toJoin(','), }), ), ), ) //=> { //=> set: Set(2) { 5, 4 }, //=> count: 3, //=> string: '5,4,5' //=> } ``` ```javascript import { asAsync, flatMapAsync, pipe, reduceAsync, toCount, toJoin, toMultiple, toSet, } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` const getPartsOfSpeech = async word => { const response = await fetch(`${API_URL}/${word}`) const [{ meanings }] = await response.json() return meanings.map(meaning => meaning.partOfSpeech) } console.log( await pipe( asAsync([`sloth`, `lazy`, `sleep`]), flatMapAsync(getPartsOfSpeech), reduceAsync(toMultiple([toSet(), toCount(), toJoin(',')])), ), ) //=> [ //=> Set(3) { 'noun', 'verb', 'adjective' }, //=> 6, //=> 'noun,verb,noun,verb,adjective,verb' //=> ] console.log( await pipe( asAsync([`sloth`, `lazy`, `sleep`]), flatMapAsync(getPartsOfSpeech), reduceAsync( toMultiple({ set: toSet(), count: toCount(), string: toJoin(','), }), ), ), ) //=> { //=> set: Set(3) { 'noun', 'verb', 'adjective' }, //=> count: 6, //=> string: 'noun,verb,noun,verb,adjective,verb' //=> } ``` ```javascript import { asConcur, mapConcur, pipe, reduceConcur, toCount, toJoin, toMultiple, toSet, } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` const getPartsOfSpeech = async word => { const response = await fetch(`${API_URL}/${word}`) const [{ meanings }] = await response.json() return meanings.map(meaning => meaning.partOfSpeech) } console.log( await pipe( asConcur([`sloth`, `lazy`, `sleep`]), mapConcur(getPartsOfSpeech), reduceConcur(toMultiple([toSet(), toCount(), toJoin(',')])), ), ) // NOTE: This order may change between runs //=> [ //=> Set(3) { 'noun', 'verb', 'adjective' }, //=> 6, //=> 'noun,verb,noun,verb,adjective,verb' //=> ] console.log( await pipe( asConcur([`sloth`, `lazy`, `sleep`]), mapConcur(getPartsOfSpeech), reduceConcur( toMultiple({ set: toSet(), count: toCount(), string: toJoin(','), }), ), ), ) // NOTE: This order may change between runs //=> { //=> set: Set(3) { 'noun', 'verb', 'adjective' }, //=> count: 6, //=> string: 'noun,verb,noun,verb,adjective,verb' //=> } ``` -------------------------------- ### Synchronous Optional Operations with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/optional.mdx Demonstrates using `find` and `or` with `pipe` for synchronous optional value retrieval. It finds a word containing a specific string and provides a default if none is found. This is useful for simple, non-blocking operations. ```javascript import { find, or, pipe } from 'lfi' const findWordContaining = string => pipe( [`sloth`, `lazy`, `sleep`], // Return an optional potentially containing the found word find(word => word.includes(string)), // Return the optional's value if it exists, or the result of the callback otherwise or(() => `no ${string}???`), ) console.log(findWordContaining(`lot`)) //=> sloth console.log(findWordContaining(`la`)) //=> lazy console.log(findWordContaining(`ep`)) //=> sleep console.log(findWordContaining(`lfi`)) //=> no lfi??? ``` -------------------------------- ### Alternative: Uncurried Function Chaining (Less Readable) Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/currying.mdx Presents an alternative implementation without using `pipe` and currying, demonstrating a less readable nested function call structure. This approach highlights the benefits of currying and `pipe` for code clarity and maintainability. ```javascript import { filter, flatMap, map, pipe, reduce, toGrouped, toMap, toSet, } from 'lfi' import zoo from 'lfi:zoo' const getSlothNamesByAgeWithoutCurrying = () => reduce( toGrouped(toSet(), toMap()), map( sloth => [sloth.age, sloth.name], filter( animal => animal.species === `sloth`, flatMap(exhibit => exhibit.animals, zoo.exhibits), ), ), ) console.log(getSlothNamesByAgeWithoutCurrying()) //=> Map(3) { //=> 7 => Set(2) { 'strawberry', 'bitsy' }, //=> 19 => Set(1) { 'max' }, //=> 24 => Set(1) { 'tommy' } //=> } ``` -------------------------------- ### Asynchronous Optional Operations with lfi Source: https://github.com/tomeraberbach/lfi/blob/main/website/docs/concepts/optional.mdx Illustrates asynchronous optional operations using `findAsync` and `orAsync` with `pipe`. It searches for a word with a specific part of speech by making API calls and provides a fallback value if no matching word is found. This is suitable for I/O-bound tasks. ```javascript import { asAsync, findAsync, orAsync, pipe } from 'lfi' const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en` const findWordWithPartOfSpeech = partOfSpeech => pipe( asAsync([`sloth`, `lazy`, `sleep`]), // Return an async optional potentially containing the found word findAsync(async word => { const response = await fetch(`${API_URL}/${word}`) const [{ meanings }] = await response.json() return meanings.some(meaning => meaning.partOfSpeech === partOfSpeech) }), // Return a promise that resolves to the async optional's value if it // exists, or the result of the callback otherwise orAsync(() => `no ${partOfSpeech}???`), ) console.log(await findWordWithPartOfSpeech(`noun`)) //=> sloth console.log(await findWordWithPartOfSpeech(`verb`)) //=> sloth console.log(await findWordWithPartOfSpeech(`adjective`)) //=> lazy console.log(await findWordWithPartOfSpeech(`adverb`)) //=> no adverb??? ```