-
Notifications
You must be signed in to change notification settings - Fork 633
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
feat(functions): adding a functions module with pipe() #6143
Conversation
Are I personally think inline arrow functions express the operation like const myNewFunc = set_arguments(myFunc, 1, undefined, 3);
// vs
const myNewFunc = (x: number) => myFunc(1, x, 3); |
Both
|
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 BTW do you suggest
Can you elaborate on more specific examples where const func = pipe(Math.abs, Math.sqrt, Math.floor);
//
const func = (num: number) => Math.floor(Math.sqrt(Math.abs(num))) |
I see 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,)
const extracted = {
bedrooms: pipe(
selectOne.attributes,
parseMatchingText("bedrooms"),
removeLabel,
parseNumber,
),
bathrooms: pipe(
selectOne.attributes,
h.parseMatchingText("bathrooms"),
removeLabel,
parseNumber,
),
} |
I'm also not a fan of these typings. |
(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.) |
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. |
This PR and #4386, which has been open since February, have had near-zero community support. Perhaps, it'd be best to have this done as a 3rd party package and see if it evolves enough to provide a stronger argument for its addition sometime in the future. |
Honestly |
@guy-borderless Can you limit this PR to |
Of course! On it. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #6143 +/- ##
=======================================
Coverage 96.35% 96.35%
=======================================
Files 552 554 +2
Lines 41937 41952 +15
Branches 6356 6355 -1
=======================================
+ Hits 40407 40423 +16
+ Misses 1490 1489 -1
Partials 40 40 ☔ View full report in Codecov by Sentry. |
c7df99b
to
3a08d69
Compare
Per feedback, PR was limited to pipe() and tests were added. |
You also got to get all the tests to pass. Looks like the title isn't named right and you're missing the copyright notice in some of the files. |
The PR naming issue seems to be because the functions module is new and so a scope doesn't exist for it. Added the copyright notice, thanks for mentioning it (didn't seem to cause a test to fail) |
bd18d57
to
9fc723a
Compare
I believe a change needs to happen in |
bc0f426
to
df3f242
Compare
d54ec2e
to
e03206b
Compare
cc @andrewthauer Do you find this |
I would note that the |
* @param input The functions to be composed | ||
* @returns A function composed of the input functions, from left to right | ||
*/ | ||
export function pipe(): <T>(arg: T) => T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this first overload? What is this for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is my habit that if an edge case has a natural interpretation I include it by default, even without a concrete use-case. This isn't a strong opinion, happy to remove this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would note that a scenario for this will, of course, involve .apply()
. Again, I don't mind.
Sorry just saw this. I'll pull the PR down and play around to see how well it works in regards to addressing #4386 |
First, I will say, that the behaviour of pipe functions varies across libraries for what they call Some libraries auto curry based on the data or function args or do not curry at all. Others distinguish the difference with something like a Ok, so here are my initial thoughts on the Pipeline typing works pretty well through pipeline const myPipe = pipe(
(num: number) => String(num),
(str) => Number(str),
);
myPipe(3)
^^^ accepts number returns string! Pipeline function arguments are not inferred This is where typing could be improved ... const myPipe = pipe(
(num: number) => String(num),
(str) => Number(str),
^^^ Ideally this would be inferred as a string
); Does not support for multiple arguments in first function Not sure this is a show stopper, but it would be nice to support multiple arguments in the first function. The const myPipe = pipe(
(a: number, b: number) => a + b,
(str) => Number(str),
);
myPipe2(3, 2);
^^^^ expected 1 args but provided 2 In general this is a good start with initial testing, but could have improved typing imo ( |
Just mentioning that function arguments should be inferred in this
implementation. Checking when near a computer
…On Mon, Jan 20, 2025, 07:56 Andrew Thauer ***@***.***> wrote:
First, I will say, that the behaviour of pipe functions varies across
libraries for what they call pipe. So first off I'd like to loosely
classify this version as a data-last curried pipe function that works
left to right.
Some libraries auto curry based on the data or function args or do not
curry at all. Others distinguish the difference with something like a flow
function (e.g. fs-ts's flow
<https://gcanti.github.io/fp-ts/modules/function.ts.html#flow>) vs pipe
<https://gcanti.github.io/fp-ts/modules/function.ts.html#pipe>.
Ok, so here are my initial thoughts on the pipe function in it's current
state:
*Pipeline typing works pretty well through pipeline*
const myPipe = pipe(
(num: number) => String(num),
(str) => Number(str),);
myPipe(3)^^^ accepts number returns string!
*Pipeline function arguments are not inferred*
This is where typing could be improved ...
const myPipe = pipe(
(num: number) => String(num),
(str) => Number(str),
^^^ Ideally this would be inferred as a string);
*Does not support for multiple arguments in first function*
Not sure this is a show stopper, but it would be nice to support multiple
arguments in the first function. The fs-ts flow
<https://gcanti.github.io/fp-ts/modules/function.ts.html#flow> function
supports this which is nice.
const myPipe = pipe(
(a: number, b: number) => a + b,
(str) => Number(str),);
myPipe2(3, 2);
^^^^ expected 1 args but provided 2
In general this is a good start with initial testing, but could have
improved typing imo (fs-ts does a good job here), If I need a quick pipe
and didn't already have another functional library pulled in I might make
use of it. That said, without a builtin curry/purry function it would
likely be a bit verbose to define a pipeline of functions that have
multiple arguments (non unary arity). Although this is perhaps not a fault
of the pipe function itself. However, the lack of a curry function makes it
less unseful from a functional perspective imo.
—
Reply to this email directly, view it on GitHub
<#6143 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AX35W655K5SWMCER4ZVKDTD2LRCVLAVCNFSM6AAAAABQQN6GY6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMMBRGEYTSNZQG4>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Andrew, thanks for your feedback. Initially, I considered requiring all pipe functions to be unary, even though it’s unnecessary for the first one, to simplify reordering functions within the pipe if the need arise. upon reflection and given deno-std’s unopinionated approach, this restriction does feel unnecessary, so I now removed it. I’ve added argument typings for up to 7 pipe functions and can extend it further if needed. While I find value in using this implementation of pipe without currying, I agree that including a curry function would be very useful. It would naturally complement pipe and enhance flexibility in the future. |
@guy-borderless @andrewthauer Do you happen to know the usage of this type of utils (such as ramda, fp-ts, etc) in major OSS projects? |
an example from sqltyper (163 ☆) export function isOperatorCommutative(name: string): boolean {
return pipe(
findOperator(name),
Option.map((op) => op.commutative || false),
Option.getOrElse<boolean>(() => false)
)
} gotenberg-js-client (103☆) recommends: import { pipe, gotenberg, convert, html, please } from 'gotenberg-js-client'
const toPDF = pipe(
gotenberg('http://localhost:3000'),
convert,
html,
please
) And a project called setup-macromamba (108☆) does this export const getMicromambaUrl = (micromambaSource: MicromambaSourceType) => {
return pipe(
micromambaSource,
match(
(version) => getMicromambaUrlFromVersion(getCondaArch(), version),
(url) => url
)
)
} I would note that |
@guy-borderless
For the above reasons, we don't think it's reasonable to add this to std. I recommend you'd publish this as a 3rd party package. If that package gets popularity in the community, then we can revisit this suggestion. Thanks for your efforts anyway. |
No worries, I hope that eventually there will be more of a demand to include something like a functions module. I'll publish something to JSR and post here for future reference. |
@kt3k, I can definitely empathize with having a defined line of what should be part of I presume this is might be related to the functional style in some way, but it would be appreciated if you could expand on some more concrete reasons so that future suggestions of adding things like If this is purely based on not having enough interest that's fine and makes sense. I just want to see if there are perhaps other reasons to consider. |
I would like to say that in my opinion having such patterns, with good typings, available in For example, using I would also note that patterns like dependency injection, via some form of a curry function, can greatly improve code quality and are often hacked in a sub-optimal way. There are opportunities to provide good value via giving utilities for working with functions, and many toolbelts contain them. |
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:
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.