J'essaie de représenter une fonction qui ne prend aucun argument et ne renvoie aucune valeur (je simule la fonction setTimeout en JavaScript, si vous devez savoir.)
case class Scheduled(time : Int, callback : => Unit)
ne compile pas, disant "les paramètres` val 'peuvent ne pas être appelés par nom "
case class Scheduled(time : Int, callback : () => Unit)
compile, mais doit être invoqué étrangement, au lieu de
Scheduled(40, { println("x") } )
Je dois faire ça
Scheduled(40, { () => println("x") } )
Ce qui fonctionne aussi, c'est
class Scheduled(time : Int, callback : Unit => Unit)
mais est invoquée d'une manière encore moins sensible
Scheduled(40, { x : Unit => println("x") } )
(Que serait une variable de type Unit?) Ce que je veux bien sûr, c'est un constructeur qui peut être invoqué comme je l'invoquerais s'il s'agissait d'une fonction ordinaire:
Scheduled(40, println("x") )
Donnez son biberon à bébé!
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
.Réponses:
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:
Si je l'appelle comme ça
Ensuite, le code s'exécutera comme ceci
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 aFunction0
. Autrement dit, une fonction qui ne prend aucun paramètre et renvoie quelque chose. Cela équivaut, par exemple, à appeler la méthodesize()
- 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,
est un littéral de fonction anonyme d'arité 0, dont le type est
On pourrait donc écrire:
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 typeUnit
. D'autres façons de l'écrire seraient(Unit) => Type
ouFunction1[Unit, Type]
. Le fait est que ... il est peu probable que ce soit ce que l'on veut. LeUnit
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,
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 retournantUnit
:Parce
andThen
que n'est défini que surFunction1
, et que les fonctions que nous chaînons retournentUnit
, nous avons dû les définir comme étant de typeFunction1[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
est un littéral pour
() => Unit
, alorsserait 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).la source
case ... =>
, donc je ne l'ai pas mentionnée. Triste mais vrai. :-)1
, le caractère'a'
, la chaîne"abc"
ou la fonction() => println("here")
, pour certains exemples). Il peut être passé en argument, stocké dans des variables, etc. Un "bloc de code" est une délimitation syntaxique d'instructions - ce n'est pas une valeur, il ne peut pas être transmis, ou quelque chose comme ça.(Unit) => Type
vs() => Type
- le premier est aFunction1[Unit, Type]
, tandis que le second est aFunction0[Type]
.Le
case
modificateur rend impliciteval
de chaque argument au constructeur. Par conséquent (comme quelqu'un l'a noté) si vous supprimez,case
vous pouvez utiliser un paramètre d'appel par nom. Le compilateur pourrait probablement le permettre de toute façon, mais il pourrait surprendre les gens s'il créaitval callback
au lieu de se transformer enlazy val callback
.Lorsque vous passez à
callback: () => Unit
maintenant, votre cas prend simplement une fonction plutôt qu'un paramètre d'appel par nom. Évidemment, la fonction peut être stockée,val callback
donc il n'y a pas de problème.Le moyen le plus simple d'obtenir ce que vous voulez (
Scheduled(40, println("x") )
où un paramètre d'appel par nom est utilisé pour passer un lambda) est probablement d'ignorercase
et de créer explicitement leapply
que vous ne pouviez pas obtenir en premier lieu:Utilisé:
la source
Dans la question, vous souhaitez simuler la fonction SetTimeOut en JavaScript. Sur la base des réponses précédentes, j'écris le code suivant:
Dans REPL, nous pouvons obtenir quelque chose comme ceci:
Notre simulation ne se comporte pas exactement de la même manière que SetTimeOut, car notre simulation est une fonction bloquante, mais SetTimeOut n'est pas bloquante.
la source
Je le fais de cette façon (je ne veux simplement pas interrompre l'application):
et l'appelle
la source