Quali sono le regole precise per quando puoi omettere parentesi, punti, parentesi graffe, = (funzioni), ecc.?


106

Quali sono le regole precise per quando puoi omettere (omettere) parentesi, punti, parentesi graffe, = (funzioni), ecc.?

Per esempio,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service è il mio oggetto
  • def findAllPresentations: Option[List[Presentation]]
  • votes ritorna List[Vote]
  • must e be sono entrambe funzioni di specifiche

Perché non posso andare:

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

?

L'errore del compilatore è:

"RestServicesSpecTest.this.service.findAllPresentations di tipo Option [List [com.sharca.Presentation]] non accetta parametri"

Perché pensa che stia tentando di passare un parametro? Perché devo usare i punti per ogni chiamata al metodo?

Perché deve (service.findAllPresentations get first votes size)essere uguale a (2) risulta in:

"non trovato: prima il valore"

Tuttavia, "deve essere uguale a 2" di (service.findAllPresentations.get.first.votes.size)deve essere uguale a 2, ovvero il concatenamento del metodo funziona bene? - catena di oggetti catena catena param.

Ho esaminato il libro e il sito web di Scala e non riesco a trovare una spiegazione esauriente.

In effetti, come spiega Rob H nella domanda Stack Overflow Quali personaggi posso omettere in Scala? , che l'unico caso d'uso valido per omettere il carattere "." è per operazioni in stile "operando operatore operando" e non per il concatenamento di metodi?

Risposte:


87

Sembra che tu sia incappato nella risposta. In ogni caso, cercherò di chiarire.

È possibile omettere il punto quando si utilizzano le notazioni con prefisso, infisso e suffisso, la cosiddetta notazione dell'operatore . Durante l'utilizzo della notazione operatore, e solo allora, è possibile omettere la parentesi se ci sono meno di due parametri passati al metodo.

Ora, la notazione dell'operatore è una notazione per la chiamata al metodo , il che significa che non può essere utilizzata in assenza dell'oggetto che viene chiamato.

Descriverò brevemente le annotazioni.

Prefisso:

Solo ~, !, +e -può essere utilizzato in prefix notazione. Questa è la notazione che usi quando scrivi !flago val liability = -debt.

Infix:

Questa è la notazione in cui il metodo appare tra un oggetto ei suoi parametri. Gli operatori aritmetici stanno tutti qui.

Postfisso (anche suffisso):

Questa notazione viene utilizzata quando il metodo segue un oggetto e non riceve parametri . Ad esempio, puoi scrivere list tail, e questa è la notazione postfissa.

Puoi concatenare chiamate di notazione infissa senza problemi, a patto che nessun metodo sia curato. Ad esempio, mi piace usare il seguente stile:

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

È la stessa cosa di:

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

Ora, perché sto usando le parentesi qui, se filtro e mappa accettano un singolo parametro? È perché sto passando loro funzioni anonime. Non posso mescolare definizioni di funzioni anonime con lo stile infix perché ho bisogno di un limite per la fine della mia funzione anonima. Inoltre, la definizione del parametro della funzione anonima potrebbe essere interpretata come l'ultimo parametro del metodo infix.

Puoi usare infisso con più parametri:

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

Le funzioni curry sono difficili da usare con la notazione infissa. Le funzioni di piegatura ne sono un chiaro esempio:

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

Devi usare le parentesi al di fuori della chiamata infisso. Non sono sicuro delle regole esatte in gioco qui.

Ora parliamo di postfix. Postfix può essere difficile da usare, perché non può mai essere usato ovunque tranne che alla fine di un'espressione . Ad esempio, non puoi fare quanto segue:

 list tail map (...)

Perché la coda non appare alla fine dell'espressione. Non puoi farlo neanche:

 list tail length

Puoi usare la notazione infissa usando le parentesi per contrassegnare la fine delle espressioni:

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

Notare che la notazione con suffisso è sconsigliata perché potrebbe non essere sicura .

Spero che questo abbia chiarito tutti i dubbi. In caso contrario, lascia un commento e vedrò cosa posso fare per migliorarlo.


ahh, quindi stai dicendo che nella mia dichiarazione: (((((realService findAllPresentations) get) first) votes) size) deve essere uguale a 2 - ottieni, prima, i voti e le dimensioni sono tutti operatori di suffisso, perché non accettano parametri ? quindi mi chiedo cosa deve, essere e uguale a essere ...
Antony Stubbs

Non dico niente del genere, anche se sono abbastanza sicuro che debba essere così. :-) "be" è probabilmente un oggetto helper, per rendere più bella la sintassi. O, più specificamente, per abilitare l'uso della notazione infissa con "must".
Daniel C. Sobral

Bene, get è Option.get, First è list.first, votes è una proprietà della classe case e size è list.size. Cosa pensi ora?
Antony Stubbs,

Ah sì, tutto questo è rafforzato dal fatto che "(realService findPresentation 1) .get.id deve essere uguale a 1" funziona - come sembra che Service # findPresentations (id: Int) sia un operatore infisso. Fantastico - Penso di aver capito ora. :)
Antony Stubbs

42

Definizioni di classe:

valoppure varpuò essere omesso dai parametri di classe che renderanno il parametro privato.

L'aggiunta di var o val renderà pubblico (ovvero, vengono generati metodi di accesso e mutatori).

{} può essere omesso se la classe non ha un corpo, ovvero

class EmptyClass

Istanziazione della classe:

I parametri generici possono essere omessi se possono essere dedotti dal compilatore. Tuttavia, tieni presente che se i tuoi tipi non corrispondono, il parametro del tipo viene sempre dedotto in modo che corrisponda. Quindi senza specificare il tipo, potresti non ottenere ciò che ti aspetti, cioè dato

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

Questo ti darà un errore di tipo (Int trovato, stringa prevista)

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

Mentre questo funziona bene:

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

Perché il parametro di tipo, T, viene dedotto come il supertipo meno comune dei due: Any.


Definizioni di funzione:

= può essere eliminato se la funzione restituisce Unit (niente).

{}per il corpo della funzione può essere eliminato se la funzione è una singola istruzione, ma solo se l'istruzione restituisce un valore (è necessario il =segno), ovvero,

def returnAString = "Hi!"

ma questo non funziona:

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

Il tipo restituito della funzione può essere omesso se può essere dedotto (un metodo ricorsivo deve avere il tipo restituito specificato).

() può essere eliminato se la funzione non accetta alcun argomento, ovvero

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

che per convenzione è riservato ai metodi che non hanno effetti collaterali - ne parleremo più avanti.

()non è effettivamente scartato di per sé quando si definisce un parametro di passaggio per nome , ma in realtà è una notazione abbastanza semanticamente diversa, cioè

def myOp(passByNameString: => String)

Dice che myOp accetta un parametro pass-by-name, che si traduce in una stringa (cioè, può essere un blocco di codice che restituisce una stringa) al contrario dei parametri della funzione,

def myOp(functionParam: () => String)

che dice che myOpaccetta una funzione che ha zero parametri e restituisce una stringa.

(Intendiamoci, i parametri pass-by-name vengono compilati in funzioni; rende semplicemente più gradevole la sintassi.)

() può essere rilasciato nella definizione del parametro della funzione se la funzione accetta solo un argomento, ad esempio:

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

Ma se richiede più di un argomento, è necessario includere il ():

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

dichiarazioni:

.può essere abbandonato per usare la notazione dell'operatore, che può essere usata solo per gli operatori infisso (operatori di metodi che accettano argomenti). Vedi la risposta di Daniel per maggiori informazioni.

  • . può anche essere rilasciato per la coda dell'elenco delle funzioni di suffisso

  • () può essere rilasciato per gli operatori postfix list.tail

  • () non può essere utilizzato con metodi definiti come:

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

Perché questa notazione è riservata per convenzione ai metodi che non hanno effetti collaterali, come List # tail (ovvero, l'invocazione di una funzione senza effetti collaterali significa che la funzione non ha alcun effetto osservabile, ad eccezione del suo valore restituito).

  • () può essere eliminato per la notazione dell'operatore quando si passa un singolo argomento

  • () potrebbe essere richiesto di utilizzare operatori postfissa che non sono alla fine di un'istruzione

  • () può essere richiesto di designare istruzioni annidate, estremità di funzioni anonime o per operatori che accettano più di un parametro

Quando si chiama una funzione che accetta una funzione, non è possibile omettere () dalla definizione della funzione interna, ad esempio:

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

Quando si chiama una funzione che accetta un parametro per nome, non è possibile specificare l'argomento come una funzione anonima senza parametri. Ad esempio, dato:

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

Devi chiamarlo come:

myOp("myop3")

o

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

ma no:

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

IMO, un uso eccessivo dei tipi di restituzione della caduta può essere dannoso per il riutilizzo del codice. Basta guardare le specifiche per un buon esempio di leggibilità ridotta a causa della mancanza di informazioni esplicite nel codice. Il numero di livelli di riferimento indiretto per capire effettivamente quale sia il tipo di variabile può essere pazzo. Si spera che strumenti migliori possano evitare questo problema e mantenere il nostro codice conciso.

(OK, nel tentativo di compilare una risposta più completa e concisa (se ho perso qualcosa o ho ottenuto qualcosa di sbagliato / impreciso per favore commenta), ho aggiunto all'inizio della risposta. Tieni presente che questa non è una lingua specifica, quindi non sto cercando di renderlo esattamente accademicamente corretto, solo più simile a una scheda di riferimento.)


10
Sto piangendo. Cos'è questo.
Profpatsch

12

Una raccolta di citazioni che danno un'idea delle varie condizioni ...

Personalmente, ho pensato che ci sarebbe stato di più nelle specifiche. Sono sicuro che deve esserci, solo che non sto cercando le parole giuste ...

Tuttavia, ci sono un paio di fonti e le ho raccolte insieme, ma niente di veramente completo / esaustivo / comprensibile / che mi spieghi i problemi di cui sopra ...:

"Se il corpo di un metodo ha più di un'espressione, è necessario circondarlo con parentesi graffe {…}. Puoi omettere le parentesi graffe se il corpo del metodo ha una sola espressione."

Dal capitolo 2, "Digita di meno, fai di più", di Programmazione in Scala :

"Il corpo del metodo superiore viene dopo il segno di uguale" = ". Perché un segno di uguale? Perché non solo le parentesi graffe {…}, come in Java? Perché i punti e virgola, i tipi di ritorno delle funzioni, gli elenchi di argomenti del metodo e persino le parentesi graffe a volte vengono omessi, l'uso di un segno di uguale impedisce diverse possibili ambiguità di analisi. L'uso di un segno di uguale ci ricorda anche che anche le funzioni sono valori in Scala, il che è coerente con il supporto di Scala della programmazione funzionale, descritto in maggior dettaglio nel Capitolo 8, Programmazione funzionale in Scala Scala."

Dal capitolo 1, "Zero to Sixty: Introducing Scala", di Programming Scala :

"Una funzione senza parametri può essere dichiarata senza parentesi, nel qual caso deve essere chiamata senza parentesi. Ciò fornisce il supporto per il principio di accesso uniforme, in modo tale che il chiamante non sappia se il simbolo è una variabile o una funzione senza parametri.

Il corpo della funzioneèpreceduto da "=" se restituisce un valore (cioè il tipo restituitoèqualcosa di diverso da Unità), ma il tipo restituito e "=" possono essere omessi quando il tipoèUnità (cioè sembra una procedura al contrario di una funzione).

Le parentesi graffe intorno al corpo non sono richieste (se il corpo è una singola espressione); più precisamente, il corpo di una funzione è solo un'espressione e qualsiasi espressione con più parti deve essere racchiusa tra parentesi graffe (un'espressione con una parte può facoltativamente essere racchiusa tra parentesi graffe). "

"Le funzioni con zero o un argomento possono essere chiamate senza il punto e le parentesi. Ma ogni espressione può avere parentesi attorno ad essa, quindi puoi omettere il punto e continuare a utilizzare le parentesi.

E poiché puoi usare le parentesi ovunque puoi usare le parentesi, puoi omettere il punto e inserire le parentesi graffe, che possono contenere più istruzioni.

Le funzioni senza argomenti possono essere chiamate senza le parentesi. Ad esempio, la funzione length () su String può essere invocata come "abc" .length invece che "abc" .length (). Se la funzione è una funzione Scala definita senza parentesi, la funzione deve essere chiamata senza parentesi.

Per convenzione, le funzioni senza argomenti che hanno effetti collaterali, come println, vengono chiamate tra parentesi; quelli senza effetti collaterali sono chiamati senza parentesi ".

Dal post del blog Scala Syntax Primer :

"Una definizione di procedura è una definizione di funzione in cui il tipo di risultato e il segno di uguale sono omessi; la sua espressione che lo definisce deve essere un blocco. Ad esempio, def f (ps) {stats} è equivalente a def f (ps): Unit = {stats }.

Esempio 4.6.3 Ecco una dichiarazione e una definizione di una procedura chiamata write:

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

Il codice precedente viene completato in modo implicito nel codice seguente: The code above is implicitly completed to the following code:

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

Dalla specifica della lingua:

"Con metodi che accettano solo un singolo parametro, Scala consente allo sviluppatore di sostituire. Con uno spazio e omettere le parentesi, abilitando la sintassi dell'operatore mostrata nel nostro esempio di operatore di inserimento. Questa sintassi viene utilizzata in altri punti dell'API Scala, come come costruzione di istanze di Range:

val firstTen:Range = 0 to 9

Anche in questo caso, to (Int) è un metodo vanilla dichiarato all'interno di una classe (in realtà ci sono alcune conversioni di tipo implicite qui, ma si ottiene la deriva). "

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

"Ora, quando provi" m 0 ", Scala scarta che sia un operatore unario, sulla base di non essere un operatore valido (~,!, - e +). Trova che" m "è un oggetto valido - è una funzione, non un metodo e tutte le funzioni sono oggetti.

Poiché "0" non è un identificatore Scala valido, non può essere né un infisso né un operatore postfisso. Pertanto, Scala si lamenta di aspettarsi ";" - che separerebbe due (quasi) espressioni valide: "m" e "0". Se lo inserissi, allora si lamenterebbe che m richiede un argomento o, in caso contrario, un "_" per trasformarlo in una funzione parzialmente applicata. "

"Credo che lo stile della sintassi dell'operatore funzioni solo quando hai un oggetto esplicito sul lato sinistro. La sintassi ha lo scopo di consentire di esprimere le operazioni in stile" operando operatore operando "in modo naturale".

Quali caratteri posso omettere in Scala?

Ma quello che mi confonde anche è questa citazione:

"Deve esserci un oggetto per ricevere una chiamata al metodo. Ad esempio, non è possibile eseguire" println "Hello World!" "Poiché println ha bisogno di un destinatario dell'oggetto. Puoi fare "Console println" Hello World! "" Che soddisfa la necessità. "

Perché per quanto posso vedere, non v'è un oggetto per ricevere la chiamata ...


1
Ok, quindi ho provato a leggere la fonte delle specifiche per ottenere alcuni indizi e woah. Questo è un ottimo esempio dei problemi con il codice magico: troppi mixin, inferenza di tipo e conversioni implicite e parametri impliciti. È così difficile da capire dall'esterno dentro! Per grandi biblioteche come quella, una migliore strumentazione potrebbe fare delle meraviglie ... un giorno ...
Antony Stubbs

3

Trovo più semplice seguire questa regola pratica: nelle espressioni gli spazi si alternano tra metodi e parametri. Nel tuo esempio, (service.findAllPresentations.get.first.votes.size) must be equalTo(2)analizza come (service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2)). Notare che le parentesi attorno al 2 hanno un'associatività maggiore rispetto agli spazi. I punti hanno anche una maggiore associatività, quindi (service.findAllPresentations.get.first.votes.size) must be.equalTo(2)analizzerebbero come (service.findAllPresentations.get.first.votes.size).must(be.equalTo(2)).

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


2

In realtà, in seconda lettura, forse questa è la chiave:

Con metodi che accettano solo un singolo parametro, Scala consente allo sviluppatore di sostituire il file. con uno spazio e ometti le parentesi

Come menzionato nel post del blog: http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6 .

Quindi forse questo è in realtà uno "zucchero di sintassi" molto rigoroso che funziona solo quando si sta effettivamente chiamando un metodo, su un oggetto, che accetta un parametro . per esempio

1 + 2
1.+(2)

E nient'altro.

Questo spiegherebbe i miei esempi nella domanda.

Ma come ho detto, se qualcuno potesse indicare di essere esattamente dove nella specifica della lingua questo è specificato, sarebbe molto apprezzato.

Ok, un bravo ragazzo (paulp_ da #scala) ha sottolineato dove nelle specifiche della lingua queste informazioni sono:

6.12.3: La precedenza e l'associatività degli operatori determinano il raggruppamento di parti di un'espressione come segue.

  • Se in un'espressione sono presenti più operazioni su infisso, gli operatori con precedenza più alta si legano più strettamente degli operatori con precedenza più bassa.
  • Se ci sono operazioni su infissi consecutive e0 op1 e1 op2. . .opn it con gli operatori op1,. . . , con la stessa precedenza, tutti questi operatori devono avere la stessa associatività. Se tutti gli operatori sono associativi a sinistra, la sequenza viene interpretata come (... (E0 op1 e1) op2...) Opn en. Altrimenti, se tutti gli operatori sono associativi a destra, la sequenza viene interpretata come e0 op1 (e1 op2 (.. .Opn en)...).
  • Gli operatori Postfix hanno sempre una precedenza inferiore rispetto agli operatori Infix. Ad esempio e1 op1 e2 op2 è sempre equivalente a (e1 op1 e2) op2.

L'operando di destra di un operatore associativo di sinistra può essere costituito da diversi argomenti racchiusi tra parentesi, ad esempio e op (e1,..., En). Questa espressione viene quindi interpretata come e.op (e1,..., En).

Un'operazione binaria associativa a sinistra e1 op e2 è interpretata come e1.op (e2). Se op è associativa a destra, la stessa operazione viene interpretata come {val x = e1; e2.op (x)}, dove x è un nuovo nome.

Hmm - per me non coincide con quello che vedo o semplicemente non lo capisco;)


hmm, per aggiungere ulteriore confusione, questo è anche valido: ((((realService findAllPresentations) get) first) votes) size) deve essere uguale a 2, ma non se rimuovo una qualsiasi di quelle coppie di parentesi ...
Antony Stubbs

2

Non ce ne sono. Probabilmente riceverai consigli sul fatto che la funzione abbia o meno effetti collaterali. Questo è falso. La correzione consiste nel non utilizzare gli effetti collaterali nella misura ragionevole consentita da Scala. Nella misura in cui non è possibile, tutte le scommesse sono annullate. Tutte le scommesse. L'uso delle parentesi è un elemento dell'insieme "all" ed è superfluo. Non fornisce alcun valore una volta che tutte le scommesse sono disattivate.

Questo consiglio è essenzialmente un tentativo di un sistema di effetti che fallisce (da non confondere con: è meno utile di altri sistemi di effetti).

Cerca di non avere effetti collaterali. Dopodiché, accetta che tutte le scommesse siano disattivate. Nascondersi dietro una notazione sintattica de facto per un sistema di effetti può e fa, solo causare danni.


Bene, ma questo è il problema quando lavori con un linguaggio ibrido OO / Funzionale, giusto? In qualsiasi esempio pratico vorrai avere funzioni di effetti collaterali ... Puoi indicarci alcune informazioni sui "sistemi di effetti"? Penso che la citazione più puntata sia la "Una funzione senza parametri può essere dichiarata senza parentesi, nel qual caso deve essere chiamata senza parentesi Questo fornisce il supporto per il Principio di accesso uniforme, in modo tale che il chiamante non sa se il il simbolo è una variabile o una funzione senza parametri. ".
Antony Stubbs
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.