RationalJS / future

A Js.Promise alternative for ReasonML
213 stars 15 forks source link

Parallel Operation #16

Open dvisztempacct opened 6 years ago

dvisztempacct commented 6 years ago

This library seems to be lacking idioms for parallel execution.

I'd like to see something like Promise.all() and additionally something like Bluebird's Promise.join() and Promise.map().

gilbert commented 6 years ago

Yes, I agree we should have these operations available.

I have a gut feeling there's some sort of functional idiom for parallel processing that we can draw from. Maybe @ostera can bring this to light, as he implemented a monad functor as a suggestion for bucklescript?

leostera commented 6 years ago

@gilbert there isn't a particular algebra that models Future values, but if you want map/bind you want a monad, and then the runtime would determine how it behaves (in this case, it eagerly runs, and on completion it calls the continuation).

@dvisztempacct the behavior of all can be achieved by sequencing a list of monads:

module Future = {
  type t('a);
  let all: (list(t('a)), list('a) => 'b );
};

let sum_results_or_0_if_any_rejects =
  [ Future.value(1), Future.value(2), Future.value(3) ]
  |> Future.all( ~resolve = List.fold_left((+), 0), ~reject = 0))

/* would essentially be */
let f_1 = Future.value(1);
let f_2 = Future.value(2);
let f_3 = Future.value(3);

let all_3 =
  f_1 |. Future.flatMap( first =>
    f_2 |. Future.flatMap( second =>
      /* at this stage below we have all the values, so we know if any rejections occurred */
      /* let's assume they are all okay! */
      f_3 |. Future.flatMap( third => Future.value([first, second, third ]))));

let sum_results_or_0_if_any_rejects =
  all_3 |. Future.fold( ~reject = _ => 0, ~resolve = List.fold_left((+), 0))

Which can as well come from a similar functor, Traversable 💯

Unfortunately join (which collides in name with the monadic join) can't be built with variadic parameters, so you'll end up with join, join3, join4, etc. After join4 you start thinking whether you want to just use all.

Hope this helps 🙏

dvisztempacct commented 6 years ago

@ostera

Thanks for the input.

the behavior of all can be achieved by sequencing a list of monads

That's a good suggestion, thanks :)

Please have a look at my PR #10 which implements a facility similar to Bluebird.map() (which is similar to ES6 Promise.all but a little more flexible.)

https://github.com/RationalJS/future/pull/10/files

Here is some code to test it:

let sleep : (unit=>unit)=>unit = [%bs.raw "f => setTimeout(f, 1000)"];

let sleepFuture = () => Future.make(resolve => sleep(resolve));

let processItem = (x:int):Future.t(int) => {
  Js.log2("processing", x);
  sleepFuture() |. Future.map(() => {
    Js.log2("completed", x);
    x
  });
};

let xs = [| 1, 2, 3 |];

xs |. Future.flatMapArray(processItem, 1)
|. Future.get(xs => {
  Js.log2("flatMapArray(1) got:", xs);
});

xs |. Future.flatMapArrayUnsafe(processItem, 1)
|. Future.get(xs => {
  Js.log2("flatMapArrayUnsafe(1) got:", xs);
});

xs |. Future.flatMapArray(processItem, 2)
|. Future.get(xs => {
  Js.log2("flatMapArray(2) got:", xs);
});

xs |. Future.flatMapArrayUnsafe(processItem, 2)
|. Future.get(xs => {
  Js.log2("flatMapArrayUnsafe(2) got:", xs);
});

xs |. Future.flatMapArray(processItem, 3)
|. Future.get(xs => {
  Js.log2("flatMapArray(3) got:", xs);
});

xs |. Future.flatMapArrayUnsafe(processItem, 3)
|. Future.get(xs => {
  Js.log2("flatMapArrayUnsafe(3) got:", xs);
});

I would add something like this to the tests/ dir except the tests seem broken.

dvisztempacct commented 6 years ago

the tests seem broken

I've addressed this in PR #18

seprich commented 6 years ago

So now there seems to be PR #22 offering the all function for list of Futures. 👍 for that ; writing flatMap monad sequences is very clumsy in comparison. However all is not sufficient in the Reason world because its type:

all: list(t('a)) => t(list('a))

requires a list of homogeneous items -> cannot be used when you want to wait different types of items. Therefore the Js.Promise.all2, Js.Promise.all3, etc. supporting tuples of up to 6 items (https://bucklescript.github.io/bucklescript/api/Js.Promise.html) On that venue I would like to see those tuple versions in this library as well. What do you think?

dfalling commented 4 years ago

They're not in the docs, but I discovered map2, map3, ... that serve this purpose. You also have to provide a function that maps the results to a tuple....

Future.map3(first, second, third, (a, b, c) => (a, b, c))