Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a shapeless polymorphic function with a naked type param

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 HLists - 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?

like image 973
Zim-Zam O'Pootertoot Avatar asked Mar 15 '14 01:03

Zim-Zam O'Pootertoot


1 Answers

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.

like image 58
Travis Brown Avatar answered Sep 19 '22 21:09

Travis Brown