LFI API Documentation

website·Indexed 31 days ago

https://lfi.dev

API reference for LFI, featuring a comprehensive set of utility functions for data processing, statistics, and iterable manipulation. Includes support for synchronous, asynchronous, and concurrent operations with functions such as reduce, sum, minMax, and various specialized reducers across versions v0.0.1 to v3.5.0.

Tokens
64.5K
Snippets
340
Records
452
Agent score
54%

What's inside LFI

  1. Overview of Reducers in lfi

    A reducer defines how to combine a lazy collection of values into a single final value, either synchronously or asynchronously. The in-progress final value during this process is called the "accumulator".

    In the lfi library, functions starting with to (e.g., toArray, toSum) are typically reducers. Sync reducers can be used with reduce, reduceAsync, and reduceConcur, while async reducers are used with reduceAsync and reduceConcur.

  2. Reduce an iterable using a reducer

    The reduce function processes an Iterable<Value> using a reducer object. It creates an initial accumulator via RawReducerWithoutFinish.create, updates it for each item in the iterable using RawReducerWithoutFinish.add, and optionally transforms the final result using RawReducerWithFinish.finish.

    If the reducer is an 'optional reducer' (lacks a create method):

    • If the iterable is empty, it returns an empty iterable (or Optional type).
    • If the iterable is not empty, it uses the first value as the initial accumulator and reduces the rest.
  3. Understand Optionals in lfi

    In lfi, an optional is a container of zero or one value, represented as an iterable. This allows for type-safe management of values that may be absent or fail to be produced. Because optionals are simply iterables, they can be lazy and compatible with any function that expects a regular iterable.

    Depending on the required execution context, lfi uses the following type aliases:

    • Optional: Alias for Iterable (Synchronous)
    • AsyncOptional: Alias for AsyncIterable (Asynchronous)
    • ConcurOptional: Alias for ConcurIterable (Concurrent)

    Important Constraint: All lfi functions intended for optionals treat iterables containing two or more values as empty optionals.

  4. Understand currying in lfi

    Currying is a technique where a function is transformed so it can be called with a prefix of its parameters. Instead of returning the final result immediately, it returns a new function that accepts the remaining parameters. In lfi, functions are automatically curried where applicable, allowing developers to create specialized functions by providing only some of the required arguments.
    // Standard function
    const sum = (a, b, c) => a + b + c;
    
    // With currying, the same logic can be invoked as:
    sum(1, 2, 3);
    sum(1, 2)(3);
    sum(1)(2, 3);
    sum(1)(2)(3);
  5. Understand ConcurIterable concepts

    A concurrent iterable (or "concur iterable") is a lazy, push-based collection of values that can be iterated concurrently. It is represented by the ConcurIterable type.

    Key characteristics:

    • Lazy Execution: It does nothing until it is called.
    • Push-based: It pushes values to a provided callback as fast as they are produced, rather than waiting for the caller to pull them.
    • Asynchronous: It returns a promise that resolves when all values have been handled or rejects if an error occurs.
    • Non-deterministic Order: Because values are processed concurrently, the order of emission depends on the speed of the asynchronous operations.
  6. Use flatMap to map and flatten iterables

    The flatMap operation returns an iterable containing the values of the iterables returned from applying a mapping function fn to each value of the input iterable in iteration order. It behaves similarly to Array.prototype.flatMap but is designed to work with all iterables.
    import { flatMap, pipe, reduce, toArray } from 'lfi'
    
    console.log(
      pipe(
        [`sloth`, `lazy`, `sleep`],
        flatMap(word => [word, `much ${word}`]),
        reduce(toArray()),
      ),
    )
    //=> [
    //=>   'sloth',
    //=>   'much sloth',
    //=>   'lazy',
    //=>   'much lazy',
    //=>   'sleep',
    //=>   'much sleep'
    //=> ]
  7. Use curried functions with pipe for readable data processing

    In lfi, curried functions are designed to work with the pipe function. pipe takes an initial value (usually an iterable) and passes it through a sequence of functions. Because lfi functions are curried, you can provide the configuration arguments (like a predicate or mapping function) first, creating a function that expects the data as its final argument. This allows the operation order to match the reading order and ensures the code remains tree-shakable, unlike traditional method chaining.
    import {
      filter,
      flatMap,
      map,
      pipe,
      reduce,
      toGrouped,
      toMap,
      toSet,
    } from 'lfi'
    import zoo from 'lfi:zoo'
    
    const getSlothNamesByAgeWithCurrying = () =>
      pipe(
        zoo.exhibits, // Initial iterable
        flatMap(exhibit => exhibit.animals), // Curried: accepts iterable next
        filter(animal => animal.species === `sloth`), // Curried: accepts iterable next
        map(sloth => [sloth.age, sloth.name]), // Curried: accepts iterable next
        reduce(toGrouped(toSet(), toMap())), // Curried: accepts iterable next
      )
    
    console.log(getSlothNamesByAgeWithCurrying())
  8. Limit concurrency of a concur iterable

    To limit the number of simultaneous asynchronous operations in a mapConcur or similar concurrent iterable, wrap the async callback with the limit-concur package.
    import { asConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi'
    import limitConcur from 'limit-concur'
    
    const API_URL = `https://random-word-form.herokuapp.com/random/adjective`
    
    await pipe(
      asConcur([`strawberry`, `max`, `bitsy`, `tommy`]),
      mapConcur(
        // Limit to at most 2 requests at a time
        limitConcur(2, async sloth => {
          const [adjective] = await (await fetch(API_URL)).json()
          return `${adjective} ${sloth}`
        }),
      ),
      reduceConcur(toArray()),
    )
    
  9. Optimize asynchronous pipelines with ConcurIterables

    Using lfi concurrent iterables provides two primary advantages over chaining utilities like p-map or p-filter:

    1. Memory Efficiency: They are lazy and do not create intermediate arrays between operations.
    2. Pipeline Throughput: Items flow through the pipeline independently. An item can move to the next stage (e.g., from mapConcur to filterConcur) as soon as its own operation is complete, without waiting for other items in the batch to finish the current stage.
    import { asConcur, filterConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi'
    
    // Each item flows through mapConcur and filterConcur independently
    const result = await pipe(
      asConcur([0, 1, 2]),
      mapConcur(mapFn),
      filterConcur(filterFn),
      reduceConcur(toArray()),
    )
  10. Use a FunctionReducer to combine values

    A FunctionReducer (or AsyncFunctionReducer) is the simplest reducer. It defines how to combine two values of the same type into a single value of that same type. Because it does not define a behavior for empty iterables, reduce returns an optional value when a function reducer is used.
    import { map, or, pipe, reduce } from 'lfi'
    
    const sumSquares = numbers =>
      pipe(
        numbers,
        map(number => number * number),
        // Combine pairs of values until only one remains
        reduce((number1, number2) => {
          return number1 + number2
        }),
        // Provide a default value for empty iterables
        or(() => 0),
      )
    
    console.log(sumSquares([1, 2, 3])) // => 14
    console.log(sumSquares([]))        // => 0
  11. Compose multiple functions using `compose`

    The compose function creates a function pipeline where the output of each function is passed as the input to the next. It supports a variable number of functions, including both lfi utility functions and standard JavaScript functions. The resulting function takes an initial value of type A and returns a final value of type J (or the return type of the last function in the chain).
    import { compose, map, reduce, toArray } from 'lfi'
    
    const screamify = compose(
      map(word => word.toUpperCase()),
      reduce(toArray()),
      // Also works with non-`lfi` functions!
      array => array.sort(),
    )
    
    console.log(screamify([`sloth`, `lazy`, `sleep`]))
    //=> [ 'SLOTH', 'LAZY', 'SLEEP' ]
  12. Reduce an async iterable using reduceAsync

    reduceAsync processes an AsyncIterable by applying an asyncReducer to its values. It behaves similarly to Array.prototype.reduce.

    Execution Flow:

    1. An initial accumulator is created via RawAsyncReducerWithoutFinish.create.
    2. Each value from the asyncIterable is added to the accumulator using RawAsyncReducerWithoutFinish.add.
    3. If the reducer supports RawAsyncReducerWithoutFinish.combine, multiple accumulators may be processed and combined if the next value is ready before previous add promises resolve.
    4. If a RawAsyncReducerWithFinish.finish method is specified, the final accumulator is transformed before being returned.

    Special Case (Optional Reducers): If the asyncReducer is an 'async optional reducer' (lacks a .create method):

    • If the asyncIterable is empty, an empty async iterable is returned.
    • Otherwise, the first value of the async iterable is used as the initial accumulator.