Comprendre implicitement dans Scala

308

Je parcourais le didacticiel Scala Playframework et je suis tombé sur cet extrait de code qui m'a 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 cela:

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

et

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

à part le fait évident, ils ont des noms de méthode différents.

Quand devrais-je utiliser implicitet pourquoi?

Clive
la source
J'ai trouvé ce tutoriel vraiment utile: Tutoriel Scala - Apprenez à créer une fonction implicite
Adrian Moisa

Réponses:

391

J'expliquerai les principaux cas d'utilisation des implicites ci-dessous, mais pour plus de détails, voir le chapitre correspondant de la programmation dans 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 prises à partir du contexte dans lequel elles sont appelées. S'il n'y a pas de valeur implicite du bon type dans la portée, il ne sera pas compilé. Puisque la valeur implicite doit se résoudre en une seule valeur et pour éviter les conflits, c'est une bonne idée de rendre le type spécifique à son objectif, par exemple, ne nécessite pas vos méthodes pour trouver un implicite Int!

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 mauvais type pour le contexte, il recherchera une Functionvaleur implicite d'un type qui lui permettra de vérifier le type. Donc, si un Aest requis et qu'il trouve un B, il recherchera une valeur implicite de type B => Adans la portée (il vérifie également certains autres endroits comme dans les objets compagnons Bet A, s'ils existent). Puisque defs peut être "éta-développé" en Functionobjets, an implicit def xyz(arg: B): Afera de même.

Ainsi, la différence entre vos méthodes est que celle qui est marquée implicitsera insérée pour vous par le compilateur quand un Doubleest trouvé mais qu'un Intest requis.

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

fonctionnera de la même manière que

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

Dans la seconde, nous avons inséré la conversion manuellement; dans le premier, le compilateur faisait de même automatiquement. La conversion est requise en raison de l'annotation de type sur le côté gauche.


Concernant votre premier extrait de jeu:

Les actions sont expliquées sur cette page à partir de la documentation Play (voir aussi les documents API ). Vous utilisez

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

sur l' Actionobjet (qui est le compagnon du trait du même nom).

Nous devons donc fournir une fonction comme argument, qui peut être écrit comme un littéral sous la forme

request => ...

Dans un littéral de fonction, la partie précédant la =>est une déclaration de valeur, et peut être marquée implicitsi vous le souhaitez, comme dans toute autre valdéclaration. Ici, il request n'est pas nécessaire de marquer implicitcette valeur pour taper check, mais ce faisant, elle sera disponible comme 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 bindFromRequestméthode de la classe Form requiert un Requestargument implicite .

Luigi Plinge
la source
12
Merci pour votre réponse. Le lien pour le chapitre 21 est vraiment génial. Je l'apprécie.
Clive
14
Juste pour ajouter ceci, la vidéo suivante donne une excellente explication des implications ainsi que d'autres fonctionnalités de scala youtube.com/watch?v=IobLWVuD-CQ
Shakti
Aller à 24:25 dans la vidéo ci-dessus (pour ceux qui ne veulent pas écouter pendant les 55 minutes)
papigee
36

ATTENTION: contient judicieusement des sarcasmes! YMMV ...

La réponse de Luigi est complète et correcte. Celui-ci ne fait que l'étendre un peu avec un exemple de la façon dont vous pouvez glorieusement surexploiter les implicits , comme cela arrive assez souvent dans les projets Scala. En fait, si souvent, vous pouvez probablement le trouver dans l'un des guides 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")
}
Daniel Dinnyes
la source
1
Haha. Bon sens de l'humour.
Dét
1
J'apprécie l'humour. Ce genre de chose est l'une des raisons pour lesquelles j'ai cessé d'essayer d'apprendre la Scala il y a de nombreuses années et je n'y reviens que maintenant. Je ne savais jamais d'où venaient (beaucoup) les implicites dans le code que je regardais.
melston
7

Pourquoi et quand vous devez marquer le requestparamètre 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 remarquez pas nécessairement comme vous l'appelleriez simplement. myForm.bindFromRequest()Vous n'avez pas à fournir explicitement les arguments implicites. Non, vous quittez le compilateur pour rechercher tout objet candidat valide à passer chaque fois qu'il rencontre un appel de méthode qui nécessite une instance de la demande. Puisque vous faire une demande disponible, tout ce que vous devez faire est de le marquer comme implicit.

Vous le marquez explicitement comme disponible pour une utilisation implicite .

Vous indiquez au compilateur qu'il est "OK" d'utiliser l'objet de requête envoyé par le framework Play (que nous avons donné le nom "request" mais aurait pu utiliser simplement "r" ou "req") partout où cela était nécessaire, "en catimini" .

myForm.bindFromRequest()

tu le vois? ce n'est pas là, mais il est là!

Cela se produit simplement sans que vous ayez à l'insérer manuellement dans tous les endroits où vous en avez besoin (mais vous pouvez le transmettre explicitement, si vous le souhaitez, qu'il soit marqué implicitou non):

myForm.bindFromRequest()(request)

Sans le marquer comme implicite, vous devrez faire ce qui précède. Le marquer comme implicite n'est pas obligatoire.

Quand devez-vous marquer la demande implicit? Vous ne devez vraiment le faire que si vous utilisez des méthodes qui déclarent une liste de paramètres implicite attendant une instance de la demande . Mais pour faire simple, vous pouvez simplement prendre l'habitude de implicit toujours marquer la demande . De cette façon, vous pouvez simplement écrire un beau code laconique.

Peter Perháč
la source
2
"De cette façon, vous pouvez simplement écrire un beau code laconique." Ou, comme le souligne @DanielDinnyes, du code magnifiquement obscurci. Il peut être très difficile de découvrir d'où vient un implicite et ils peuvent en fait rendre le code plus difficile à lire et à maintenir si vous ne faites pas attention.
melston
7

In scala fonctionne implicitement comme :

Convertisseur

Injecteur de valeur de paramètre

Il existe 3 types d'utilisation implicite

  1. Conversion de type implicite : il convertit l'affectation produisant l'erreur en type prévu

    val x: String = "1"

    val y: Int = x

La chaîne n'est pas le sous-type d'Int , donc l'erreur se produit à la ligne 2. Pour résoudre l'erreur, le compilateur recherchera une telle méthode dans la portée qui a un mot clé implicite et prend une chaîne comme argument et renvoie 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 de récepteur implicite : Nous avons généralement par les propriétés de l'objet d'appel du récepteur, par exemple. méthodes ou variables. Ainsi, pour appeler une propriété par un récepteur, la propriété doit être membre de la classe / de l'objet de ce récepteur.

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

    class Johnny{

    val haveTv:String = "Sony"

    }

   val mahadi = new Mahadi



   mahadi.haveTv // Error happening

Ici, mahadi.haveTv produira une erreur. Parce que le compilateur scala va d' abord chercher la haveTv propriété à Mahadi récepteur. Il ne trouvera pas. Deuxièmement, il recherchera une méthode dans la portée ayant un mot clé implicite qui prend l' objet Mahadi comme argument et renvoie l' objet Johnny . Mais il n'a pas ici. Cela créera donc une erreur . Mais ce qui suit est correct.

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ètres : 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 essaiera de transmettre la valeur, mais il n'obtiendra 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 recherchera tout val dans la portée qui a 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 recherchera une valeur implicite ayant le type Int car le paramètre a a un 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.

Un 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 du corps de la méthode x , il existe une variable locale implicite (les paramètres sont des variables locales ) a qui est le paramètre de x , donc dans le corps de la méthode x la valeur de l'argument implicite de la signature de méthode est déposée par la variable locale de la méthode x implicite (paramètre) aimplicitement .

Alors

 def x(implicit a:Int)= l

sera dans le compilateur comme celui-ci

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

Un 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 d'un argument de valeur explicite ou d'une valeur implicite dans la portée .

Nous pouvons donc rendre le paramètre de la fonction littérale explicitement implicite lorsque nous appelons la méthode x

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

Cela a été utilisé dans la méthode d'action 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 vous ne mentionnez pas explicitement le paramètre de demande comme implicite, alors vous devez avoir été écrit-

def index = Action{
request =>

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

}
MHJ
la source
4

De plus, dans le cas ci-dessus, il devrait y avoir only oneune fonction implicite dont le type est double => Int. Sinon, le compilateur devient confus et ne compile 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
rileyss
la source
0

Un exemple très basique de Implicits in scala.

Paramètres implicites :

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

Remarque: ici multipliersera implicitement transmis à la fonction multiply. Les paramètres manquants de l'appel de fonction sont recherchés par type dans la portée actuelle, ce qui signifie que le code ne sera pas compilé s'il n'y a pas de variable implicite de type Int dans la portée.

Conversions implicites :

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

Remarque: Lorsque nous appelons une multiplyfonction en passant une valeur double, le compilateur essaiera de trouver la fonction implicite de conversion dans la portée actuelle, qui se convertit Inten Double(en tant que fonction multiplyaccepte le Intparamètre). S'il n'y a pas de convertfonction implicite , le compilateur ne compilera pas le code.

Keshav Lodhi
la source
0

J'avais exactement la même question que vous et je pense que je devrais partager comment j'ai commencé à la comprendre par quelques exemples très simples (notez que cela ne couvre que les cas d'utilisation courants).

Scala utilise deux cas d'utilisation courants implicit.

  • L'utiliser sur une variable
  • L'utiliser sur une fonction

Les exemples sont les suivants

L'utiliser sur une variable . Comme vous pouvez le voir, si le implicitmot-clé est utilisé dans la dernière liste de paramètres, la variable la plus proche sera utilisée.

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

L'utiliser sur une fonction . Comme vous pouvez le voir, si le implicitest utilisé sur la fonction, la méthode de conversion de type la plus proche sera utilisée.

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!

J'espère que cela peut vous aider.

RobotCharlie
la source