Reputation: 11317
I have two functions with the exact same implementation - only one handles Option
and the other handles Seq
. I'd like to use generics to write this as a single function that handles Iterable
, while keeping the concrete type in the calling code - if this is possible?
def f[T](a: Seq[Failure \/ T]): Failure \/ Seq[T] = { ??? }
def g[T](b: Option[Failure \/ T]): Failure \/ Option[T] = { ??? }
The implementation isn't important, but for context they translate from a collection of results (each of which may either have succeeded (T
) or failed (Failure
)) to either a single failure or a complete collection of successful results. \/
is just scalaz's version of Either.
I'm looking to do something like this:
def f[I[T] <: Iterable[T]](results: I[Failure \/ T]): Failure \/ I[T] = { ??? }
Upvotes: 0
Views: 636
Reputation: 28511
Off the top of my head and not tested, so apologies for typos.
import scala.collection.generic.CanBuildFrom
def combine[M[X] <: Iterable[X], T](
input: M[Failure \/ T]
)(
implicit cbf: CanBuildFrom[Nothing, T, M[T]]
): Failure \/ M[T] = {
def inner(builder: Builder[T, M[T]], els: M[T]): Failure \/ M[T] = {
els.headOption match {
case Some(\/-(right)) => inner(builder += right, els.tail)
case Some(-\/(left)) => -\/(left)
case None => \/-(builder.result())
}
}
inner(cbf(), input)
}
Something like that, you have an inner recursion that "short circuits" when the first failure is found.
Upvotes: 2
Reputation: 6537
In FP, this pattern is expressed by an interplay between a traversable collection (such as Seq
or Option
) and an applicative functor (such as Failure \/ ?
).
The generic implementation (using scalaz
) is then
import scalaz._
import scalaz.syntax.traverse._
def f[F[_]: Traverse, G[_]: Applicative, T](a: F[G[T]]): G[F[T]] = a.sequence
At the call site, you would do
import scalaz.std._
type FailureOr[A] = Failure \/ A
val x: Option[FailureOr[Int]] = ???
val y: List[FailureOr[Int]] = ???
val z: Vector[FailureOr[Int]] = ???
f[Option, FailureOr, Int](x)
f[List, FailureOr, Int](y)
f[Vector, FailureOr, Int](z)
// or just directly
import scalaz.syntax.traverse._
x.sequence
y.sequence
z.sequence
Note that I used List
and Vector
instead of Seq
. This is because scalaz
doesn't provide an implicit Traverse
instance for Seq
. While conceptually Seq
is traversable, it is better (for performance reasons) to implement the Traverse
operations specifically for concrete implementations of Seq
such as List
or Vector
. If you really want to, you can write your own instance of Traverse[Seq]
, just know that it will be suboptimal for some implementations of Seq
.
Upvotes: 3
Reputation: 5315
You can do something like this (see implementation for Future.sequence
as an example):
def f[T, M[X] <: Iterable[X]](results: M[Failure \/ T): Failure \/ M[T]
You'll probably need some CanBuildFrom
too.
Upvotes: 1