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

106

Quelles sont les règles précises pour lesquelles vous pouvez omettre (omettre) les parenthèses, les points, les 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 Retour List[Vote]
  • doit et être sont tous deux des fonctions de 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'essaye de passer un paramètre? Pourquoi dois-je utiliser des points pour chaque appel de méthode?

Pourquoi doit (service.findAllPresentations get first votes size)être égal à (2) aboutir à:

"introuvable: valeur en premier"

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éthodes fonctionne bien? - Paramètre de chaîne de chaîne d'objets.

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

Est-ce en fait, comme Rob H l'explique 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érateur opérande", et non pour le chaînage de méthodes?

Antony Stubbs
la source

Réponses:

87

Vous semblez avoir trébuché sur la réponse. Quoi qu'il en soit, je vais essayer de le clarifier.

Vous pouvez omettre le point lorsque vous utilisez les notations de préfixe, d'infixe et de suffixe - la soi-disant notation d'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 l' 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é dans la notation préfixe. C'est la notation que vous utilisez lorsque vous écrivez !flagou 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 tiennent tous ici.

Postfix (é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 postfixe.

Vous pouvez enchaîner les appels de notation infixe sans problème, tant qu'aucune méthode n'est curée. 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 est-ce que 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 infixe car j'ai besoin d'une limite pour la fin de ma fonction anonyme. De plus, la définition de paramètre de la fonction anonyme peut être interprétée comme le dernier paramètre de la méthode infix.

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.

Maintenant, parlons de postfix. Postfix peut être difficile à utiliser, car il ne peut être utilisé nulle part sauf à 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 non plus faire cela:

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

Daniel C. Sobral
la source
ahh, donc vous dites que dans ma déclaration: (((((realService findAllPresentations) get) first) votes) size) doit être égal à 2 - get, first, votes et size sont tous des opérateurs postfix, car ils ne prennent aucun paramètre ? alors je me demande ce qui doit, être et être égal pour être ...
Antony Stubbs
Je ne dis rien de tel, même si je suis presque sûr que cela doit être le cas. :-) "be" est probablement un objet d'aide, pour rendre la syntaxe plus jolie. Ou, plus précisément, pour permettre l'utilisation de la notation infixe avec "must".
Daniel C.Sobral
Eh bien, get est Option.get, le premier est list.first, votes est une propriété de classe de cas et size est list.size. Que pensez-vous maintenant?
Antony Stubbs
Ah oui - tout cela est renforcé par le fait que "(realService findPresentation 1) .get.id doit être égal à 1" fonctionne - car Service # findPresentations (id: Int) est un opérateur infixe semble-t-il. Cool - je pense que je comprends maintenant. :)
Antony Stubbs
42

Définitions de classe:

valou varpeut ê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 des accesseurs de méthode et des 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, alors le paramètre de type est toujours déduit pour 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 commun des deux - Any.


Définitions des fonctions:

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

{}car le corps de la fonction peut être supprimé si la fonction est une seule instruction, mais uniquement si l'instruction renvoie une valeur (vous avez besoin du =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 abandonné 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 - nous en parlerons plus tard.

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

def myOp(passByNameString: => String)

Dit que myOp prend un paramètre pass-by-name, qui aboutit à une chaîne (c'est-à-dire qu'il peut s'agir d'un bloc de code qui renvoie une chaîne) par opposition aux paramètres de fonction,

def myOp(functionParam: () => String)

qui dit myOpprend une fonction qui n'a aucun paramètre et renvoie une chaîne.

(Remarquez que les paramètres de passage par nom sont compilés en fonctions; cela rend simplement la syntaxe plus agréable.)

() peut être supprimée dans la définition du paramètre de 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:

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

  • . peut également être supprimé pour les fonctions postfix list tail

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

  • () ne peut pas être utilisé avec les 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 pour les 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 nécessaire d'utiliser des opérateurs postfix qui ne sont pas à la fin d'une instruction

  • () peut être nécessaire 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 suit:

myOp("myop3")

ou

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

mais non:

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

OMI, la surutilisation de la suppression des types de retour peut être préjudiciable à 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 déterminer réellement quel est le type d'une variable peut être fou. Espérons que de meilleurs outils peuvent éviter ce problème et garder notre code concis.

(OK, dans le but de compiler une réponse plus complète et concise (si j'ai manqué quelque chose, ou 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écification, donc je n'essaye pas de la rendre exactement correcte sur le plan académique - mais plutôt comme une carte de référence.)

Antony Stubbs
la source
10
Je pleure. Qu'est-ce que c'est.
Profpatsch
12

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 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."

Du chapitre 2, "Tapez moins, faites plus", de Programming Scala :

"Le corps de la méthode supérieure vient après le signe égal '='. Pourquoi un signe égal? Pourquoi pas seulement 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 le support de Scala de la programmation fonctionnelle, décrit plus en détail au chapitre 8, Programmation fonctionnelle dans Scala. "

Du chapitre 1, "Zero to Sixty: Introducing Scala", de Programming Scala :

"Une fonction sans paramètre 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 comportant plusieurs parties doit être placée entre accolades (une expression avec une partie peut éventuellement être 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 et toujours utiliser des parenthèses.

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

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

Par convention, les fonctions sans arguments qui ont des effets secondaires, comme println, sont appelées avec des 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} équivaut à 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) }
}"

À partir de la spécification de langue:

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

val firstTen:Range = 0 to 9

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

De Scala pour les réfugiés Java, partie 6: surmonter Java :

"Maintenant, quand vous essayez" m 0 ", Scala le rejette comme étant un opérateur unaire, au motif qu'il n'est pas valide (~,!, - et +). Il trouve 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 Scala valide, il ne peut être ni un infixe ni un opérateur de suffixe. Par conséquent, Scala se plaint d'avoir attendu ";" - 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 une 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 des opérations de style" opérande d'opérande "de manière naturelle."

Quels personnages puis-je omettre dans Scala?

Mais ce qui me trouble 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 le println a besoin d'un destinataire d'objet. Vous pouvez faire "Console println" Hello World! "" Qui satisfait le besoin. "

Car d'après ce que je peux voir, il y a un objet pour recevoir l'appel ...

Antony Stubbs
la source
1
Ok, alors j'ai essayé de lire la source Specs pour obtenir des indices et woah. C'est un excellent exemple des problèmes avec le code magique - trop de mixins, d'inférence de type et de conversions implicites et de paramètres implicites. C'est si difficile à comprendre de l'extérieur vers l'intérieur! Pour les grandes bibliothèques comme celle-ci, un meilleur outillage pourrait faire des merveilles ... un jour ...
Antony Stubbs
3

Je trouve plus facile de suivre cette règle empirique: dans les expressions, les espaces alternent entre les méthodes et les 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, ils (service.findAllPresentations.get.first.votes.size) must be.equalTo(2)seraient donc analysés comme (service.findAllPresentations.get.first.votes.size).must(be.equalTo(2)).

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

Mario Camou
la source
2

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. avec un espace et omettre les parenthèses

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

Alors peut-être est-ce en fait un "sucre de syntaxe" très strict qui ne fonctionne que 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 la langue cela est spécifié, ce serait très apprécié.

Ok, un gentil garçon (paulp_ de #scala) a indiqué où dans la spécification du langage cette information se trouve:

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

  • S'il y a plusieurs opérations d'infixe dans une expression, les opérateurs avec une priorité plus élevée se lient plus étroitement que les opérateurs avec une priorité inférieure.
  • S'il y a des opérations d'infixe consécutives e0 op1 e1 op2. . .opn fr avec les opérateurs op1,. . . , ouvert de la 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 associatifs à droite, la séquence est interprétée comme e0 op1 (e1 op2 (.. .Opn en)...).
  • Les opérateurs Postfix ont toujours une priorité inférieure à celle des 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 être constitué de plusieurs arguments entre parenthèses, par exemple 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 associative à 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;)

Antony Stubbs
la source
hmm, pour ajouter encore à la confusion, ceci est également valable: (((((realService findAllPresentations) get) first) votes) size) doit être égal à 2, mais pas si je supprime l'une de ces paires de parenthèses ...
Antony Stubbs
2

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 les effets secondaires dans la mesure raisonnable autorisée par Scala. Dans la mesure où il ne le peut pas, tous les paris sont ouverts. Tout paris. L'utilisation de parenthèses est un élément de l'ensemble "tout" et est superflue. Il ne fournit aucune valeur une fois que tous les paris sont ouverts.

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

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

user92983
la source
Eh bien, mais c'est le problème lorsque vous travaillez avec un langage hybride OO / fonctionnel, n'est-ce pas? Dans n'importe quel exemple pratique, vous allez vouloir avoir des fonctions d'effets secondaires ... Pouvez-vous nous indiquer quelques informations sur les «systèmes d'effets»? Je pense que la citation la plus à souligner est la suivante: "Une fonction sans paramètres peut être déclarée sans parenthèses, auquel cas elle doit être appelée sans parenthèses Cela fournit un support pour 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ètre. ".
Antony Stubbs