Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't Scala find implicit value for parameter scala.slick.session.Session?

I am running a Scala Play 2.2 application with Slick 1.0.1. I am trying to wrap all of my database calls into a future try, for example:

object DbTeachers extends Table[DbTeacher]("edu_teachers") {
...
  def insertTeacher(school: Int, userId: String)
                   (implicit ec: ExecutionContext, db: Database) = 
    future { Try { db.withSession => { implicit s: Session =>
      (DbTeachers.school ~ DbTeachers.teacher).insert(school, userId)
  }}}
}

I find that the pattern future { Try { db.withSession => { ACTUAL_CODE_GOES_HERE }}} creates clutter and I would like to abstract it out as follows:

sealed class DbAsync[T](block: => T) {
  import play.api.libs.concurrent.Execution.Implicits.defaultContext
  implicit lazy val db = Database.forDataSource(DB.getDataSource())
  def get: Future[Try[T]] = future { Try { db.withSession { implicit s: Session =>
    block 
  }}}
}

object DbAsync {
  def apply[T](block: => T): Future[Try[T]] = new DbAsync[T](block).get
}

And then I can write my insertTeacher function as:

def insertTeacher(school: Int, userId: String) = DbAsync {
  (DbTeachers.school ~ DbTeachers.teacher).insert(school, userId)
}

However, the scala compiler (2.10.2) complains about this: could not find implicit value for parameter session: scala.slick.session.Session

According to my understanding, the insert() method does have an implicit session variable in scope within the DbAsync block, and because it is a call-by-name parameter, it shouldn't actually be evaluated until it is called within the DbAsync, at which time there would be an implicit session object in scope.

So, my question is, how do I convince the Scala compiler that there actually is an implicit Session object in scope?

like image 917
Alex Avatar asked Dec 26 '13 17:12

Alex


1 Answers

Your suggestion is incorrect. It doesn't matter where call-by-name parameter will be evaluated. All implicit parameters should be resolved at compile time in the place where they are required.

You could make it work this way:

def dbAsync[T](block: Session => T): Future[Try[T]] = {
  import play.api.libs.concurrent.Execution.Implicits.defaultContext
  implicit lazy val db = Database.forDataSource(DB.getDataSource())
  future { Try { db.withSession { block }}}
}

def insertTeacher(school: Int, userId: String) = dbAsync { implicit s: Session =>
  (DbTeachers.school ~ DbTeachers.teacher).insert(school, userId)
}

Note that you don't need class DbAsync nor object DbAsync.

Note that you should not use defaultContext for blocking operations. You could create additional ExecutionContext with configured thread pool.

like image 128
senia Avatar answered Nov 14 '22 22:11

senia