Quelle est la différence entre =>, () => et Unit =>

153

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é!

Malvolio
la source
3
Une autre façon d'utiliser les classes de cas avec des paramètres par nom, est de les placer dans une liste de paramètres secondaires, par exemple 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éthodes equals/ générées hashCode.
nilskp
Quelques aspects plus intéressants concernant les différences entre les paramètres par nom et les fonctions 0-arité se trouvent dans cette question et la réponse. C'est en fait ce que je cherchais quand j'ai trouvé cette question.
lex82

Réponses:

234

Appel par nom: => Type

La => Typenotation 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 () => Typecorrespond 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) => Typeou Function1[Unit, Type]. Le fait est que ... il est peu probable que ce soit ce que l'on veut. Le Unitbut 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 andThenque 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 Unitla valeur du type est écrite (), ce qui ressemble à une liste de paramètres 0-arity (mais ce n'est pas le cas).

Daniel C. Sobral
la source
Je devrai peut-être être le premier à voter contre le vote après deux ans. Quelqu'un s'interroge sur la syntaxe case => à Noël, et je ne peux pas recommander cette réponse comme canonique et complète! Vers quoi le monde arrive-t-il? Peut-être que les Mayas étaient juste à côté d'une semaine. Ont-ils figuré correctement dans les années bissextiles? L'heure d'été?
som-snytt
@ som-snytt Eh bien, la question ne posait pas de question case ... =>, donc je ne l'ai pas mentionnée. Triste mais vrai. :-)
Daniel C. Sobral
1
@Daniel C. Sobral pourriez-vous s'il vous plaît expliquer "C'est un bloc de code, pas un littéral." partie. Alors, quelle est la différence exacte entre deux?
nish1013
2
@ nish1013 Un "littéral" est une valeur (l'entier 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.
Daniel C. Sobral
1
@Alex C'est la même différence que (Unit) => Typevs () => Type- le premier est a Function1[Unit, Type], tandis que le second est a Function0[Type].
Daniel C. Sobral
36
case class Scheduled(time : Int, callback :  => Unit)

Le casemodificateur rend implicite valde chaque argument au constructeur. Par conséquent (comme quelqu'un l'a noté) si vous supprimez, casevous 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éait val callbackau lieu de se transformer en lazy val callback.

Lorsque vous passez à callback: () => Unitmaintenant, votre cas prend simplement une fonction plutôt qu'un paramètre d'appel par nom. Évidemment, la fonction peut être stockée, val callbackdonc 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'ignorer caseet de créer explicitement le applyque vous ne pouviez pas obtenir en premier lieu:

class Scheduled(val time: Int, val callback: () => Unit) {
    def doit = callback()
}

object Scheduled {
    def apply(time: Int, callback: => Unit) =
        new Scheduled(time, { () => callback })
}

Utilisé:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x
Ben Jackson
la source
3
Pourquoi ne pas garder une classe de cas et remplacer simplement la valeur par défaut? De plus, le compilateur ne peut pas traduire un nom en un val paresseux, car ils ont une sémantique intrinsèquement différente, le paresseux est au plus une fois et le nom a à chaque référence
Viktor Klang
@ViktorKlang Comment pouvez-vous remplacer la méthode d'application par défaut d'une classe de cas? stackoverflow.com/questions/2660975/…
Sawyer
object ClassName {def apply (…):… =…}
Viktor Klang
Quatre ans plus tard et je me rends compte que la réponse que j'ai choisie ne répondait qu'à la question du titre, pas à celle que j'avais réellement (à laquelle celle-ci répond).
Malvolio
1

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:

class Scheduled(time: Int, cb: => Unit) {
  private def runCb = cb
}

object Scheduled {
  def apply(time: Int, cb: => Unit) = {
    val instance = new Scheduled(time, cb)
    Thread.sleep(time*1000)
    instance.runCb
  }
}

Dans REPL, nous pouvons obtenir quelque chose comme ceci:

scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b

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.

Jeff Xu
la source
0

Je le fais de cette façon (je ne veux simplement pas interrompre l'application):

case class Thing[A](..., lazy: () => A) {}
object Thing {
  def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}

et l'appelle

Thing.of(..., your_value)
Alexey Rykhalskiy
la source