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

Infrastructure for metaprogramming in pipe/compose (await inside pipe()) #138

Open
koraa opened this issue Oct 27, 2020 · 2 comments
Open
Labels
breaking-change This is a tag for issues whose implementation needs a new major version enhancement New feature or request help wanted Extra attention is needed needs-api-design

Comments

@koraa
Copy link
Contributor

koraa commented Oct 27, 2020

Provide a generalized pipe/compose metaprogramming infrastructure.

pipe.do = Tuple('do', 'fn'); // One element tuple as a tag
pipe.await = pipe.do((p, fn) => Promise.await(p));

Whenever pipe() (or compose) encounters do, it will evaluate all the functions to the left of the do statement and compose the functions to the right into a single function; passing the value and the functions into the function stored inside to.

compose(...leftFns, pipe.do(doFn), ...rightFunctions) <=> doFn(compose(...leftFns), compose(...rightFns))

We could also use a more general meta tuple that allows for generalized rewriting:

compose(...leftFns, pipe.meta(metaFn), ...rightFns) <=> metaFn(leftFns, rightFns)

In this framework do could be implemented as a special case of meta:

pipe.do = (doFn) =>
  pipe.meta((l, r) => (v) => doFn(composev(l)(v), composev(r)));

Do alone would allow for some interesting transformations on pipe; e.g. do(ifdef) would early abort pipe execution and do(map) would actually introduce loops as part of the function composition infrastructure.

Actually, I believe this would be about as general as the haskell do monad (hence the name) while staying in the fully functional framework.

This is different from the do syntax mostly because this uses explicit connectives instead of type dependent connectives as monads to (on the other hand this could be remedied with a type class).

Of course, how practical this is would have to be evaluated but the basic use case with await is in my definetly useful.

@koraa koraa added the enhancement New feature or request label Oct 27, 2020
@koraa koraa changed the title _AWAIT Infrastructure for metaprogramming in pipe/compose (await inside pipe()) Apr 30, 2021
@koraa koraa added breaking-change This is a tag for issues whose implementation needs a new major version help wanted Extra attention is needed needs-api-design labels Apr 30, 2021
@ptpaterson
Copy link

ptpaterson commented May 2, 2021

I have a few first impressions. To start, I think that this is really clever and pretty cool. It sounds inevitable that if you use pipe often enough, then you will want to resolve a promise in there eventually.

question, does this pipe.await definition drop the right-side functions?

// it looks like this:
pipe.await = pipe.do((p, fn) => Promise.await(p));
compose(...leftFns, pipe.await(awaitFn), ...rightFunctions)

// would transform into this:
awaitFn(compose(...leftFns), compose(...rightFns))

// which is the same as:
Promise.await(compose(...leftFns))

When I discovered ferrum, and was asking about generic map/fmap operations, I was coming already very familiar with the fp-ts package. While using it, I had lamented that every Monad had to have it's own implementation of map, e.g. Either.map, Task.map, TaskEither.map, etc. I thought it would be cool if there could be a ferrum Functor trait, but of course learned that not even Rust does that.

Point is: I became familiar with using Task and TaskEither as wrappers for promises, as well as leveraging natural transformations with Either to handle a mixture of synchronous and asynchronous code. I would probably, personally, prefer to leverage those over pipe meta programming. I think that's really just saying that I would prefer to be more explicit (and indeed verbose) about the operation rather than use the do function. I think. It's likely I could be persuaded 🙂

// this
pipe(
  leftFns,
  asyncFn, // returns Task, not actual promise
  task.chain(anotherAsynFn),
  task.map(...rightFns)
)()

// or even

pipe(
  leftFns,
  asyncFn, // returns Task, not actual promise
  task.chain(anotherAsynFn)
)()
  .then(result => pipe(
    result,
    ...rightFns)
  )
)

// over this
pipe(
  leftFns,
  pipe.await(asyncFn),
  pipe.await(anotherAsynFn),
  ...rightFns)
)()

@koraa
Copy link
Contributor Author

koraa commented May 16, 2021

@ptpaterson

question, does this pipe.await definition drop the right-side functions?

No, using Promise.await directly it should transform:

compose(...rightFns)(Promise.await(compose(...leftFns)))

Point is: I became familiar with using Task and TaskEither as wrappers for promises, as well as leveraging natural transformations with Either to handle a mixture of synchronous and asynchronous code. I would probably, personally, prefer to leverage those over pipe meta programming. I think that's really just saying that I would prefer to be more explicit (and indeed verbose) about the operation rather than use the do function. I think. It's likely I could be persuaded slightly_smiling_face

Let me just point out that the syntax I was planning to introduce is slightly different from what you used in your comment; the following bit of could wouldn't really happen.

// over this
pipe(
  leftFns,
  pipe.await(asyncFn),
  pipe.await(anotherAsynFn),
  ...rightFns)
)()

Instead it would be this:

pipe(
  leftFns,
  asyncFn,
  pipe.await,
  anotherAsynFn,
  pipe.await,
  ...rightFns)
)()

I am not sure how the other code examples could be implemented; there isn't really a task variable anywhere that can be used. However, you could (and already can) easily use then as a free function:

const then = curry('then', (p, f) => Promise.resolve(p).then(f));

pipe(
  value,
  asyncFn,
  then(anotherAsyncFn),
  then(andAThirdAsyncFn));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change This is a tag for issues whose implementation needs a new major version enhancement New feature or request help wanted Extra attention is needed needs-api-design
Projects
None yet
Development

No branches or pull requests

2 participants