Open koraa opened 4 years ago
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)
)()
@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));
Provide a generalized pipe/compose metaprogramming infrastructure.
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.
We could also use a more general
meta
tuple that allows for generalized rewriting:In this framework do could be implemented as a special case of meta:
Do alone would allow for some interesting transformations on pipe; e.g.
do(ifdef)
would early abort pipe execution anddo(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.