0

I am trying to write a Value Class to add functionality to anything that implements Seq[_] and allow it to make batch calls that return a Future[_] (specifically, I am using it to make batch REST calls).

final class BatchedList[A, C[X] <: Seq[X]](val targetList: C[A]) extends AnyVal {

  def batchRequests[B](batchSize: Int)(runner: Seq[A] => Seq[Future[Either[Result, B]]])
    (implicit bf: CanBuildFrom[C[A], Either[Result, B], C[Either[Result, B]]]): Future[Either[Result, C[B]]] = {
      targetList.grouped(batchSize).foldLeft(Future.successful(bf(targetList))) { (results, set) =>
        results flatMap { responses =>
          Future.sequence(runner(set)).map(responses ++=)
        }
      } map {
        _.result().sequenceU
      }
    }
  }

However, I can't get this to compile. I keep receiving the compiler error

value sequenceU is not a member of type parameter C[Either[play.api.mvc.Result,B]]

I've imported both scalaz._ and Scalaz._, and I know they've provided a Traverse[_] for my use case (which is List[_] in this example). I'm pretty sure this is some sort of implicit resolution issue with the types, but I'm stumped on how to proceed forward resolving it.

Jeff
  • 237
  • 2
  • 6

1 Answers1

1

I believe that this happens because Scalaz doesn't provide typeclass instances for Seq but instead for IndexedSeq and List. As such, you'll need to provide the Traverse instance yourself (note the additional implicit argument C to batchRequests):

final class BatchedList[A, C[X] <: Seq[X]](val targetList: C[A]) extends AnyVal {
  def batchRequests[B](batchSize: Int)(runner: Seq[A] => Seq[Future[Either[Result, B]]])
    (implicit bf: CanBuildFrom[C[A], Either[Result, B], C[Either[Result, B]]], C: Traverse[C]): Future[Either[Result, C[B]]] =
      targetList.grouped(batchSize).foldLeft(Future.successful(bf(targetList))) { (results, set) =>
        results flatMap { responses =>
          Future.sequence(runner(set)).map(responses ++=)
        }
      } map {
        _.result().sequenceU
      }
}

As you can see, this will return a sequence type corresponding to the type provided as C:

scala> def run[A](s: Seq[A]): Seq[Future[Either[Result, A]]] =
     |   s.map(i => Future.successful(Right(i)))
run: [A](s: Seq[A])Seq[scala.concurrent.Future[Either[Result,A]]]

scala> :t new BatchedList(List(1,2,3)).batchRequests(1)(run)
scala.concurrent.Future[Either[Result,List[Int]]]

scala> :t new BatchedList(Vector(1,2,3)).batchRequests(1)(run)
scala.concurrent.Future[Either[Result,scala.collection.immutable.Vector[Int]]]

If you always want it to return a Seq it's a simple matter of an upcast.

Hugh
  • 8,872
  • 2
  • 37
  • 42
  • But the result of `_.result()` is not a `Seq[_]`, but a `C[_]`, which when called on a List, should be `List[_]` – Jeff Oct 29 '14 at 00:14
  • When called on a `List`, it does indeed return a `Future[Either[Result, List[B]]]` – isn't this what you asked for? I've edited my answer to illustrate this. – Hugh Oct 29 '14 at 04:48
  • Got it, I missed the addition of the `implicit C: Traverse[C]`. So then I guess my question is, why is that additional parameter needed? Since I've imported all the implicits with `Scalaz._` in the same scope as the function declaration, why do I need to add it in the method signature? I guess I'm confused on the implicit resolution rules. – Jeff Oct 29 '14 at 18:59
  • 1
    I'm not an expert on this stuff, but my understanding of the reason is that as you'd written it, you've told it that `targetList` is a `C[A]` where `C[_] <: Seq[_]`. Thus, even though the compiler knows that `C` is *some* child type of `Seq`, it doesn't know which one, and as there are no `Traverse` for `Seq` but only for *some* of its sub-types, there's no appropriate instance for it to find. For example, when `C` is `List`, there is indeed a `Traverse[List]` instance, but in this method Scala doesn't *know* `C` is `List`. – Hugh Oct 30 '14 at 03:40