ses
ses

Reputation: 13372

Why does it need implicit arg if code works without it

There is Security Example where you can find => implicit request for def withUser(... method.

Based on that example I've created one-file-sample here: https://github.com/Sergey80/scala-samples/blob/master/src/main/scala/implicits/usecases/RequestResponseTest.scala (where is no implicit .. and I do not see why I need use it)

The question is: why do I need type implicit request (as was shown in Security Example) if it works without implicit. Since we are already referring on requestas val (defined above in the code).

Copy-pasting my simplified-sample here (where not implicit is used), this code has the same functions as in initial Security Example (withUser, withAtuh) with same set of parameters, the point was just to make it launchable from one file:

package implicits.usecases

object RequestResponseTest extends App {
  type User = String      // for simplicity sake - all are strings
  type Request = String
  type Result = String

  trait Action  // there are two auth Action - Ok and Not Ok
  case class OkAction(content:Result) extends Action
  case class UnauthorizedAction(content:Result) extends Action

  var userDB = List("user1", "user2", "user3") // our simple database

  val user = "user1"  // usually user available from the session
  var request : Request = "request"  // current request

  // check authorization and wraps request into Action
  def withAuth(f: User => (Request => Result) ): Action = {
    println("withAuth in play...")

    val result:Result = f("user1")(request) // (is user was found or not)

    def isAuthorized(user:User): Boolean = {
      println("check authorisation...")
      true                                // authorize everyone, welcome :)
    }

    def onUnAuthorized(request: Request): Action = {
      println("wrapped to Action as not authorized")
      UnauthorizedAction(request)
    }

    if (result == "ok") {
      if (isAuthorized(request)) {
        println("wrapped to Action as authorized")
        OkAction(request)
      } else onUnAuthorized(request)
    } else onUnAuthorized(request)
  }

  //
  def withUser(f: User => (Request => Result)) : Action = {
    println("withUser in play...")

    def findInDb(user: User): Option[User] = {
      userDB.find(u => u == user)
    }

    val authResult:Action = withAuth (    // call 'withAuth'
      /* Create anonymous function to please the 'withAuth'
         it is already known who is current user, and what request is, but doesn't what Result is.
         And he Result is the fact whether the user exists in users database or not.
         So it tries to get the Result by searching for the current user.
         If user has been found then it makes sense to continue with Authorisation (withAuth)
      */
      user => request => { // passing anonymous function with user, request, and result (where result is based on user existence in the user db )
        val userOption = findInDb("user1")          // find the user in users db
        val result:Result = userOption match {      // check if user exists
          case Some(_) => // user has been found
            println("user has been found")
            "ok"
          case None    => // user has not been found
            println("user has not been found")
            "not ok"
        }
       result // "ok" / "not ok" (user has been found or not)
      } // end of passing anonymous function to 'withAuth'
    )

    authResult match {
      case OkAction(_) => f(user)(request)  // if authorized do The work
    }

    authResult
  } // edn of 'withUser'

  // Let's run this and to some work (no `implicit request` here)
  def doWork() = withUser {                     // doWork -> withUser -> withAuth  (like Decorator/Wrapper pattern)
    user => request => {
      // if user exists (withUser) and authorization is ok (withAuth), then this work will done
      println("do some important work here!!")
      "work is done!"                           // Result is just a String
    }
  }
  val result = doWork()  // doWork doesn't care about user or request
}
/* Output:
withUser in play...
withAuth in play...
user has been found
check authorisation...
wrapped to Action as authorized
do some important work here!!
*/

Update: I understand if implicit was put on type Request to let compiler look up for value of this type which is defined implicitly somewhere. Like Inversion of Control injection. But there in Security Example it is put on value itself. On "request" value. If I'm putting 'request' it automatically means that I already have access to it and compiler knows about it (without implicit).

Upvotes: 1

Views: 102

Answers (2)

ses
ses

Reputation: 13372

This simple snippet explained me the thing (let me remember). Kind of essential:

  implicit val integer = 3

  def function4(implicit i:Int) = i   // this works as default variable (ALMOST)
  // same as this
  def function4_1(i:Int = implicitly[Int]) = i   // implicit scope has Int = 3

  val result4 = function4         // should be without ()
  val result4_1 = function4_1()   // requires ()

  println("result4: " + result4)     // 3
  println("result4_1: " + result4_1) // 3

Also I've prepared couple examples here: 1 2 (2 - improving my initial code with Request-Response case)

Upvotes: 0

Michael Zajac
Michael Zajac

Reputation: 55569

One word answer: convenience.

Let's take this simple example from your first link:

def login = Action { implicit request =>
  Ok(views.html.login(loginForm))
}

You probably don't need the implicit here, but it's often convenient to have. Here's why:

This is really calling Action.apply. In particular, an overload that accepts a parameter:

block: Request[A] => Result

The block being passed here is the anonymous function:

request => Ok(views.html.login(loginForm))

The Request is probably not being used at all here, so whether it's implicit or not doesn't really matter. However, Play has many functions which require an implicit Request (for i18n, checking the uri accessed, whether or not a secure path was used, etc).

If views.html.login (a Play view) requires an implicit Request parameter, then it would be very useful to mark request as implicit, which we have the option of doing so.

implicit request => Ok(views.html.login(loginForm))

All the above code is doing is creating an anonymous function Request[A] => Result, in which the Request[A] parameter is made implicit. It isn't looking for an implicit, it's creating one. Similar to:

def anon(req: Request[A]): Result = {
    implicit val request = req
    Ok(views.html.login(loginForm)
}

Why? It makes for a much less boiler-plate, especially when many methods within the block require Request parameters. There's also no ambiguity, because the implicit is contained within the block of the anonymous function, and in this example, there is only one Request that each method should receive (there's only method call in this example, but there could be several more).

Otherwise you'd have to write:

def login = Action { request =>
  Ok(views.html.login(loginForm)(request))
}

The withUser example isn't really any different. It just involves another nested anonymous function where request is made implicit.

Upvotes: 1

Related Questions