Comprensión implícita en Scala

308

Estaba abriéndome 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 esta publicación .

Aún 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 implicity por qué?

Clive
fuente
Encontré este tutorial realmente útil: Tutorial de Scala - Aprenda a crear funciones implícitas
Adrian Moisa

Respuestas:

391

Explicaré los principales casos de uso de implicidades 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 de 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 alcance, no se compilará. Dado que el valor implícito debe resolverse en un solo valor y para evitar conflictos, 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 implícito Int!

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 Functionvalor implícito de un tipo que le permita verificar el tipo. Entonces, si Ase requiere un y encuentra un B, buscará un valor implícito de tipo B => Aen el alcance (también verifica algunos otros lugares como en los objetos complementarios By A, si existen). Como defs puede ser "expandido en eta" en Functionobjetos, una implicit def xyz(arg: B): Avoluntad también lo hará.

Entonces, la diferencia entre sus métodos es que implicitel compilador insertará el marcado para usted cuando Doublese encuentre un pero Intse requiere un.

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 el segundo, hemos insertado la conversión manualmente; en el primero el compilador hizo lo mismo automáticamente. 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 de la documentación de Play (ver también documentos de API ). Tu estas usando

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

en el Actionobjeto (que es el compañero del rasgo del mismo nombre).

Por lo tanto, debemos proporcionar una función como argumento, que se puede escribir como literal en la forma

request => ...

En una función literal, la parte anterior a la =>es una declaración de valor, y puede marcarse implicitsi lo desea, como en cualquier otra valdeclaración. Aquí, request no tiene que estar marcado implicitpara 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 bindFromRequestmétodo en la clase Form requiere un Requestargumento implícito .

Luigi Plinge
fuente
12
Gracias por la respuesta. El enlace para el capítulo 21 es realmente impresionante. Lo aprecio.
Clive
14
Solo para agregar esto, el siguiente video brinda una excelente explicación de las implicidades más algunas otras características de scala youtube.com/watch?v=IobLWVuD-CQ
Shakti
Vaya a 24:25 en el video de arriba (para aquellos que no quieren escuchar los 55 minutos)
papigee
36

ADVERTENCIA: contiene sarcasmo juiciosamente! YMMV ...

La respuesta de Luigi es completa y correcta. Éste es sólo para extenderla un poco con un ejemplo de cómo se puede gloriosamente uso excesivo implícitos , como ocurre muy a menudo en proyectos Scala. En realidad, tan a menudo, incluso puede encontrarlo en una de las guías de "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")
}
Daniel Dinnyes
fuente
1
Jaja. Buen sentido del humor.
Det
1
Agradezco el humor. Este tipo de cosas es una de las razones por las que dejé de intentar aprender Scala hace muchos años y solo ahora estoy volviendo a ello. Nunca estaba seguro de dónde provenían algunas (muchas) de las implicidades en el código que estaba viendo.
melston
7

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

Algunos métodos que utilizará en el cuerpo de su 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 se da cuenta de esto, ya que simplemente llamaría myForm.bindFromRequest(). No tiene 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 encuentra con una llamada a un método que requiere una instancia de la solicitud. Ya que hacer un pedido disponibles, todo lo que necesita hacer es para marcarlo como implicit.

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

Usted le sugiere al compilador que está "bien" usar el objeto de solicitud enviado por el marco de Play (que le dimos el nombre de "solicitud" pero que podría haber usado solo "r" o "req") donde sea necesario, "a escondidas" .

myForm.bindFromRequest()

¿Míralo? ¡No está allí, pero está allí!

Simplemente sucede sin tener que colocarlo manualmente en cada lugar que sea necesario (pero puede pasarlo explícitamente, si lo desea, sin importar si está marcado implicito no):

myForm.bindFromRequest()(request)

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

¿Cuándo debe marcar la solicitud como implicit? Realmente solo necesita hacerlo si está utilizando métodos que declaran una lista implícita de parámetros esperando una instancia de la Solicitud . Pero para simplificarlo, puede acostumbrarse a marcar la solicitud implicit siempre . De esa manera puedes escribir un hermoso código conciso.

Peter Perháč
fuente
2
"De esa manera puedes escribir un hermoso código conciso". O, como señala @DanielDinnyes, un código bellamente ofuscado. Puede ser un verdadero dolor rastrear de dónde viene un implícito y, de hecho, pueden hacer que el código sea más difícil de leer y mantener si no tienes cuidado.
melston
7

En scala funciona implícitamente como :

Convertidor

Valor del parámetro inyector

Hay 3 tipos de uso de implícito

  1. Conversión de tipo implícita : convierte el error que produce la asignación en el tipo deseado

    val x: String = "1"

    val y: Int = x

String no es el subtipo de Int , por lo que se produce un error en la línea 2. Para resolver el error, el compilador buscará dicho método en el ámbito que tenga una palabra clave implícita y tome un String como argumento y devuelva un Int .

entonces

implicit def z(a:String):Int = 2

val x :String = "1"

val y:Int = x // compiler will use z here like val y:Int=z(x)

println(y) // result 2  & no error!
  1. Conversión implícita del receptor : generalmente, por receptor, llamamos a las propiedades del objeto, por ejemplo. métodos o variables. Entonces, para llamar a cualquier propiedad por un receptor, la propiedad debe ser miembro de la clase / objeto de ese receptor.

    class Mahadi{
    
    val haveCar:String ="BMW"
    
    }

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Aquí mahadi.haveTv producirá un error. Debido a que el compilador Scala buscará primero la haveTv propiedad a Mahadi receptor. No lo encontrarás. En segundo lugar, buscará un método en el alcance que tenga una palabra clave implícita que tome el objeto Mahadi como argumento y devuelva el objeto Johnny . Pero no tiene aquí. Entonces creará un error . Pero lo siguiente está bien.

class Mahadi{

val haveCar:String ="BMW"

}

class Johnny{

val haveTv:String = "Sony"

}

val mahadi = new Mahadi

implicit def z(a:Mahadi):Johnny = new Johnny

mahadi.haveTv // compiler will use z here like new Johnny().haveTv

println(mahadi.haveTv)// result Sony & no error
  1. Inyección implícita de parámetros : si llamamos a un método y no pasamos su valor de parámetro, causará un error. El compilador scala funciona así: primero intentará pasar el valor, pero no obtendrá ningún valor directo para el parámetro.

    def x(a:Int)= a
    
    x // ERROR happening

Segundo, si el parámetro tiene alguna palabra clave implícita, buscará cualquier valor en el alcance que tenga el mismo tipo de valor. Si no lo consigue, causará un error.

def x(implicit a:Int)= a

x // error happening here

Para resolver este problema, el compilador buscará un valor implícito que tenga el tipo de Int porque el parámetro a tiene una palabra clave implícita .

def x(implicit a:Int)=a

implicit val z:Int =10

x // compiler will use implicit like this x(z)
println(x) // will result 10 & no error.

Otro ejemplo:

def l(implicit b:Int)

def x(implicit a:Int)= l(a)

también podemos escribirlo como

def x(implicit a:Int)= l

Debido a que l tiene un parámetro implícito y dentro del alcance del cuerpo del método x , existe una variable local implícita (los parámetros son variables locales ) a, que es el parámetro de x , por lo que en el cuerpo del método x el valor de argumento implícito de la firma del método l es presentada por la variable de del método local x implícita (parámetro) aimplícitamente .

Entonces

 def x(implicit a:Int)= l

estará en un compilador como este

def x(implicit a:Int)= l(a)

Otro ejemplo:

def c(implicit k:Int):String = k.toString

def x(a:Int => String):String =a

x{
x => c
}

que causará error, porque c en x {x => c} necesita explícitamente el valor de paso en el argumento o val implícita en su alcance .

Entonces podemos hacer que el parámetro de la función literal sea explícitamente implícito cuando llamamos al método x

x{
implicit x => c // the compiler will set the parameter of c like this c(x)
}

Esto se ha utilizado en el método de acción de Play-Framework

in view folder of app the template is declared like
@()(implicit requestHreader:RequestHeader)

in controller action is like

def index = Action{
implicit request =>

Ok(views.html.formpage())  

}

si no menciona el parámetro de solicitud como implícito explícitamente, entonces debe haber sido escrito

def index = Action{
request =>

Ok(views.html.formpage()(request))  

}
MHJ
fuente
4

Además, en el caso anterior debería haber una only onefunción implícita cuyo tipo es double => Int. De lo contrario, el compilador se confunde y no se 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
rileyss
fuente
0

Un ejemplo muy básico de Implicits in scala.

Parámetros implícitos :

val value = 10
implicit val multiplier = 3
def multiply(implicit by: Int) = value * by
val result = multiply // implicit parameter wiil be passed here
println(result) // It will print 30 as a result

Nota: Aquí se multiplierpasará implícitamente a la función multiply. Los parámetros que faltan en la llamada a la función se buscan por tipo en el alcance actual, lo que significa que el código no se compilará si no hay una variable implícita de tipo Int en el alcance.

Conversiones implícitas :

implicit def convert(a: Double): Int = a.toInt
val res = multiply(2.0) // Type conversions with implicit functions
println(res)  // It will print 20 as a result

Nota: Cuando llamamos a la multiplyfunción que pasa un valor doble, el compilador intentará encontrar la función implícita de conversión en el ámbito actual, que convierte Inta Double(Como parámetro de multiplyaceptación de función Int). Si no hay una convertfunción implícita , el compilador no compilará el código.

Keshav Lodhi
fuente
0

Tenía exactamente la misma pregunta que usted y creo que debería compartir cómo comencé a entenderla con algunos ejemplos realmente simples (tenga en cuenta que solo cubre los casos de uso comunes).

Hay dos casos de uso comunes en el uso de Scala implicit.

  • Usarlo en una variable
  • Usarlo en una función

Los ejemplos son los siguientes

Usándolo en una variable . Como puede ver, si la implicitpalabra clave se usa en la última lista de parámetros, se usará la variable más cercana.

// Here I define a class and initiated an instance of this class
case class Person(val name: String)
val charles: Person = Person("Charles")

// Here I define a function
def greeting(words: String)(implicit person: Person) = person match {
  case Person(name: String) if name != "" => s"$name, $words"
    case _ => "$words"
}

greeting("Good morning") // Charles, Good moring

val charles: Person = Person("")
greeting("Good morning") // Good moring

Usándolo en una función . Como puede ver, si implicitse usa en la función, se usará el método de conversión de tipo más cercano.

val num = 10 // num: Int (of course)

// Here I define a implicit function
implicit def intToString(num: Int) = s"$num -- I am a String now!"

val num = 10 // num: Int (of course). Nothing happens yet.. Compiler believes you want 10 to be an Int

// Util...
val num: String = 10 // Compiler trust you first, and it thinks you have `implicitly` told it that you had a way to covert the type from Int to String, which the function `intToString` can do!
// So num is now actually "10 -- I am a String now!"
// console will print this -> val num: String = 10 -- I am a String now!

Espero que esto pueda ayudar.

RobotCharlie
fuente