Appel par nom: => Type
La => Type
notation signifie appel par nom, qui est l'une des nombreuses façons dont les paramètres peuvent être passés. Si vous ne les connaissez pas, je vous recommande de prendre le temps de lire cet article de Wikipédia, même si de nos jours, il est principalement appelé par valeur et par référence.
Cela signifie que ce qui est passé est remplacé par le nom de la valeur à l'intérieur de la fonction. Par exemple, prenez cette fonction:
def f(x: => Int) = x * x
Si je l'appelle comme ça
var y = 0
f { y += 1; y }
Ensuite, le code s'exécutera comme ceci
{ y += 1; y } * { y += 1; y }
Bien que cela soulève le point de savoir ce qui se passe en cas de conflit de nom d'identifiant. Dans l'appel par nom traditionnel, un mécanisme appelé substitution évitant la capture a lieu pour éviter les conflits de noms. Dans Scala, cependant, cela est implémenté d'une autre manière avec le même résultat - les noms d'identificateurs à l'intérieur du paramètre ne peuvent pas faire référence ou masquer les identificateurs dans la fonction appelée.
Il y a d'autres points liés à l'appel par nom dont je parlerai après avoir expliqué les deux autres.
Fonctions 0-arity: () => Type
La syntaxe () => Type
correspond au type de a Function0
. Autrement dit, une fonction qui ne prend aucun paramètre et renvoie quelque chose. Cela équivaut, par exemple, à appeler la méthode size()
- elle ne prend aucun paramètre et renvoie un nombre.
Il est cependant intéressant de noter que cette syntaxe est très similaire à la syntaxe d'un littéral de fonction anonyme , ce qui est la cause d'une certaine confusion. Par exemple,
() => println("I'm an anonymous function")
est un littéral de fonction anonyme d'arité 0, dont le type est
() => Unit
On pourrait donc écrire:
val f: () => Unit = () => println("I'm an anonymous function")
Cependant, il est important de ne pas confondre le type avec la valeur.
Unité => Type
Il ne s'agit en fait que d'un Function1
, dont le premier paramètre est de type Unit
. D'autres façons de l'écrire seraient (Unit) => Type
ou Function1[Unit, Type]
. Le fait est que ... il est peu probable que ce soit ce que l'on veut. Le Unit
but principal du type est d'indiquer une valeur qui ne l'intéresse pas, donc cela n'a pas de sens de recevoir cette valeur.
Considérez, par exemple,
def f(x: Unit) = ...
Que pourrait-on faire avec x
? Il ne peut avoir qu'une seule valeur, il n'est donc pas nécessaire de le recevoir. Une utilisation possible serait de chaîner des fonctions retournant Unit
:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Parce andThen
que n'est défini que sur Function1
, et que les fonctions que nous chaînons retournent Unit
, nous avons dû les définir comme étant de type Function1[Unit, Unit]
pour pouvoir les chaîner.
Sources de confusion
La première source de confusion est de penser que la similitude entre le type et le littéral qui existe pour les fonctions 0-arité existe également pour l'appel par nom. En d'autres termes, penser ça, parce que
() => { println("Hi!") }
est un littéral pour () => Unit
, alors
{ println("Hi!") }
serait un littéral pour => Unit
. Ce n'est pas. C'est un bloc de code , pas un littéral.
Une autre source de confusion est que Unit
la valeur du type est écrite ()
, ce qui ressemble à une liste de paramètres 0-arity (mais ce n'est pas le cas).
case class Scheduled(time: Int)(callback: => Unit)
. Cela fonctionne car la liste des paramètres secondaires n'est pas exposée publiquement, ni incluse dans les méthodesequals
/ généréeshashCode
.