Comprensión implícita en Scala


Estaba haciendo mi camino a través del tutorial de Scala playframework y me encontré con este fragmento de código que me tenía desconcertado:

def newTask = Action { implicit request =>
taskForm.bindFromRequest.fold(
        errors => BadRequest(views.html.index(Task.all(), errors)),
        label => {
          Task.create(label)
          Redirect(routes.Application.tasks())
        } 
  )
}

Así que decidí investigar y me encontré con este post .

Todavía no lo entiendo.

¿Cuál es la diferencia entre esto:

implicit def double2Int(d : Double) : Int = d.toInt

Y

def double2IntNonImplicit(d : Double) : Int = d.toInt

Aparte del hecho obvio, tienen nombres de métodos diferentes.

¿Cuándo debo usar implicit y por qué?

Author: Andrii Abramov, 2012-04-30

4 answers

Explicaré los principales casos de uso de implicits a continuación, pero para más detalles vea el capítulo relevante de Programación en Scala.

Parámetros Implícitos

La lista final de parámetros en un método se puede marcar implicit, lo que significa que los valores se tomarán del contexto en el que se llaman. Si no hay un valor implícito del tipo correcto en el ámbito, no se compilará. Dado que el valor implícito debe resolver a un solo valor y para evitar choques, es una buena idea hacer que el tipo sea específico para su propósito,por ejemplo, ¡no requiera que sus métodos encuentren un Int implícito!

Ejemplo:

  // probably in a library
class Prefixer(val prefix: String)
def addPrefix(s: String)(implicit p: Prefixer) = p.prefix + s

  // then probably in your application
implicit val myImplicitPrefixer = new Prefixer("***")
addPrefix("abc")  // returns "***abc"

Conversiones implícitas

Cuando el compilador encuentra una expresión del tipo incorrecto para el contexto, buscará un valor implícito Function de un tipo que le permitirá typecheck. Así que si se requiere un A y encuentra un B, buscará un valor implícito de tipo B => A en el alcance (también comprueba algunos otros lugares como en los objetos compañeros B y A, si existen). Dado que def s puede ser "eta-expandida" en Function objetos, un implicit def xyz(arg: B): A también servirá.

Así que la diferencia entre sus métodos es que el marcado implicit será insertado por usted por el compilador cuando se encuentre un Double pero se requiera un Int.

implicit def doubleToInt(d: Double) = d.toInt
val x: Int = 42.0

Funcionará igual que

def doubleToInt(d: Double) = d.toInt
val x: Int = doubleToInt(42.0)

En la segunda hemos insertado la conversión manualmente; en la primera el compilador hizo lo mismo automáticamente. El la conversión es necesaria debido a la anotación de tipo en el lado izquierdo.


Con respecto a tu primer fragmento de Play:

Las acciones se explican en esta página desde la documentación de Play (véase también API docs). Usted está usando

apply(block: (Request[AnyContent]) ⇒ Result): Action[AnyContent]

En el objeto Action (que es el compañero del rasgo del mismo nombre).

Así que necesitamos proporcionar una función como argumento, que se puede escribir como un literal en la forma

request => ...

En un literal de función, la parte antes de => es una declaración de valor, y se puede marcar implicit si lo desea, al igual que en cualquier otra declaración val. Aquí, request no tiene que ser marcado implicit para que esto escriba check, pero al hacerlo estará disponible como un valor implícito para cualquier método que pueda necesitarlo dentro de la función (y por supuesto, también se puede usar explícitamente). En este caso particular, esto se ha hecho porque el bindFromRequest method on the Form class requires an implicit Request argument.

 340
Author: Luigi Plinge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-29 11:18:42

ADVERTENCIA: contiene sarcasmo juiciosamente! YMMV...

La respuesta de Luigi es completa y correcta. Este es solo para extenderlo un poco con un ejemplo de cómo se puede abusar gloriosamente implicits, como sucede muy a menudo en los proyectos de Scala. En realidad tan a menudo, probablemente incluso se puede encontrar en una de las guías "Mejores Prácticas".

object HelloWorld {
  case class Text(content: String)
  case class Prefix(text: String)

  implicit def String2Text(content: String)(implicit prefix: Prefix) = {
    Text(prefix.text + " " + content)
  }

  def printText(text: Text): Unit = {
    println(text.content)
  }

  def main(args: Array[String]): Unit = {
    printText("World!")
  }

  // Best to hide this line somewhere below a pile of completely unrelated code.
  // Better yet, import its package from another distant place.
  implicit val prefixLOL = Prefix("Hello")
}
 24
Author: Daniel Dinnyes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-03 16:43:15

Por qué y cuándo debe marcar el parámetro request como implicit:

Algunos métodos que utilizarás en el cuerpo de tu acción tienen una lista de parámetros implícitos como, por ejemplo, Form.scala define un método:

def bindFromRequest()(implicit request: play.api.mvc.Request[_]): Form[T] = { ... }

No necesariamente te das cuenta de esto ya que simplemente llamarías myForm.bindFromRequest() No tienes que proporcionar los argumentos implícitos explícitamente. No, deja el compilador para buscar cualquier objeto candidato válido para pasar cada vez que se encuentre llamada a un método que requiere una instancia de la solicitud. Dado que tiene una solicitud disponible, todo lo que necesita hacer es marcarla como implicit.

Usted explícitamente lo marca como disponible para uso implícito.

Le insinúas al compilador que está "bien" usar el objeto request enviado por el framework Play (que le dimos el nombre "request" pero podríamos haber usado solo "r" o "req") donde sea necesario, "a escondidas".

myForm.bindFromRequest()

¿Lo ves? no hay, pero está allí!

Simplemente sucede sin tener que encajarlo manualmente en cada lugar que se necesite (pero puede pasarlo explícitamente, si así lo desea, sin importar si está marcado implicit o no):

myForm.bindFromRequest()(request)

Sin marcarlo como implícito, tendría que hacer lo anterior. Marcarlo como implícito no es necesario.

Cuando debe marcar la solicitud como implicit? Usted solo necesita realmente si usted está haciendo uso de métodos que declare una lista de parámetros implícitos esperando una instancia de la Solicitud. Pero para mantenerlo simple, solo podría adquirir el hábito de marcar la solicitud implicit siempre . De esa manera usted puede escribir código conciso hermoso.

 4
Author: Peter Perháč,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-31 09:24:08

También, en el caso anterior debería haber only one función implícita cuyo tipo es double => Int. De lo contrario, el compilador se confunde y no compilará correctamente.

//this won't compile

implicit def doubleToInt(d: Double) = d.toInt
implicit def doubleToIntSecond(d: Double) = d.toInt
val x: Int = 42.0
 0
Author: rileyss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-25 16:48:48