Matt
Matt

Reputation: 11317

Using Iterable with scala generics

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

Answers (3)

flavian
flavian

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

Tomas Mikula
Tomas Mikula

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

Cyrille Corpet
Cyrille Corpet

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

Related Questions