Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

functions-module #6143

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

guy-borderless
Copy link
Contributor

@guy-borderless guy-borderless commented Oct 24, 2024

Hi,
I'd like to suggest adding an std module for utilities related to functions.

related: #4386

There are common manipulations to functions implemented in numerous npm packages and toolbelts, and using them where appropriate greatly improves code quality. It would be handy to have these in deno_std.
I want to suggest for starters the following two:

  1. pipe
  2. set_arguments (currying / dependency injection)

These two scenarios have some typescript knowledge behind them and it makes sense to reach for a proper utility.

Both of these have many different implementations in terms of code and typings. In my opinion, the implementation most in line with the pragmatic spirit of the std should avoid functional programming jargon for simplicity and accessibility to beginners.

For discussion in this PR I have included two implementations (without tests) that I think would suit the deno_std

pipe has good typescript performance and small implementation. I'm also using it in several projects.

set_arguments has a very non-academic intuitive signature that I think suites nicely with the deno_std. This signature does not expect a data-last coding convention, so it doesn't leak to the rest of the code. I have also provided a more classic functional programming implementation of this called curry to contrast.

I can significantly simplify the typing definition of set_arguments if you'd like me to go ahead with preparing this PR.

Since functions are an important primitive in JS, I'm sure more utilities will be added.

@kt3k
Copy link
Member

kt3k commented Oct 24, 2024

Are pipe and set_arguments common in JS ecosystem? I rarely see them in various code bases.

I personally think inline arrow functions express the operation like set_arguments better.

const myNewFunc = set_arguments(myFunc, 1, undefined, 3);
// vs
const myNewFunc = (x: number) => myFunc(1, x, 3);

@guy-borderless
Copy link
Contributor Author

Are pipe and set_arguments common in JS ecosystem? I rarely see them in various code bases.

I personally think inline arrow functions express the operation like set_arguments better.

const myNewFunc = set_arguments(myFunc, 1, undefined, 3);
// vs
const myNewFunc = (x: number) => myFunc(1, x, 3);

Both curry and pipe are very common and implemented by all lodash style toolbelts (including lodash of course), as well as many specific packages. Your example works because you have the number primitive. In cases of a business object, which you may or may not control, it's much nicer to use something like curry or set_arguments.

pipe is a common pattern, for example in ETL code when you have a lot of linear transformations, it makes the code much more readable, as it prevents spaghetti.

@kt3k
Copy link
Member

kt3k commented Oct 24, 2024

Your example works because you have the number primitive.

I don't understand this. Inline arrow function example should work with any parameters available in that scope. I think that has the same capability as what set_arguments (or curry) can do.

BTW do you suggest curry or set_arguments? or both?

pipe is a common pattern, for example in ETL code when you have a lot of linear transformations, it makes the code much more readable, as it prevents spaghetti.

Can you elaborate on more specific examples where pipe is better for readability? The given example looks too trivial to replace with simple function composition.

const func = pipe(Math.abs, Math.sqrt, Math.floor);
//
const func = (num: number) => Math.floor(Math.sqrt(Math.abs(num)))

@kt3k kt3k added the feedback welcome We want community's feedback on this issue or PR label Oct 24, 2024
@guy-borderless
Copy link
Contributor Author

guy-borderless commented Oct 25, 2024

Your example works because you have the number primitive.

I don't understand this. Inline arrow function example should work with any parameters available in that scope. I think that has the same capability as what set_arguments (or curry) can do.

BTW do you suggest curry or set_arguments? or both?

pipe is a common pattern, for example in ETL code when you have a lot of linear transformations, it makes the code much more readable, as it prevents spaghetti.

Can you elaborate on more specific examples where pipe is better for readability? The given example looks too trivial to replace with simple function composition.

const func = pipe(Math.abs, Math.sqrt, Math.floor);
//
const func = (num: number) => Math.floor(Math.sqrt(Math.abs(num)))

I see set_arguments as just a less functional version of curry, so if we want to be more strict it's either, depending on the policy.

Yes, of course, you can create an arrow function to inject parameters:

const fnWithFirstDeps = (dataArg: Parameters<typeof fn>[2]) => fn(dependency1, dependency2, dataArg)

I think it's a common and rather generic need, so having a utility that obviates the need to create a local function for it and improves readability is handy, while also encouraging a good coding practice (dependency injection). Sometimes different parts of the code inject different dependencies, having a utility encourages doing the injection locally and not creating another function or re-using the above function which entangles concerns.

const fnWithFirstDeps = setArguments(fn, dependency1, dependency2,)

pipe is useful in data processing. You don't want to compose many functions to create a pipe as this creates long bracket scopes and isn't readable. Here is a less trivial example:

const extracted = {
  bedrooms:  pipe(
      selectOne.attributes,
      parseMatchingText("bedrooms"),
      removeLabel,
      parseNumber,
    ),
  bathrooms:  pipe(
      selectOne.attributes,
      h.parseMatchingText("bathrooms"),
      removeLabel,
      parseNumber,
    ),
}

@kt3k
Copy link
Member

kt3k commented Oct 28, 2024

I'm also not a fan of these typings. curry and setArguments seem having limits in number of arguments, while inline arrow function composition doesn't have such limitation. The type of pipe function looks extremely hard to understand. I guess the type error message will be also harder to understand with pipe than simpler function composition.

@kt3k
Copy link
Member

kt3k commented Oct 28, 2024

(BTW while I'm personally not in favor of this, we are open for adding this package if there's enough community support to this idea.)

@guy-borderless
Copy link
Contributor Author

I'm also not a fan of these typings. curry and setArguments seem having limits in number of arguments, while inline arrow function composition doesn't have such limitation. The type of pipe function looks extremely hard to understand. I guess the type error message will be also harder to understand with pipe than simpler function composition.

Typing for a number of arguments is very common and gives a good experience. As far as I can see, it's a theoretical, not a practical issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feedback welcome We want community's feedback on this issue or PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants