Jakie są dokładne zasady, kiedy można pominąć nawiasy, kropki, nawiasy klamrowe, = (funkcje) itp.?

106

Jakie są dokładne zasady, kiedy można pominąć (pomijać) nawiasy, kropki, nawiasy klamrowe, = (funkcje) itp.?

Na przykład,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service jest moim przedmiotem
  • def findAllPresentations: Option[List[Presentation]]
  • votes zwroty List[Vote]
  • muszą i być są funkcjami specyfikacji

Dlaczego nie mogę iść:

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

?

Błąd kompilatora to:

„RestServicesSpecTest.this.service.findAllPresentations typu Option [List [com.sharca.Presentation]] nie przyjmuje parametrów”

Dlaczego myśli, że próbuję przekazać parametr? Dlaczego muszę używać kropek dla każdego wywołania metody?

Dlaczego musi (service.findAllPresentations get first votes size)być equalTo (2) skutkuje:

„nie znaleziono: najpierw wartość”

Jednak „must be equalTo 2” (service.findAllPresentations.get.first.votes.size)musi być równe 2, czyli łączenie metod działa dobrze? - parametr łańcucha łańcucha obiektów.

Przejrzałem książkę i stronę internetową Scala i nie mogę znaleźć wyczerpującego wyjaśnienia.

Czy tak jest w rzeczywistości, jak wyjaśnia Rob H w pytaniu Stack Overflow Które znaki mogę pominąć w Scali? , że jedyny prawidłowy przypadek użycia dla pominięcia „.” jest przeznaczony do operacji w stylu „operand operator operand”, a nie do łączenia metod?

Antony Stubbs
źródło

Odpowiedzi:

87

Wydaje się, że natknąłeś się na odpowiedź. W każdym razie spróbuję to wyjaśnić.

Możesz pominąć kropkę używając notacji z przedrostkiem, wrostkiem i postfiksem - tzw. Notacja operatorowa . Korzystając z notacji operatorowej i tylko wtedy, można pominąć nawias, jeśli do metody przekazano mniej niż dwa parametry.

Teraz notacja operatora jest notacją dla wywołania metody , co oznacza, że ​​nie może być używana w przypadku braku wywoływanego obiektu.

Pokrótce opiszę szczegółowo zapisy.

Prefiks:

Tylko ~, !, +i -może być stosowany w notacji prefiks. To jest notacja, której używasz podczas pisania !flaglub val liability = -debt.

Infiks:

To jest notacja, w której metoda pojawia się między obiektem a jego parametrami. Wszystkie operatory arytmetyczne pasują tutaj.

Postfix (także sufiks):

Ta notacja jest używana, gdy metoda podąża za obiektem i nie otrzymuje żadnych parametrów . Na przykład możesz pisać list taili to jest notacja postfiksowa.

Możesz łączyć wywołania notacji wrostków bez problemu, o ile żadna metoda nie jest aktywna. Na przykład lubię używać następującego stylu:

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

To to samo, co:

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

Dlaczego używam tutaj nawiasów, jeśli filtr i mapa przyjmują jeden parametr? To dlatego, że przekazuję im anonimowe funkcje. Nie mogę mieszać definicji funkcji anonimowych ze stylem wrostków, ponieważ potrzebuję granicy na końcu mojej funkcji anonimowej. Ponadto definicja parametru funkcji anonimowej może być interpretowana jako ostatni parametr metody infix.

Możesz użyć infiksu z wieloma parametrami:

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

Funkcje curry są trudne w użyciu z notacją wrostków. Funkcje składania są tego wyraźnym przykładem:

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

Musisz użyć nawiasów poza wywołaniem wrostka. Nie jestem pewien, jakie dokładnie zasady tu obowiązują.

Porozmawiajmy teraz o Postfixie. Postfix może być trudny w użyciu, ponieważ nigdy nie można go użyć nigdzie poza końcem wyrażenia . Na przykład nie możesz wykonać następujących czynności:

 list tail map (...)

Ponieważ ogon nie pojawia się na końcu wyrażenia. Nie możesz też tego zrobić:

 list tail length

Możesz użyć notacji wrostkowej, używając nawiasów do oznaczenia końca wyrażeń:

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

Zauważ, że notacja postfiksowa nie jest zalecana, ponieważ może być niebezpieczna .

Mam nadzieję, że to rozwiało wszystkie wątpliwości. Jeśli nie, po prostu zostaw komentarz, a zobaczę, co mogę zrobić, aby to poprawić.

Daniel C. Sobral
źródło
ahh, więc mówisz, że w moim oświadczeniu: ((((realService findAllPresentations) get) first) głosów) size) musi być równe 2 - get, first, głosy i rozmiar to wszystkie operatory postfiksowe, ponieważ nie przyjmują żadnych parametrów ? więc zastanawiam się, co musi, być i równe ...
Antony Stubbs,
Nic takiego nie mówię, chociaż jestem prawie pewien, że tak musi być. :-) "be" jest prawdopodobnie obiektem pomocniczym, aby upiększyć składnię. Lub, bardziej szczegółowo, aby umożliwić użycie notacji wrostek z „musi”.
Daniel C. Sobral
Cóż, to Option.get, pierwsze to list.first, głosy to właściwość klasy sprawy, a rozmiar to list.size. Co o tym myślisz?
Antony Stubbs,
Ach tak - to wszystko jest wzmocnione faktem, że "(realService findPresentation 1) .get.id musi być równy 1" - jak się wydaje, Service # findPresentations (id: Int) jest operatorem wrostkowym. Super - myślę, że teraz rozumiem. :)
Antony Stubbs
42

Definicje klas:

vallub varmoże zostać pominięty w parametrach klasy, co spowoduje, że parametr stanie się prywatny.

Dodanie var lub val spowoduje, że stanie się on publiczny (to znaczy, że zostaną wygenerowane metody dostępu i mutatory).

{} można pominąć, jeśli klasa nie ma treści, to znaczy

class EmptyClass

Instancja klasy:

Parametry ogólne można pominąć, jeśli mogą być wywnioskowane przez kompilator. Należy jednak pamiętać, że jeśli typy nie są zgodne, parametr typu jest zawsze pobierany, aby był zgodny. Tak więc bez określenia typu możesz nie otrzymać tego, czego oczekujesz - to znaczy danego

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

To da ci błąd typu (znaleziono Int, oczekiwany ciąg)

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

Podczas gdy to działa dobrze:

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

Ponieważ parametr typu, T, jest wywnioskowany jako najmniej powszechny nadtyp z dwóch - Any.


Definicje funkcji:

= można usunąć, jeśli funkcja zwraca Jednostka (nic).

{}ponieważ treść funkcji może zostać usunięta, jeśli funkcja jest pojedynczą instrukcją, ale tylko wtedy, gdy instrukcja zwraca wartość (potrzebujesz =znaku), to znaczy

def returnAString = "Hi!"

ale to nie działa:

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

Zwracany typ funkcji można pominąć, jeśli można go wywnioskować (metoda rekurencyjna musi mieć określony typ zwracania).

() można porzucić, jeśli funkcja nie przyjmuje żadnych argumentów, to znaczy

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

który umownie jest zarezerwowany dla metod, które nie mają skutków ubocznych - o tym później.

()nie jest w rzeczywistości pomijana jako taka podczas definiowania paramentera pass by name , ale w rzeczywistości jest to całkiem inna semantycznie notacja, to znaczy

def myOp(passByNameString: => String)

Mówi, że myOp przyjmuje parametr przekazywany przez nazwę, co skutkuje ciągiem znaków (to znaczy może być blokiem kodu, który zwraca ciąg), w przeciwieństwie do parametrów funkcji,

def myOp(functionParam: () => String)

która mówi, że myOpprzyjmuje funkcję, która ma zero parametrów i zwraca String.

(Pamiętaj, parametry przekazywania przez nazwę są kompilowane w funkcje; to po prostu poprawia składnię).

() można pominąć w definicji parametru funkcji, jeśli funkcja przyjmuje tylko jeden argument, na przykład:

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

Ale jeśli wymaga więcej niż jednego argumentu, musisz dołączyć ():

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

Sprawozdania:

.można zrezygnować z używania notacji operatorów, która może być używana tylko dla operatorów wrostków (operatorów metod pobierających argumenty). Zobacz odpowiedź Daniela, aby uzyskać więcej informacji.

  • . może być również usunięty z listy funkcji postfiksowych

  • () można usunąć dla operatorów postfiksowych list.tail

  • () nie można używać z metodami zdefiniowanymi jako:

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

Ponieważ ta notacja jest przez konwencję zarezerwowana dla metod, które nie mają skutków ubocznych, takich jak List # tail (czyli wywołanie funkcji bez skutków ubocznych oznacza, że ​​funkcja nie ma żadnego obserwowalnego efektu, z wyjątkiem wartości zwracanej).

  • () można porzucić dla notacji operatora podczas przekazywania pojedynczego argumentu

  • () może być wymagane użycie operatorów postfiksowych, które nie znajdują się na końcu instrukcji

  • () może być wymagane wyznaczenie zagnieżdżonych instrukcji, końców funkcji anonimowych lub operatorów, które przyjmują więcej niż jeden parametr

Podczas wywoływania funkcji, która przyjmuje funkcję, nie można pominąć () w definicji funkcji wewnętrznej, na przykład:

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

Podczas wywoływania funkcji, która przyjmuje parametr typu „by-name”, nie można określić argumentu jako funkcji anonimowej bez parametrów. Na przykład, biorąc pod uwagę:

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

Musisz to nazwać:

myOp("myop3")

lub

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

ale nie:

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

IMO, nadużywanie upuszczania typów zwracanych może być szkodliwe dla ponownego wykorzystania kodu. Wystarczy spojrzeć na specyfikację, aby znaleźć dobry przykład ograniczonej czytelności z powodu braku wyraźnych informacji w kodzie. Liczba poziomów pośrednich, aby faktycznie dowiedzieć się, jaki jest typ zmiennej, może być szalona. Miejmy nadzieję, że lepsze narzędzia mogą zapobiec temu problemowi i zachować zwięzły kod.

(OK, w celu skompilowania pełniejszej, zwięzłej odpowiedzi (jeśli coś przeoczyłem lub dostałem coś złego / niedokładnego, proszę o komentarz), dodałem na początku odpowiedzi. Pamiętaj, że to nie jest język specyfikacji, więc nie staram się, aby było dokładnie poprawne naukowo - po prostu bardziej jak karta referencyjna.)

Antony Stubbs
źródło
10
Płaczę. Co to jest.
Profpatsch
12

Zbiór cytatów dających wgląd w różne warunki ...

Osobiście myślałem, że w specyfikacji będzie więcej. Jestem pewien, że musi być, po prostu nie szukam odpowiednich słów ...

Źródeł jest jednak kilka i zebrałem je razem, ale nic tak naprawdę kompletnego / kompleksowego / zrozumiałego / nie wyjaśnia mi powyższych problemów ...:

„Jeśli treść metody ma więcej niż jedno wyrażenie, musisz ją otoczyć nawiasami klamrowymi {…}. Możesz pominąć nawiasy, jeśli treść metody ma tylko jedno wyrażenie”.

Z rozdziału 2, „Pisz mniej, rób więcej”, książki Programowanie Scala :

„Treść metody górnej znajduje się po znaku równości„ = ”. Dlaczego znak równości? Dlaczego nie zwykłe nawiasy klamrowe {…}, jak w Javie? Ponieważ średniki, typy zwracane przez funkcje, listy argumentów metod, a nawet nawiasy klamrowe są czasami pomijane, użycie znaku równości zapobiega kilku możliwym niejednoznacznościom podczas analizowania.Użycie znaku równości przypomina nam również, że nawet funkcje są wartościami w Scali, co jest zgodne ze wsparciem Scali dla programowania funkcjonalnego, opisanym bardziej szczegółowo w rozdziale 8, Programowanie funkcjonalne w Scala ”.

Z rozdziału 1, „Zero to Sixty: Introduction Scala”, książki Programming Scala :

„Funkcję bez parametrów można zadeklarować bez nawiasów, w takim przypadku należy ją wywołać bez nawiasów. Zapewnia to obsługę zasady jednolitego dostępu, tak że wywołujący nie wie, czy symbol jest zmienną, czy funkcją bez parametry.

Treść funkcji jest poprzedzona znakiem „=”, jeśli zwraca wartość (tj. Typ zwracany jest inny niż Unit), ale typ zwracany i „=” można pominąć, gdy typem jest Jednostka (tj. Wygląda jak procedura w przeciwieństwie do funkcji).

Szelki wokół ciała nie są wymagane (jeśli ciało jest pojedynczą ekspresją); a dokładniej, treść funkcji jest po prostu wyrażeniem, a każde wyrażenie składające się z wielu części musi być ujęte w nawiasy klamrowe (wyrażenie z jedną częścią może być opcjonalnie ujęte w nawiasy). "

„Funkcje z zerem lub z jednym argumentem można wywołać bez kropki i nawiasów. Jednak każde wyrażenie może mieć nawiasy wokół siebie, więc można pominąć kropkę i nadal używać nawiasów.

A ponieważ możesz używać nawiasów wszędzie tam, gdzie możesz użyć nawiasów, możesz pominąć kropkę i wstawić nawiasy, które mogą zawierać wiele instrukcji.

Funkcje bez argumentów można wywoływać bez nawiasów. Na przykład funkcję length () w łańcuchu można wywołać jako „abc” .length zamiast „abc” .length (). Jeśli funkcja jest funkcją Scali zdefiniowaną bez nawiasów, wówczas należy wywołać funkcję bez nawiasów.

Zgodnie z konwencją funkcje bez argumentów, które mają skutki uboczne, takie jak println, są wywoływane z nawiasami; te bez skutków ubocznych nazywane są bez nawiasów. "

Z wpisu na blogu Scala Syntax Primer :

"Definicja procedury to definicja funkcji, w której typ wyniku i znak równości są pomijane; jej wyrażeniem definiującym musi być blok. Np. Def f (ps) {stats} jest równoważne z def f (ps): Unit = {stats }.

Przykład 4.6.3 Oto deklaracja i definicja procedury o nazwie write:

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

Powyższy kod jest niejawnie uzupełniany do następującego kodu:

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

Ze specyfikacji językowej:

„W przypadku metod, które przyjmują tylko jeden parametr, Scala umożliwia programistom zastąpienie. Spacją i pominięcie nawiasów, umożliwiając składnię operatora pokazaną w naszym przykładzie operatora wstawiania. Składnia ta jest używana w innych miejscach interfejsu API Scala, np. jako konstruowanie instancji Range:

val firstTen:Range = 0 to 9

Tutaj ponownie, to (Int) jest metodą waniliową zadeklarowaną wewnątrz klasy (w rzeczywistości jest tu więcej niejawnych konwersji typów, ale otrzymujesz dryf). "

Ze Scali dla uchodźców z języka Java, część 6: Jak pokonać Javę :

„Teraz, kiedy spróbujesz„ m 0 ”, Scala odrzuca to jako operator jednoargumentowy, ponieważ nie jest prawidłowym operatorem (~,!, - i +). Okazuje się, że„ m ”jest prawidłowym obiektem - jest funkcją, a nie metodą, a wszystkie funkcje są obiektami.

Ponieważ „0” nie jest poprawnym identyfikatorem Scali, nie może być ani operatorem infix, ani operatorem postfiksowym. Dlatego Scala narzeka, że ​​spodziewał się „;” - co rozdzieliłoby dwa (prawie) prawidłowe wyrażenia: „m” i „0”. Gdybyś go wstawił, narzekałby, że m wymaga albo argumentu, albo, w przypadku jego braku, znaku "_", aby przekształcić go w częściowo zastosowaną funkcję. "

„Uważam, że styl składni operatora działa tylko wtedy, gdy po lewej stronie znajduje się wyraźny obiekt. Składnia ma na celu umożliwienie wyrażenia operacji stylu„ operand operator operand ”w naturalny sposób.

Jakie postacie mogę pominąć w Scali?

Ale to, co mnie również dezorientuje, to ten cytat:

„Musi istnieć obiekt, aby otrzymać wywołanie metody. Na przykład nie można wykonać polecenia„ println „Hello World!” ”, Ponieważ println potrzebuje odbiorcy obiektu. Możesz wykonać polecenie „Console println„ Hello World! ”,„ Które zaspokoi potrzeby ”.

Bo o ile widzę, nie jest przedmiotem, aby odebrać połączenie ...

Antony Stubbs
źródło
1
Ok, więc spróbowałem przeczytać źródło Specyfikacji, aby uzyskać wskazówki i woah. To świetny przykład problemów z kodem magicznym - zbyt wiele mixinów, wnioskowanie o typie i niejawne konwersje oraz niejawne parametry. Tak trudno to zrozumieć z zewnątrz! W przypadku takich dużych bibliotek lepsze narzędzia mogą zdziałać cuda ... pewnego dnia ...
Antony Stubbs,
3

Łatwiej jest mi przestrzegać tej praktycznej zasady: w wyrażeniach spacje zmieniają się między metodami i parametrami. W twoim przykładzie (service.findAllPresentations.get.first.votes.size) must be equalTo(2)analizuje jako (service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2)). Zwróć uwagę, że nawiasy wokół 2 mają większą zespolenie niż przestrzenie. Kropki mają również wyższą łączność, więc (service.findAllPresentations.get.first.votes.size) must be.equalTo(2)przeanalizują jako (service.findAllPresentations.get.first.votes.size).must(be.equalTo(2)).

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

Mario Camou
źródło
2

Właściwie przy drugim czytaniu może to jest klucz:

W przypadku metod, które przyjmują tylko jeden parametr, Scala umożliwia deweloperowi zastąpienie. spacją i pomiń nawiasy

Jak wspomniano w poście na blogu: http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6 .

Być może jest to w rzeczywistości bardzo ścisły „cukier składniowy”, który działa tylko wtedy , gdy skutecznie wywołujesz metodę, na obiekcie, który przyjmuje jeden parametr . na przykład

1 + 2
1.+(2)

I nic więcej.

To wyjaśniałoby moje przykłady w pytaniu.

Ale jak powiedziałem, gdyby ktoś mógł wskazać, że znajduje się dokładnie tam, gdzie w specyfikacji języka jest to określone, byłoby to bardzo mile widziane.

Ok, jakiś miły gość (paulp_ z #scala) wskazał, gdzie w specyfikacji języka jest ta informacja:

6.12.3: Pierwszeństwo i asocjatywność operatorów określają grupowanie części wyrażenia w następujący sposób.

  • Jeśli w wyrażeniu występuje kilka operacji wrostkowych, operatory o wyższym priorytecie wiążą się ściślej niż operatory o niższym priorytecie.
  • Jeśli istnieją kolejne operacje infixowe e0 op1 e1 op2. . .opn pl z operatorami op1,. . . , opn o tym samym priorytecie, wszystkie te operatory muszą mieć taką samą asocjatywność. Jeśli wszystkie operatory są lewostronne, sekwencja jest interpretowana jako (... (E0 op1 e1) op2..) Opn en. W przeciwnym razie, jeśli wszystkie operatory są prawostronne, sekwencja jest interpretowana jako e0 op1 (e1 op2 (.. .Opn en)...).
  • Operatory przyrostkowe zawsze mają niższy priorytet niż operatory wrostkowe. Np. E1 op1 e2 op2 jest zawsze równoważne z (e1 op1 e2) op2.

Operand po prawej stronie operatora asocjacyjnego po lewej stronie może składać się z kilku argumentów umieszczonych w nawiasach, np. E op (e1,.., En). To wyrażenie jest następnie interpretowane jako e.op (e1,..., En).

Lewostronna operacja binarna e1 op e2 jest interpretowana jako e1.op (e2). Jeśli op jest prawostronne, ta sama operacja jest interpretowana jako {val x = e1; e2.op (x)}, gdzie x to nowa nazwa.

Hmm - dla mnie to nie zazębia się z tym, co widzę lub po prostu tego nie rozumiem;)

Antony Stubbs
źródło
hmm, aby jeszcze bardziej pogłębić zamieszanie, jest to również poprawne: ((((realService findAllPresentations) get) first) głosów) rozmiar) musi być równe 2, ale nie jeśli usunę którąkolwiek z tych par nawiasów ...
Antony Stubbs,
2

Nie ma. Prawdopodobnie otrzymasz poradę dotyczącą tego, czy funkcja ma skutki uboczne. To jest fałszywe. Poprawka polega na tym, aby nie używać skutków ubocznych w rozsądnym zakresie dozwolonym przez firmę Scala. O ile nie jest to możliwe, wszystkie zakłady są wykluczone. Wszystkie zakłady. Stosowanie nawiasów jest elementem zbioru „wszystko” i jest zbędne. Nie zapewnia żadnej wartości, gdy wszystkie zakłady są wyłączone.

Ta rada jest zasadniczo próbą stworzenia systemu efektów który zawodzi (nie mylić z: jest mniej przydatny niż inne systemy efektów).

Staraj się nie mieć efektu ubocznego. Następnie zaakceptuj, że wszystkie zakłady są wyłączone. Ukrywanie się za de facto notacją składniową dla systemu efektów może i robi tylko krzywdę.

user92983
źródło
Cóż, ale to jest problem, gdy pracujesz z hybrydowym językiem OO / funkcjonalnym, prawda? W jakimkolwiek praktycznym przykładzie będziesz chciał mieć funkcje efektów ubocznych ... Czy możesz wskazać nam jakieś informacje na temat „systemów efektów”? Myślę, że tym bardziej trafnym cytatem jest "Funkcja bez parametrów może być zadeklarowana bez nawiasów, w którym to przypadku musi zostać wywołana bez nawiasów. Zapewnia to obsługę zasady jednolitego dostępu, tak że dzwoniący nie wie, czy symbol to zmienna lub funkcja bez parametrów. ".
Antony Stubbs,