web-dev-qa-db-fra.com

Compréhension implicite dans Scala

Je faisais mon chemin dans le tutoriel playframework Scala et je suis tombé sur cet extrait de code qui m'avait laissé perplexe:

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

J'ai donc décidé d'enquêter et suis tombé sur ce post .

Je ne comprends toujours pas.

Quelle est la différence entre ceci:

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

et

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

autre que le fait évident, ils ont des noms de méthode différents.

Quand devrais-je utiliser implicit et pourquoi?

287
Clive

J'expliquerai les principaux cas d'utilisation des implicites ci-dessous, mais pour plus de détails, voir le chapitre correspondant de la Programmation en Scala .

Paramètres implicites

La liste finale des paramètres d'une méthode peut être marquée implicit, ce qui signifie que les valeurs seront extraites du contexte dans lequel elles sont appelées. S'il n'y a pas de valeur implicite du bon type dans la portée, la compilation n'est pas effectuée. Étant donné que la valeur implicite doit être résolue en une valeur unique et éviter les conflits, il est judicieux de définir le type en fonction de son objectif, par exemple. N’avez pas besoin de vos méthodes pour trouver un Int implicite!

exemple:

  // 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"

Conversions implicites

Lorsque le compilateur trouve une expression du type incorrect pour le contexte, il recherche une valeur implicite Function d'un type lui permettant de procéder à une vérification de type. Donc, si un A est requis et qu'il trouve un B, il recherchera une valeur implicite de type B => A dans la portée (il vérifie également d'autres endroits comme dans le B et A objets associés, s'ils existent). Puisque defs peut être "eta-étendu" en Function objets, une implicit def xyz(arg: B): A fera également l'affaire.

Ainsi, la différence entre vos méthodes est que celle marquée implicit sera insérée par le compilateur quand un Double sera trouvé mais qu'un Int sera requis.

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

fonctionnera comme

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

Dans le second cas, nous avons inséré la conversion manuellement. dans le premier le compilateur a fait la même chose automatiquement. La conversion est nécessaire en raison de l'annotation de type sur le côté gauche.


En ce qui concerne votre premier extrait de Play:

Les actions sont expliquées dans cette page dans la documentation de Play (voir aussi documents d'API ). Vous utilisez

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

sur l'objet Action (qui accompagne le trait du même nom).

Nous avons donc besoin de fournir une fonction comme argument, qui peut être écrit sous forme littérale sous la forme

request => ...

Dans un littéral de fonction, la partie précédant le => est une déclaration de valeur et peut être marquée implicit si vous le souhaitez, comme dans toute autre déclaration val. Ici, request ne doit pas être marqué implicit pour cela, mais ce sera disponible en tant que valeur implicite pour toutes les méthodes qui pourraient en avoir besoin dans la fonction (et bien sûr, elle peut également être utilisée explicitement). Dans ce cas particulier, cela a été fait car la méthode bindFromRequest de la classe Form requiert un argument implicite Request.

364

ATTENTION: contient judicieusement le sarcasme! YMMV ...

La réponse de Luigi est complète et correcte. Celui-ci est seulement pour l'étendre un peu avec un exemple de la façon dont vous pouvez abuser glorieusement implique, comme cela arrive souvent dans les projets Scala. En fait si souvent, vous pouvez probablement même le trouver dans l'un des "Meilleures pratiques".

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")
}
34
Daniel Dinnyes

Pourquoi et quand vous devez marquer le paramètre request comme implicit:

Certaines méthodes que vous utiliserez dans le corps de votre action ont une liste de paramètres implicites comme, par exemple, Form.scala définit une méthode:

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

Vous ne le remarquerez pas nécessairement comme vous appelleriez simplement myForm.bindFromRequest() Vous n'avez pas à fournir explicitement les arguments implicites. Non, vous laissez le compilateur pour rechercher tout objet candidat valide à transmettre chaque fois qu'il rencontre un appel de méthode nécessitant une instance de la demande. Puisque vous do avez une requête disponible, tout ce que vous avez à faire est de la marquer comme implicit.

Vous explicitement marquez-le comme étant disponible pour implicite utilisez.

Vous indiquez au compilateur qu'il est "correct" d'utiliser l'objet de requête envoyé par le framework Play (que nous avons appelé "requête" mais que nous aurions pu simplement utiliser "r" ou "req") si nécessaire, "en cachette" .

myForm.bindFromRequest()

le voir? ce n'est pas là, mais c'est est là!

Cela se produit sans que vous ayez à l'insérer manuellement à chaque endroit où vous en avez besoin (mais vous pouvez le passez explicitement, si vous le souhaitez, même s'il est marqué implicit ou pas):

myForm.bindFromRequest()(request)

Sans le marquer comme implicite, vous devez faire ce qui précède. En le marquant comme implicite, vous n'êtes pas obligé.

Quand devez-vous marquer la demande comme implicit? Vous n’avez vraiment besoin de le faire que si vous utilisez des méthodes qui déclarent une liste de paramètres implicites attend une instance de Request . Mais pour simplifier les choses, vous pouvez simplement prendre l’habitude de marquer la requête implicit toujours . De cette façon, vous pouvez simplement écrire de beaux codes laconiques.

6
Peter Perháč

De plus, dans le cas ci-dessus, il devrait y avoir only one fonction implicite de type double => Int. Sinon, le compilateur devient confus et ne compilera pas correctement.

//this won't compile

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

Dans scala implicite fonctionne comme:

Convertisseur

Injecteur de valeur de paramètre

Il existe 3 types d'utilisation d'Implicit

  1. Conversion de type implicite}: convertit l'affectation génératrice d'erreur en type prévu

    val x: String = "1"

    val y: Int = x

String n'est pas le sous-type de Int, donc une erreur survient à la ligne 2. Pour résoudre l'erreur, le compilateur recherche une telle méthode dans l'étendue qui a un mot clé implicite et prend une String comme argument et retourne un Int.

alors

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. Conversion implicite du récepteur: Nous avons généralement, par exemple, les propriétés de l'objet d'appel du destinataire. méthodes ou variables. Donc, pour appeler une propriété par un destinataire, la propriété doit être le membre de la classe/de l'objet de ce destinataire.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Ici, (mahadi.haveTv} générera une erreur. Parce que scala le compilateur va d'abord rechercher la propriété haveTv dans mahadi receiver. Il ne trouvera pas. Deuxièmement, il recherchera une méthode de portée ayant mot-clé implicite qui prend objet Mahadi comme argument et retourne objet Johnny. Mais ça n'a pas ici. Donc, cela va créer erreur. Mais ce qui suit est d'accord.

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. Injection implicite de paramètre: si nous appelons une méthode et ne transmettons pas sa valeur de paramètre, cela provoquera une erreur. Le compilateur scala fonctionne comme ceci - le premier essayera de transmettre la valeur, mais il n’aura aucune valeur directe pour le paramètre.

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

Deuxièmement, si le paramètre a un mot-clé implicite, il cherchera tous les val dans la portée qui ont le même type de valeur. Sinon, cela provoquera une erreur.

def x(implicit a:Int)= a

x // error happening here

Pour résoudre ce problème, le compilateur cherchera un valeur implicite ayant le type de Int parce que le paramètre a a mot clé implicite.

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.

n autre exemple:

def l(implicit b:Int)

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

on peut aussi l'écrire comme-

def x(implicit a:Int)= l

Parce que l a un paramètre implicite et dans la portée de corps de la méthode x, il y a un variable locale implicite _ ( les paramètres sont variables locales ) a qui est le paramètre de x, donc dans le corps de xméthode la signature de méthode l'argument implicite de la valeur est classé par la variable implicite locale de la méthode x (paramètre) a implicitement}.

Alors

 def x(implicit a:Int)= l

sera en compilateur comme ça

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

n autre exemple:

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

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

x{
x => c
}

cela provoquera une erreur, car c dans x {x => c} a besoin de passer explicitement en valeur ou en valeur implicite dans la portée.

Ainsi, nous pouvons rendre paramètre explicitement implicite de la fonction littéral lorsque nous appelons le méthode x

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

Ceci a été utilisé dans méthode d'action de Play-Framework

def index = Action{
implicit request =>

Ok(views.html.formpage)

}
0
MHJ