7.15. Operacje infiksowe
Operacja infiksowa polega na umieszczeniu operatora pomiędzy wyrażeniem, na którym jest wywołana, a jego argumentem lub listą argumentów. Taka operacja może być użyta w przypadku każdego identyfikatora, jednak interpretacja operacji zależy od tego, jaki jest ten identyfikator. Operatory mają różne priorytety i różne sposoby wiązania.
Operatory mogą wiązać swoje argumenty od lewej do prawej lub od prawej do lewej. Identyfikatory, których ostatnim znakiem jest znak dwukropka, wiążą od prawej do lewej, gdy są użyte w operacji infiksowej. Pozostałe identyfikatory wiążą od lewej do prawej.
Operacja a x b, gdzie x jest operatorem wiążącym od lewej do prawej, jest interpretowana jako wywołanie a.x(b).
Przykładowo, następujące dwa wyrażenia są sobie równoważne i powodują wywołanie metody o nazwie + na obiekcie klasy String. Metoda + nie jest zakończona dwukropkiem i dlatego wiąże od lewej do prawej.
scala> "a" + "b"
res0: String = ab
scala> "a".+("b")
res1: String = ab
Operacja a x b, gdzie x jest operatorem wiążącym od prawej do lewej, jest interpretowana tak, jak byłoby interpretowane wywołanie b.x(a) z tą różnicą, że wyrażenie a jest ewaluowane przed ewaluacją wyrażenia b. Przykładem operatora wiążącego od prawej do lewej jest operator :: z klasy List, reprezentującej listy. Metoda :: wywołana na liście tworzy nową listę, z dodatkowym elementem podanym jako argument umieszczonym na początku listy wynikowej. Metoda :: może być wywołana również na liście pustej, reprezentowanej przez obiekt o identyfikatorze Nil i w tym przypadku tworzy jednoelementową listę, zawierającą element podany w jej argumencie. Zwykłe wywołanie metody :: mogłoby wyglądać następująco:
scala> Nil.::(1) res2: List[Int] = List(1)
![]() | Klasa List jest opisana w punkcie 9.10. |
Powyższa operacja tworzy jednoelementową listę wartości typu Int, zawierającą liczbę 1. Taką samą listę można utworzyć wykorzystując operację infiksową w sposób następujący:
scala> 1 :: Nil res3: List[Int] = List(1)
Oprócz sposobu wiązania operatora, na interpretację wyrażenia z użyciem operacji infiksowej ma wpływ priorytet operatorów. Priorytet ma znaczenie w przypadku użycia wyrażenia zawierającego sekwencję dwóch lub większej liczby operatorów. Operatory o większym priorytecie wiążą swoje argumenty przed operatorami o mniejszym priorytecie. Na przykład operator * ma większy priorytet od operatora +. W następującym wyrażeniu arytmetycznym najpierw wykonywana jest operacja *, oznaczająca mnożenie, a dopiero potem +, oznaczająca dodawanie.
scala> 1+2*3 res4: Int = 7
Wynikiem tego wyrażenia jest 7. Taki sam wynik uzyskać można wywołując metody * i + za pomocą zwykłej notacji.
scala> (1).+((2).*(3)) res5: Int = 7
Kolejność wykonywania operacji wynikającą z priorytetów przypisanych operatorom można zmienić za pomocą nawiasów. W poniższym wyrażeniu najpierw wykonywane jest dodawanie, a dopiero potem mnożenie.
scala> (1+2)*3 res6: Int = 9
Taki sam wynik uzyskamy wywołując metody + i * w sposób następujący, z użyciem zwykłej notacji.
scala> ((1).+(2)).*(3) res7: Int = 9
Priorytety operatorów zależą, z pewnym wyjątkiem, od pierwszego znaku operatora, według poniższej listy. Operatory zaczynające się od znaków umieszczonych bliżej początku listy mają wyższy priorytet od operatorów zaczynających się od znaków umieszczonych dalej. Operatory zaczynające się od znaków znajdujących się w tym samym wierszu mają taki sam priorytet.
- inne znaki specjalne
- * / %
- + -
- :
- = !
- > <
- &
- ^
- |
- wszystkie litery
Wspomnianym wcześniej wyjątkiem od reguły są operatory przypisania. Operatorem przypisania jest symbol złożony ze znaków będących operatorami (patrz opis drugiego rodzaju identyfikatorów w punkcie 2.1), który kończy się znakiem równości =, z wyjątkiem takich operatorów, które zaczynają się od znaku równości oraz z wyjątkiem operatorów <=, >= i !=. Priorytet operatorów przypisania jest taki, jak priorytet zwykłego symbolu przypisania = i jest mniejszy niż priorytet wszystkich innych operatorów. W pliku OperatorTest1.scala znajduje się klasa OpTest, w której zdefiniowano szereg metod mających różne priorytety. Zdefiniowane operacje tworzą nowe instancje klasy OpTest w taki sposób, który pozwala na zilustrowanie tego, w jakiej kolejności wykonywane są poszczególne operacje.
Plik OperatorTest1.scala: class OpTest(label: String) { def *= (that: OpTest) = new OpTest("("+this+" *= " +that+")") def xxx(that: OpTest) = new OpTest("("+this+" xxx "+that+")") def | (that: OpTest) = new OpTest("("+this+" | " +that+")") def =@=(that: OpTest) = new OpTest("("+this+" =@= "+that+")") def + (that: OpTest) = new OpTest("("+this+" + " +that+")") def * (that: OpTest) = new OpTest("("+this+" * " +that+")") def ! = new OpTest("("+this+" ! )") override def toString = label }
W pliku OperatorTest2.scala znajdują się definicje kilku instancji tej klasy.
Plik OperatorTest2.scala: val a = new OpTest("a") val b = new OpTest("b") val c = new OpTest("c") val d = new OpTest("d") val e = new OpTest("e") val f = new OpTest("f") val g = new OpTest("g")
Poniższe przykłady pokazują kilka wyrażeń zbudowanych z tych instancji.
scala> :load OperatorTest2.scala Loading OperatorTest2.scala... a: OpTest = a b: OpTest = b c: OpTest = c d: OpTest = d e: OpTest = e f: OpTest = f g: OpTest = g scala> a *= b xxx c | d =@= e + f * g res8: OpTest = (a *= (b xxx (c | (d =@= (e + (f * g)))))) scala> a * b + c =@= d | e xxx f *= g res9: OpTest = ((((((a * b) + c) =@= d) | e) xxx f) *= g) scala> a xxx b | c + d =@= e *= f * g res10: OpTest = ((a xxx (b | ((c + d) =@= e))) *= (f * g))
W przypadku użycia operacji infiksowych i operacji postfiksowej w jednym wyrażeniu, operacje infiksowe są wykonywane najpierw. Poniższe wyrażenie zawiera operatory infiksowe xxx i * oraz postfiksowy !. Operacja z użyciem operatora postfiksowego ! jest wykonana na końcu.
scala> import language.postfixOps import language.postfixOps scala> a xxx b * c ! res11: OpTest = ((a xxx (b * c)) ! )
Jeśli w wyrażeniu użyta jest sekwencja operatorów o takim samym priorytecie, a kolejność ich wykonywania nie jest określona za pomocą nawiasów, to ta kolejność jest zgodna z kierunkiem wiązania operatorów. W takim wyrażeniu wszystkie operatory muszą mieć taki sam kierunek wiązania, a więc wszystkie muszą wiązać od lewej do prawej lub wszystkie od prawej do lewej. Oba poniższe wyrażenia są błędne, gdyż operator +: wiąże od prawej do lewej, operator ++ od lewej do prawej, a oba operatory zaczynają się od tego samego znaku, a więc mają ten sam priorytet.
scala> Nil +: Nil ++ Nil
<console>:1: error: left- and right-associative operators with same precedence may not be mixed
Nil +: Nil ++ Nil
^
scala> Nil ++ Nil +: Nil
<console>:1: error: left- and right-associative operators with same precedence may not be mixed
Nil ++ Nil +: Nil
^
W przypadku wyrażenia a x b y c, w którym operatory x i y o takim samym priorytecie wiążą od lewej do prawej, najpierw wykonywana jest operacja z użyciem x, a potem z użyciem y, czyli takie wyrażenie odpowiada wyrażeniu ((a x b) y c). Poniższe wyrażenia są równoważne.
scala> 3 - 2 + 1 res12: Int = 2 scala> (3 - 2) + 1 res13: Int = 2
W przypadku wyrażenia a x b y c, w którym operatory x i y o takim samym priorytecie wiążą od prawej do lewej, najpierw wykonywana jest operacja z użyciem y, a potem z użyciem x, czyli takie wyrażenie odpowiada wyrażeniu (a x (b y c)). Poniższe wyrażenia są równoważne.
scala> 1 :: 2 :: Nil res14: List[Int] = List(1, 2) scala> 1 :: (2 :: Nil) res15: List[Int] = List(1, 2)
![]() | Specyfikacja języka Scala opisuje operacje infiksowe w punkcie 6.12.3. |

Plik OperatorTest1.scala:
class OpTest(label: String) {
def *= (that: OpTest) = new OpTest("("+this+" *= " +that+")")
def xxx(that: OpTest) = new OpTest("("+this+" xxx "+that+")")
def | (that: OpTest) = new OpTest("("+this+" | " +that+")")
def =@=(that: OpTest) = new OpTest("("+this+" =@= "+that+")")
def + (that: OpTest) = new OpTest("("+this+" + " +that+")")
def * (that: OpTest) = new OpTest("("+this+" * " +that+")")
def ! = new OpTest("("+this+" ! )")
override def toString = label
}
