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

Parallel Operation #16

Open
dvisztempacct opened this issue Jun 15, 2018 · 6 comments
Open

Parallel Operation #16

dvisztempacct opened this issue Jun 15, 2018 · 6 comments

Comments

@dvisztempacct
Copy link
Contributor

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
Copy link
Member

gilbert commented Jun 15, 2018

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
Copy link

leostera commented Jun 17, 2018

@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
Copy link
Contributor Author

@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
Copy link
Contributor Author

the tests seem broken

I've addressed this in PR #18

@seprich
Copy link
Contributor

seprich commented Oct 16, 2018

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
Copy link
Contributor

dfalling commented Dec 6, 2019

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))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants