web-dev-qa-db-fra.com

Quelles sont les règles précises pour quand vous pouvez omettre les parenthèses, les points, les accolades, = (fonctions), etc.?

Quelles sont les règles précises pour quand vous pouvez omettre (omettre) des parenthèses, des points, des accolades, = (fonctions), etc.?

Par exemple,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service est mon objet
  • def findAllPresentations: Option[List[Presentation]]
  • votes renvoie List[Vote]
  • doit et être sont les deux fonctions des spécifications

Pourquoi je ne peux pas y aller:

(service findAllPresentations get first votes size) must be equalTo(2)

?

L'erreur du compilateur est:

"RestServicesSpecTest.this.service.findAllPresentations de type Option [List [com.sharca.Presentation]] ne prend pas de paramètres"

Pourquoi pense-t-il que j'essaie de transmettre un paramètre? Pourquoi dois-je utiliser des points pour chaque appel de méthode?

Pourquoi faut-il (service.findAllPresentations get first votes size) être égal (2) donne:

"introuvable: valeur d'abord"

Pourtant, le "doit être égal à 2" de (service.findAllPresentations.get.first.votes.size) doit être égal à 2, c'est-à-dire que le chaînage de méthode fonctionne bien? - chaîne d'objet chaîne chaîne param.

J'ai parcouru le Scala et je ne trouve pas vraiment d'explication complète.

Est-ce en fait, comme l'explique Rob H dans la question Stack Overflow Quels caractères puis-je omettre dans Scala?, que le seul cas d'utilisation valide pour omettre le '.' est pour les opérations de style "opérande opérande opérande", et non pour le chaînage de méthode?

100
Antony Stubbs

Définitions des classes:

val ou var peuvent être omis des paramètres de classe, ce qui rendra le paramètre privé.

L'ajout de var ou val le rendra public (c'est-à-dire que les accesseurs de méthode et les mutateurs sont générés).

{} peut être omis si la classe n'a pas de corps, c'est-à-dire

class EmptyClass

Instanciation de classe:

Les paramètres génériques peuvent être omis s'ils peuvent être déduits par le compilateur. Cependant, notez que si vos types ne correspondent pas, le paramètre type est toujours déduit de sorte qu'il corresponde. Donc, sans spécifier le type, vous n'obtiendrez peut-être pas ce que vous attendez - c'est-à-dire, étant donné

class D[T](val x:T, val y:T);

Cela vous donnera une erreur de type (Int trouvé, chaîne attendue)

var zz = new D[String]("Hi1", 1) // type error

Alors que cela fonctionne bien:

var z = new D("Hi1", 1)
== D{def x: Any; def y: Any}

Parce que le paramètre de type, T, est inféré comme le supertype le moins courant des deux - Any.


Définitions des fonctions:

= peut être supprimé si la fonction renvoie Unit (rien).

{} pour le corps de la fonction peut être supprimé si la fonction est une instruction unique, mais uniquement si l'instruction renvoie une valeur (vous avez besoin de = signe), c'est-à-dire

def returnAString = "Hi!"

mais cela ne fonctionne pas:

def returnAString "Hi!" // Compile error - '=' expected but string literal found."

Le type de retour de la fonction peut être omis s'il peut être déduit (une méthode récursive doit avoir son type de retour spécifié).

() peut être supprimé si la fonction ne prend aucun argument, c'est-à-dire

def endOfString {
  return "myDog".substring(2,1)
}

qui par convention est réservé aux méthodes qui n'ont pas d'effets secondaires - plus à ce sujet plus tard.

() n'est pas réellement abandonné en soi lors de la définition d'un paramètre passe par nom, mais il s'agit en fait d'un tout à fait différent sémantiquement notation, c'est-à-dire

def myOp(passByNameString: => String)

Dit myOp prend un paramètre passe-par-nom, qui se traduit par une chaîne (c'est-à-dire qu'il peut être un bloc de code qui renvoie une chaîne) par opposition aux paramètres de fonction,

def myOp(functionParam: () => String)

qui dit myOp prend une fonction qui n'a aucun paramètre et retourne une chaîne.

(Attention, les paramètres de nom de passe sont compilés en fonctions; cela rend simplement la syntaxe plus agréable.)

() peut être supprimé dans la définition du paramètre de la fonction si la fonction ne prend qu'un seul argument, par exemple:

def myOp2(passByNameString:(Int) => String) { .. } // - You can drop the ()
def myOp2(passByNameString:Int => String) { .. }

Mais si cela prend plus d'un argument, vous devez inclure le ():

def myOp2(passByNameString:(Int, String) => String) { .. }

Déclarations:

. peut être supprimé pour utiliser la notation d'opérateur, qui peut uniquement être utilisée pour les opérateurs infixes (opérateurs de méthodes qui prennent des arguments). Voir réponse de Daniel pour plus d'informations.

  • . peut également être supprimé pour la liste des fonctions postfixées

  • () peut être supprimé pour les opérateurs postfixés list.tail

  • () ne peut pas être utilisé avec des méthodes définies comme:

    def aMethod = "hi!" // Missing () on method definition
    aMethod // Works
    aMethod() // Compile error when calling method
    

Parce que cette notation est réservée par convention aux méthodes qui n'ont pas d'effets secondaires, comme List # tail (c'est-à-dire que l'invocation d'une fonction sans effets secondaires signifie que la fonction n'a aucun effet observable, à l'exception de sa valeur de retour).

  • () peut être supprimé pour la notation d'opérateur lors de la transmission d'un seul argument

  • () peut être requis pour utiliser des opérateurs postfix qui ne se trouvent pas à la fin d'une instruction

  • () peut être requis pour désigner des instructions imbriquées, des fins de fonctions anonymes ou pour des opérateurs qui prennent plus d'un paramètre

Lorsque vous appelez une fonction qui prend une fonction, vous ne pouvez pas omettre le () de la définition de fonction interne, par exemple:

def myOp3(paramFunc0:() => String) {
    println(paramFunc0)
}
myOp3(() => "myop3") // Works
myOp3(=> "myop3") // Doesn't work

Lorsque vous appelez une fonction qui prend un paramètre par nom, vous ne pouvez pas spécifier l'argument en tant que fonction anonyme sans paramètre. Par exemple, étant donné:

def myOp2(passByNameString:Int => String) {
  println(passByNameString)
}

Vous devez l'appeler comme:

myOp("myop3")

ou

myOp({
  val source = sourceProvider.source
  val p = myObject.findNameFromSource(source)
  p
})

mais non:

myOp(() => "myop3") // Doesn't work

IMO, la surutilisation de la suppression des types de retour peut être nuisible pour la réutilisation du code. Il suffit de regarder les spécifications pour un bon exemple de lisibilité réduite en raison du manque d'informations explicites dans le code. Le nombre de niveaux d'indirection pour réellement comprendre quel est le type d'une variable peut être fou. J'espère que de meilleurs outils peuvent éviter ce problème et garder notre code concis.

(OK, dans la quête pour compiler une réponse plus complète et concise (si j'ai raté quelque chose, ou que j'ai quelque chose de mal/inexact, veuillez commenter), j'ai ajouté au début de la réponse. Veuillez noter que ce n'est pas une langue spécifications, donc je n'essaye pas de le rendre exactement académique correct - juste plus comme une carte de référence.)

39
Antony Stubbs

Vous semblez être tombé sur la réponse. Quoi qu'il en soit, je vais essayer de clarifier les choses.

Vous pouvez omettre le point lorsque vous utilisez les notations préfixe, infixe et suffixe - la soi-disant notation notation opérateur. Lors de l'utilisation de la notation d'opérateur, et seulement alors, vous pouvez omettre la parenthèse s'il y a moins de deux paramètres passés à la méthode.

Maintenant, la notation d'opérateur est une notation pour appel de méthode, ce qui signifie qu'elle ne peut pas être utilisée en l'absence de l'objet qui est appelé.

Je vais détailler brièvement les notations.

Préfixe:

Seulement ~, !, + et - peut être utilisé en notation de préfixe. Il s'agit de la notation que vous utilisez lorsque vous écrivez !flag ou val liability = -debt.

Infixe:

C'est la notation où la méthode apparaît entre un objet et ses paramètres. Les opérateurs arithmétiques correspondent tous ici.

Postfixe (également suffixe):

Cette notation est utilisée lorsque la méthode suit un objet et ne reçoit aucun paramètre . Par exemple, vous pouvez écrire list tail, et c'est la notation postfixée.

Vous pouvez enchaîner des appels de notation infixe sans problème, tant qu'aucune méthode n'est curry. Par exemple, j'aime utiliser le style suivant:

(list
 filter (...)
 map (...)
 mkString ", "
)

C'est la même chose que:

list filter (...) map (...) mkString ", "

Maintenant, pourquoi j'utilise des parenthèses ici, si le filtre et la carte prennent un seul paramètre? C'est parce que je leur passe des fonctions anonymes. Je ne peux pas mélanger les définitions de fonctions anonymes avec le style infix parce que j'ai besoin d'une limite pour la fin de ma fonction anonyme. De plus, la définition des paramètres de la fonction anonyme peut être interprétée comme le dernier paramètre de la méthode infixe.

Vous pouvez utiliser infix avec plusieurs paramètres:

string substring (start, end) map (_ toInt) mkString ("<", ", ", ">")

Les fonctions curry sont difficiles à utiliser avec la notation infixe. Les fonctions de pliage en sont un exemple clair:

(0 /: list) ((cnt, string) => cnt + string.size)
(list foldLeft 0) ((cnt, string) => cnt + string.size)

Vous devez utiliser des parenthèses en dehors de l'appel d'infixe. Je ne suis pas sûr des règles exactes en jeu ici.

Parlons maintenant de postfix. Postfix peut être difficile à utiliser, car il ne peut jamais être utilisé ailleurs qu'à la fin d'une expression. Par exemple, vous ne pouvez pas effectuer les opérations suivantes:

 list tail map (...)

Parce que la queue n'apparaît pas à la fin de l'expression. Vous ne pouvez pas faire cela non plus:

 list tail length

Vous pouvez utiliser la notation infixe en utilisant des parenthèses pour marquer la fin des expressions:

 (list tail) map (...)
 (list tail) length

Notez que la notation postfixée est déconseillée car elle peut être dangereuse .

J'espère que cela a dissipé tous les doutes. Sinon, déposez simplement un commentaire et je verrai ce que je peux faire pour l'améliorer.

83
Daniel C. Sobral

Une collection de citations donnant un aperçu des différentes conditions ...

Personnellement, je pensais qu'il y aurait plus dans la spécification. Je suis sûr qu'il doit y en avoir, je ne cherche tout simplement pas les bons mots ...

Il existe cependant quelques sources, et je les ai rassemblées ensemble, mais rien de vraiment complet/complet/compréhensible/qui m'explique les problèmes ci-dessus ...:

"Si un corps de méthode a plus d'une expression, vous devez l'entourer d'accolades {...}. Vous pouvez omettre les accolades si le corps de méthode n'a qu'une seule expression."

De chapitre 2, "Tapez moins, faites plus", de Programmation Scala :

"Le corps de la méthode supérieure vient après le signe égal '='. Pourquoi un signe égal? Pourquoi pas juste des accolades {...}, comme en Java? Parce que les points-virgules, les types de retour de fonction, les listes d'arguments de méthode et même les accolades sont parfois omis, l'utilisation d'un signe égal empêche plusieurs ambiguïtés d'analyse possibles. L'utilisation d'un signe égal nous rappelle également que même les fonctions sont des valeurs dans Scala, ce qui est cohérent avec la prise en charge par Scala de la programmation fonctionnelle, décrite plus en détail au chapitre 8, Programmation fonctionnelle dans Scala. "

De chapitre 1, "De zéro à soixante: présentation de Scala", de Programmation de Scala :

"Une fonction sans paramètres peut être déclarée sans parenthèses, auquel cas elle doit être appelée sans parenthèses. Cela permet de prendre en charge le principe d'accès uniforme, de sorte que l'appelant ne sait pas si le symbole est une variable ou une fonction sans paramètres.

Le corps de la fonction est précédé de "=" s'il renvoie une valeur (c'est-à-dire que le type de retour est autre chose que Unit), mais le type de retour et le "=" peuvent être omis lorsque le type est Unit (c'est-à-dire qu'il ressemble à une procédure par opposition à une fonction).

Les accolades autour du corps ne sont pas nécessaires (si le corps est une seule expression); plus précisément, le corps d'une fonction n'est qu'une expression, et toute expression à plusieurs parties doit être placée entre accolades (une expression à une partie peut éventuellement être placée entre accolades). "

"Les fonctions avec zéro ou un argument peuvent être appelées sans le point et les parenthèses. Mais toute expression peut être entourée de parenthèses, vous pouvez donc omettre le point tout en utilisant des parenthèses.

Et puisque vous pouvez utiliser des accolades partout où vous pouvez utiliser des parenthèses, vous pouvez omettre le point et mettre des accolades, qui peuvent contenir plusieurs instructions.

Les fonctions sans arguments peuvent être appelées sans les parenthèses. Par exemple, la fonction length () sur String peut être invoquée en tant que "abc" .length plutôt que "abc" .length (). Si la fonction est une fonction Scala définie sans parenthèses, alors la fonction doit être appelée sans parenthèses.

Par convention, les fonctions sans arguments ayant des effets secondaires, comme println, sont appelées entre parenthèses; ceux sans effets secondaires sont appelés sans parenthèses. "

De l'article de blog Scala Syntax Primer:

"Une définition de procédure est une définition de fonction où le type de résultat et le signe égal sont omis; son expression de définition doit être un bloc. Par exemple, def f (ps) {stats} est équivalent à def f (ps): Unit = {stats }.

Exemple 4.6.3 Voici une déclaration et une définition d'une procédure nommée write:

trait Writer {
    def write(str: String)
}
object Terminal extends Writer {
    def write(str: String) { System.out.println(str) }
}

Le code ci-dessus est implicitement complété par le code suivant:

trait Writer {
    def write(str: String): Unit
}
object Terminal extends Writer {
    def write(str: String): Unit = { System.out.println(str) }
}"

De la spécification de langue:

"Avec les méthodes qui ne prennent qu'un seul paramètre, Scala permet au développeur de remplacer le. Par un espace et d'omettre les parenthèses, activant la syntaxe d'opérateur montrée dans notre exemple d'opérateur d'insertion. Cette syntaxe est utilisée dans d'autres endroits de l'API Scala, comme la construction d'instances Range:

val firstTen:Range = 0 to 9

Là encore, to (Int) est une méthode Vanilla déclarée à l'intérieur d'une classe (il y a en fait quelques conversions de type plus implicites ici, mais vous obtenez la dérive). "

From Scala for Java Refugees Part 6: Getting Over Java:

"Maintenant, lorsque vous essayez" m 0 ", Scala supprime qu'il s'agit d'un opérateur unaire, au motif qu'il n'est pas valide (~,!, - et +). Il constate que "m" est un objet valide - c'est une fonction, pas une méthode, et toutes les fonctions sont des objets.

Comme "0" n'est pas un identifiant valide Scala, il ne peut être ni un infixe ni un opérateur postfix. Par conséquent, Scala se plaint qu'il attendait ";" - qui séparerait deux expressions (presque) valides: "m" et "0". Si vous l'avez inséré, alors il se plaindrait que m nécessite soit un argument, soit, à défaut, un "_" pour le transformer en un fonction partiellement appliquée. "

"Je crois que le style de syntaxe d'opérateur ne fonctionne que lorsque vous avez un objet explicite sur le côté gauche. La syntaxe est destinée à vous permettre d'exprimer les opérations de style" opérande d'opérande d'opérande "de manière naturelle."

Quels caractères puis-je omettre dans Scala?

Mais ce qui m'embrouille aussi, c'est cette citation:

"Il doit y avoir un objet pour recevoir un appel de méthode. Par exemple, vous ne pouvez pas faire" println "Hello World!" "Car println a besoin d'un destinataire d'objet. Vous pouvez faire "Console println" Hello World! "" Qui satisfait le besoin. "

Parce que pour autant que je puisse voir, il y a un objet pour recevoir l'appel ...

12
Antony Stubbs

Il n'y en a pas. Vous recevrez probablement des conseils pour savoir si la fonction a ou non des effets secondaires. C'est faux. La correction consiste à ne pas utiliser d'effets secondaires dans la mesure raisonnable autorisée par Scala. Dans la mesure où cela n'est pas possible, tous les paris sont désactivés. Tous les paris. L'utilisation de parenthèses est un élément de l'ensemble "tous" et est superflue. Il ne fournit aucune valeur une fois que tous les paris sont désactivés.

Ce conseil est essentiellement une tentative de système d'effets qui échoue (à ne pas confondre avec: est moins utile que les autres systèmes d'effets).

Essayez de ne pas provoquer d'effets secondaires. Après cela, acceptez que tous les paris soient désactivés. Se cacher derrière une notation syntaxique de facto pour un système d'effets ne peut que causer du tort.

2
user92983

En fait, en deuxième lecture, c'est peut-être la clé:

Avec des méthodes qui ne prennent qu'un seul paramètre, Scala permet au développeur de remplacer le .par un espace et d'omettre les parenthèses

Comme mentionné sur le blog: http://www.codecommit.com/blog/scala/scala-for-Java-refugees-part-6 .

Alors peut-être que c'est en fait un "sucre de syntaxe" très strict qui seulement fonctionne là où vous appelez effectivement une méthode, sur un objet, qui prend un paramètre . par exemple.

1 + 2
1.+(2)

Et rien d'autre.

Cela expliquerait mes exemples dans la question.

Mais comme je l'ai dit, si quelqu'un pouvait indiquer exactement où dans la spécification de langue cela est spécifié, ce serait très apprécié.

Ok, un gentil camarade (paulp_ de #scala) a indiqué où se trouvent ces informations dans la langue:

6.12.3: La priorité et l'associativité des opérateurs déterminent le regroupement des parties d'une expression comme suit.

  • S'il existe plusieurs opérations d'infixe dans une expression, les opérateurs de priorité supérieure se lient plus étroitement que les opérateurs de priorité inférieure.
  • S'il y a des opérations d'infixe consécutives e0 op1 e1 op2. . .opn en avec les opérateurs op1,. . . , opn de même priorité, alors tous ces opérateurs doivent avoir la même associativité. Si tous les opérateurs sont associatifs à gauche, la séquence est interprétée comme (... (E0 op1 e1) op2...) Opn en. Sinon, si tous les opérateurs sont associés à droite, la séquence est interprétée comme e0 op1 (e1 op2 (.. .Opn en)...).
  • Les opérateurs postfixes ont toujours une priorité plus faible que les opérateurs infixes. Par exemple. e1 op1 e2 op2 est toujours équivalent à (e1 op1 e2) op2.

L'opérande de droite d'un opérateur associatif de gauche peut consister en plusieurs arguments entre parenthèses, par ex. e op (e1,..., en). Cette expression est alors interprétée comme e.op (e1,..., En).

Une opération binaire associative à gauche e1 op e2 est interprétée comme e1.op (e2). Si op est associé à droite, la même opération est interprétée comme {val x = e1; e2.op (x)}, où x est un nouveau nom.

Hmm - pour moi, cela ne correspond pas à ce que je vois ou je ne le comprends tout simplement pas;)

2
Antony Stubbs

Je trouve plus facile de suivre cette règle de base: dans les expressions, les espaces alternent entre méthodes et paramètres. Dans votre exemple, (service.findAllPresentations.get.first.votes.size) must be equalTo(2) Analyse comme (service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2)). Notez que les parenthèses autour des 2 ont une associativité plus élevée que les espaces. Les points ont également une associativité plus élevée, donc (service.findAllPresentations.get.first.votes.size) must be.equalTo(2) Serait analysé comme (service.findAllPresentations.get.first.votes.size).must(be.equalTo(2)).

service findAllPresentations get first votes size must be equalTo 2 Analyse comme service.findAllPresentations(get).first(votes).size(must).be(equalTo).2.

2
Mario Camou