-
Notifications
You must be signed in to change notification settings - Fork 21
Description
I’m copying the content of scala/collection-strawman#247 here.
We currently have the Future.traverse
and Future.sequence
methods that allow us to turn a Seq[Future[A]]
into a Future[Seq[A]]
. But I find these operations very useful on other types as well. For instance to turn:
- an
Option[Future[A]]
into aFuture[Option[A]]
, - an
Either[E, Future[A]]
into aFuture[Either[E, A]]
,
but also to turn:
- a
List[Option[A]]
into anOption[List[A]]
, - a
List[Either[E, A]]
into anEither[E, List[A]]
. - a
List[Validated[A]]
into aValidated[List[A]]
, - …
We can see that there are lots of possible combinations. To make the implementation concise and extensible (cf the example with an imaginary Validated
data type which is not part of the stdlib) it’s probably a good idea to rely on typeclasses.
On one hand, a typeclass-based design (with Traverse
and Applicative
) would provide the highest level of reuse: once you define N instances of Traverse
and M instances of Applicative
you can combine them to handle N×M situations.
On the other hand, the discoverability of the traverse
and sequence
operations would be rather poor. Today it is easy to find Future.sequence
(and to understand what it does) just by browsing the Scaladoc. But if, instead, we only find an instance of Applicative
in Future
’s documentation, it is not obvious what it can be useful for.
A compromise might be to have the typeclass-based design (so that advanced users have the full power) but also define specialized forwarders in companion objects of Applicative
instances (such as Future
).
In summary, the changes we would have to apply would be the following.
- Define
Traverse
andApplicative
typeclasses - Define useful instances
- For
Applicative
, those that come to my mind are:Option
,Future
,Either
- For
Traverse
:List
,Option
,Either
- For
- Add forwarder methods in companion objects of
Applicative
instances. For instance, forFuture
:For the sake of comparison, the current definition of these methods is the following:object Future { def traverse[F[_], A, B](fa: F[A])(f: A => Future[B])(implicit F: Traverse[F], ec: ExecutionContext): Future[F[B]] = F.traverse(fa)(f) def sequence[F[_], A](ffa: F[Future[A]])(implicit F: Traverse[F], ec: ExecutionContext): Future[F[A]] = F.sequence(ffa) }
I don’t think the one that usesobject Future { def traverse[A, B, M[X] <: TraversableOnce[X]](in: M[A])(fn: A => Future[B])(implicit cbf: CanBuildFrom[M[A], B, M[B]], ec: ExecutionContext): Future[M[B]] def sequence[A, M[X] <: TraversableOnce[X]](in: M[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], ec: ExecutionContext): Future[M[A]] }
Traverse
is more complicated that the current one…
@Jasper-M pointed out that:
It feels a bit awkward to only add
Traverse
andApplicative
just for this purpose, but not have all related typeclasses and operations.
And I agree with this point… I’d like to start a discussion on the topic. What do other users or scala maintainers think?