I'm trying to make sure I understand how to write clean and robust idiomatic Scala, so I wrote a custom generic implementation sequence
of type A[B[C]] => B[A[C]]
(instead of TraversableOnce[Future[A]] => Future[TraversableOnce[A]]
-- I know something like this exists in scalaz).
Is the following code a reasonable and idiomatic way of writing sequence
? Are there existing traits in the core Scala library that would be easy to substitute for Liftable
, Monoid
, and Traversable
that would keep this implementation of sequence
relatively intact? What are the most significant improvements that can be made to this code?
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import java.util.concurrent.Executors
import scala.util.Success
import scala.util.Failure
object TestMain {
implicit val ec = ExecutionContext
.fromExecutorService(Executors.newSingleThreadExecutor)
trait Liftable[A[_], B[_]] {
def lift[C](bc: B[C]): B[A[C]]
}
trait Monoid[A] {
def mappend(a1: A, a2: A): A
def mzero: A
}
trait Traversable[T[_]] {
def head[A](t: T[A]): Option[A]
def next[A](t: T[A]): T[A]
}
implicit val listOfFutureLiftable = new Liftable[List, Future] {
def lift[C](fc: Future[C]) = fc.map(c => List(c))
}
implicit def listMonoid[A] = new Monoid[List[A]] {
def mappend(a1: List[A], a2: List[A]) = a1 ++ a2
def mzero = Nil
}
implicit def futureMonoid[A: Monoid] = new Monoid[Future[A]] {
def mappend(a1: Future[A], a2: Future[A]) = {
for {
v1 <- a1
v2 <- a2
} yield implicitly[Monoid[A]].mappend(v1, v2)
}
def mzero = Future.successful(implicitly[Monoid[A]].mzero)
}
implicit val listTraversable = new Traversable[List] {
def head[A](l: List[A]) = l match {
case Nil => None
case x :: _ => Some(x)
}
def next[A](l: List[A]) = l.tail
}
def sequence[A[_], B[_], C](lf: A[B[C]])(
implicit ta: Traversable[A],
lab: Liftable[A, B],
mb: Monoid[B[A[C]]]): B[A[C]] = {
val obc: Option[B[C]] = ta.head(lf)
obc match {
case Some(bc) => {
val first: B[A[C]] = lab.lift(bc)
val tail: A[B[C]] = ta.next(lf)
val rest: B[A[C]] = sequence(tail)
mb.mappend(first, rest)
}
case None => mb.mzero
}
}
def main(args: Array[String]) {
val lfi: List[Future[Int]] = List(Future(1), Future(2))
val fli: Future[List[Int]] = sequence(lfi)
fli andThen {
case Success(li) => println(li)
case Failure(e) => println("fail: " + e)
} andThen {
case _ => ec.shutdown
}
}
}