As described here
Type-safe way to divide a tuple into multiple tuples
I have a method with the following signature
def execute[T <: Record](funs: Seq[(Session) => T]): Seq[T]
where Session
is a Slick database session; the basic implementation of this method is
def execute[T <: Record](funs: Seq[(Session) => T): Seq[T] = {
db withSession {
session: Session => funs.map(fun => fun(session))
}}
(where db
is a Slick Database
) with other implementations that add things like logging, caching, multi-threading, etc. In particular, the multi-threading implementation uses funs.grouped(ceil(funs.size / threadCount)).map(funs => Future {})
to divide up the functions among several threads.
I would like to create a version of the method that accepts a tuple of functions so that I can return values of different types - as described in the question linked to above I didn't know of a good way to split up a tuple into smaller tuples and then recombine the results for the multi-threaded case, but the answer to that question was to use the Shapeless library's HList
s - however I am unclear as to how I can create a polymorphic variant of the (Session) => T
function, the problem being that all of the examples of polymorphic functions that I've seen use wrapped type params, e.g. (Set ~> Option)
which each wrap a polymorphic T
, but I am trying to create a (Session ~> T)
function where Session
is invariant and the polymorphic T
isn't wrapped in a Set
or Option
etc. I am doubtless looking at this problem the wrong way owing to not having sufficient experience with Shapeless.
How can I use Shapeless to create a polymorphic version of the def execute(funs: Seq[(Session) => T]): Seq[T]
function?
You actually don't really need or want a polymorphic function here—you can get what you're looking for from a few type classes that Shapeless provides out of the box. It looks a little odd, but it's not really all that complicated (note that I'm using Shapeless 2.0—you could do this in 1.2.4, but it'd be messier):
import shapeless._, ops.tuple.{ ConstMapper, ToList, ZipApply }
import shapeless.syntax.std.tuple._
def execute[F <: Product, S, O](funs: F)(implicit
cm: ConstMapper.Aux[F, Session, S],
za: ZipApply.Aux[F, S, O],
tl: ToList[O, Record]
): O = db withSession { session: Session =>
funs.zipApply(funs.mapConst(session))
}
We're essentially just taking our session, making a new tuple by repeating it as many times as our input is long, zipping the input tuple with this new tuple of sessions, and then applying the first part of each zipped element to the second part. The ToList
part requires all elements of the resulting tuple to be subtypes of Record
.
For the sake of a complete working example, here are some simple demonstration definitions:
type Session = String
trait Record
case class RecordX(s: String) extends Record
case class RecordY(i: Int) extends Record
def x(s: Session) = RecordX(s)
def y(s: Session) = RecordY(s.size)
object db {
def withSession[T](f: Session => T) = f("foo")
}
And it works!
scala> execute((x _, y _))
res0: (RecordX, RecordY) = (RecordX(foo),RecordY(3))
We get a nice appropriately statically typed tuple as our result.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With