es-toolkit icon indicating copy to clipboard operation
es-toolkit copied to clipboard

Add `fp` export

Open seungrodotlee opened this issue 1 year ago • 13 comments

Add es-toolkit/fp export to support functional programming and pipeline syntax.

// example

pipe(
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  filter(isEven),
  map(double),
  join(", "),
) // Expect: "4, 8, 12, 16, 20"

TODO

Scripts

  • [x] Write a script to clone and convert existing functions into 'pipable' functions for pipelining.
  • [ ] Enhance a script to clone and convert existing specs to fp-version specs.

Codes

  • [ ] Write basic functions for fp (ex. pipe, map, filter ...)
  • [ ] Convert all available existing functions

Documents

  • [ ] Write documents about es-toolkit/fp

seungrodotlee avatar Sep 20 '24 18:09 seungrodotlee

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
es-toolkit ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 22, 2025 9:54am

vercel[bot] avatar Sep 20 '24 18:09 vercel[bot]

any progress on this thread?

D-Sketon avatar Nov 01 '24 16:11 D-Sketon

While I am trying to work as quickly as possible, but it is not a very high priority, so I am falling behind. I hope to be able to work on it this week. 🥲

seungrodotlee avatar Nov 01 '24 16:11 seungrodotlee

The syntax for the pipe function is planned as follows.

function toString(value: unknown) {
  return `string:${value}`;
}

function toStringAsync(value: unknown) {
  return Promise.resolve(`string:${value}`);
}

function length(value: string) {
  return value.length;
}

const result = pipe(1, toString, length);
console.log(result); // 8

// Use pipe with async function
const asyncResult = await pipe(1, toStringAsync, length);
console.log(asyncResult); // 8

// Use pipe with curried function
const mapKeyResult = await pipe(
  { a: 1, b: 2 },
  mapKeys((value, key) => key + value)
);
console.log(mapKeyResult); // { a1: 1, b2: 2 }
  • receive the initial value as first parameter
  • receive functions that process the value from the second parameter onward
  • can handle async functions

seungrodotlee avatar Nov 02 '24 14:11 seungrodotlee

image

bench result of pipe

seungrodotlee avatar Nov 02 '24 14:11 seungrodotlee

As far as I remember, this team was not interested in that work, but it is a work that I am interested in.

But is there a reason why I have to copy the implementation? It also looks good to refer to the original function with curring to take the consistency of the implementation.

const pipeableCountBy = <T>(fn: (arg: T) => boolean) => (arr: Array<T>) => countBy(arr, fn);
pipe(
   [1, 2, 3, 4],
   pipeableCountBy(v => v % 2 == 0)
);

If you're not going to make it in consideration of curring from the beginning, like fxts, that seems to be the best option.

The advantage of p.spipe comes as a delay assessment. That's the linq of dotnet, that's the fxts, that's the effect-ts.

sunrabbit123 avatar Nov 12 '24 07:11 sunrabbit123

Maybe we don't need to copy original implementation even if there is a script for copying it... I agree with that idea.

But it would be better to keep current design that 'auto-currying' the function by number of received argument to avoid situations like the one below.

// BAD
import { omit } from "es-toolkit";
import { omit as pipableOmit } from "es-toolkit/fp";

const omitted = omit(obj, ['a', 'b']); // usage without pipe

// ...

pipe(
  obj,
  pipableOmit(['a', 'b'])
); // usage with pipe
// GOOD
import { omit } from "es-toolkit/fp";

const omitted = omit(obj, ['a', 'b']); // usage without pipe

// ...

pipe(
  obj,
  omit(['a', 'b'])
); // usage with pipe

I gonna fix the implementation and script to refer it, not copy the implementation of original one.

seungrodotlee avatar Nov 17 '24 11:11 seungrodotlee

I think that's a good idea, too. The core of my story is to refer to the feature implementation to follow the original, but only to change the interface.

sunrabbit123 avatar Nov 17 '24 15:11 sunrabbit123

functions list in fp

array

  • at
  • chunk
  • countBy
  • difference
  • differenceBy
  • differenceWith
  • drop
  • dropRight
  • dropRightWhile
  • dropWhile
  • every
  • fill
  • filter
  • find
  • findIndex
  • flatMap
  • flatMapDeep
  • forEach
  • forEachRight
  • groupBy
  • includes
  • intersection
  • intersectionBy
  • intersectionWith
  • isSubset
  • join
  • keyBy
  • map
  • maxBy
  • minBy
  • orderBy
  • partition
  • pullAt
  • sampleSize
  • slice
  • some
  • sort
  • sortBy
  • take
  • takeRight
  • takeRightWhile
  • takeWhile
  • union
  • unionBy
  • unionWith
  • uniq
  • uniqBy
  • uniqWith
  • unzipWith
  • xor
  • xorBy
  • xorWith
  • zipObject

core

  • pipe

object

  • mapKeys
  • mapValues
  • merge
  • mergeWith
  • omit
  • omitBy
  • pick
  • pickBy
  • toMerged

seungrodotlee avatar Nov 19 '24 14:11 seungrodotlee

Are there already plans to review/merge this PR? Thanks

Zomono avatar Feb 27 '25 16:02 Zomono

Are there already plans to review/merge this PR? Thanks

Sorry for late reply.

We're doing some discussions about details and there will be some changes in implementations!

seungrodotlee avatar Mar 07 '25 06:03 seungrodotlee

Will fp package implement structural sharing based on object prototyping like lodash/fp does?

nabato avatar Mar 29 '25 10:03 nabato

I'm planning to close this PR since it was not updated it for a long time.

If you plan to resume work on it, feel free to reopen.

dayongkr avatar Jul 22 '25 14:07 dayongkr

Hi everyone, thanks a lot for your awesome work and dedication. Is there any update regarding this? pipe and /fp?

amrsalama avatar Sep 27 '25 16:09 amrsalama