2
votes

Background

I am trying to understand best practices for bringing implicit objects into scope within a Scala application.

I have a Playframework 2.2.0 (Scala 2.10) web app that mixes in a trait for Authorization. It checks. The Authenticated object checks that there is a user_id in scope, attempts to retrieve the user info, access token, and a data package object called a MagicNotebook from cache, database, and web service call. If the request is valid, then various objects are added to the wrapped request.

  object Authenticated extends ActionBuilder[AuthenticatedRequest] {
    def invokeBlock[A](request: Request[A],
                       block: (AuthenticatedRequest[A] => Future[SimpleResult])) = {
      request.session.get(userName).map { implicit userId =>
        Cache.getAs[DbUser](userKey).map { user =>
          Cache.getAs[String](accessTokenKey).map { accessToken =>
            Cache.getAs[MagicNotebook](magicNotebookKey(userId)).map { notebook =>
              block(AuthenticatedRequest(user, accessToken, notebook, request) )
            }.getOrElse(startOver)
          }.getOrElse {
            requestNewAccessToken(user.token).flatMap { response =>
              persistAccessToken(response).map { accessToken =>
                Cache.getAs[MagicNotebook](magicNotebookKey(userId)).map { notebook =>
                  block(AuthenticatedRequest(user, accessToken, notebook, request))
                }.getOrElse(startOver)
              }.getOrElse(startOver)
            }
          }
        }.getOrElse(startOver) // user not found in Cache
      }.getOrElse(startOver) // userName not found in session
    }
  }
}

case class AuthenticatedRequest[A](user: DbUser,
                                   accessToken: String,
                                   magic: MagicNotebook,
                                   request: Request[A])
    extends WrappedRequest[A](request)

Question

What is the best way to bring these implicit variables into scope?

Through an implicit class?

I tried to use an implicit companion class, with the following code:

object Helper {
  implicit class Magical(request: AuthenticatedRequest[AnyContent]) {
    def folderMap = request.magic.fMap
    def documentMap = request.magic.dMap
  }
}

However, I don't really get the benefit of an implicit this way:

def testing = Authenticated { implicit request =>
  import services.Helper._
  request.magic.home.folders // doesn't compile
  request.magic.home.folders(Magical(request).ffMap) // compiles, but not implicitly
  Ok("testing 123")
}

Through an import statement?

One possibility I considered was through an import statement within the controller. Here, the request has a MagicNotebook object in scope that I would like to use as an implicit variable.

def testing = Authenticated { implicit request =>
  import request.magic._
  request.magic.home.folders // implicit map is a parameter to the `folder` method
  Ok("testing 123")
}

Through a companion trait?

Here, I create a companion trait that is mixed into the Authenticate trait that includes the two maps of the MagicNotebook object into scope of the controller.

trait Magic {
  implicit def folderMap[A](implicit request: AuthenticatedRequest[A]) =
    request.magic.fMap
  implicit def docMap[A](implicit request: AuthenticatedRequest[A]) = 
    request.magic.dMap
}

My preference is the companion trait solution, but I was wondering if there might be a better way that I overlooked. I ended up re-writing methods that use the implicit variable, to use the MagicNotebook's two maps instead of whole object as implicit parameters.

But again, I was wondering if there might be a better way.

2
implicit class Magical(request: AuthenticatedRequest[AnyContent]) would fix your compiler error.Ashalynd
You were already using it, I meant dropping "implicit" from the parameter signature as it was in your exampleAshalynd
yes @Ashalynd, that fixes the compile error, but it doesn't help to use the implicits, see edit aboveAlex
I see. I would probably go for import statement, it seems to be the easiest way.Ashalynd
Is there a reason for not using for comprehensions in your first code block?pedrofurla

2 Answers

3
votes

One of the ways I know of defining implicits is by using Package Objects.

package implicitIdiomatic {
    implicit def nullableLongToOption(l:java.lang.Long) = Option(l)
  }
}

package implicitIdiomatic
class ImplicitIdiomaticTest{
  val l:Long = 1

  longOpt(l)

  def longOpt(l:Option[Long]) = l match {case Some(l1) => println(l1); case None => println("No long")}
}

Kind of useless example but hope you get the idea. Now when longOpt gets l, it is converted to Option[Long] using the implicit.

As long as you are working in the same package as defined by the package object you don't need the import statement.

2
votes

I am quite partial to package objects for this sort of thing. See What's New in Scala 2.8: Package Objects for a description. Package objects effectively allow you to put implicit classes into a package, which you can't otherwise do.

However, the main snag with this approach is that you can't split the definition of an object across multiple source files, so because the implicit classes need to be defined within the package object, they also need to be all in the same source file. If you have many implicit classes you wish to have imported, this can result in a large and unwieldy source file. However, that in itself is a sign that you have a “package god object” which should be split.