20.8. Klasa StringContext
W tym podrozdziale zajmiemy się dokładniej literałami przetwarzanych łańcuchów znakowych, które zostały zaprezentowane w rozdziale 3. Przypomnijmy, że literał przetwarzanego łańcucha znakowego składa się z literału znakowego zawierającego interpolowane wartości, poprzedzonego jakimś identyfikatorem. Interpolowane wartości to wyrażenia poprzedzone znakiem dolara i ewentualnie objęte znakami nawiasów klamrowych. Standardowo Scala pozwala używać takich literałów rozpoczynających się identyfikatorami s, raw i f. Tego rodzaju literały są przez kompilator zamieniane na wyrażenia tworzące instancję klasy StringContext i wywołujące na tej instancji metodę o nazwie takiej, jak identyfikator od którego rozpoczyna się literał. Wartości interpolowane są przekazywane jako kolejne argumenty tej metody. Natomiast fragmenty łańcucha znakowego znajdujące się pomiędzy tymi wartościami są przekazywane jako argumenty konstruktora klasy StringContext. Spójrzmy na literał z następującego przykładu.
scala> val (a,b,c) = (2,5,7) a: Int = 2 b: Int = 5 c: Int = 7 scala> s"Adding $a to $b gives $c." res0: String = Adding 2 to 5 gives 7.
Taki sam wynik możemy uzyskać jawnie tworząc instancję klasy StringContext i wywołując na niej metodę s, jak w następującym przykładzie.
scala> new StringContext("Adding "," to "," gives ",".").s(a,b,c)
res1: String = Adding 2 to 5 gives 7.
Wykorzystując opisany mechanizm oraz niejawne konwersje możemy tworzyć nowe identyfikatory, które umieszczone przed łańcuchami znakowymi spowodują ich przetworzenie. Plik NewLinesString.scala pokazuje przykład takiego nowego identyfikatora.
Plik NewLinesString.scala: implicit class NewLinesString(sc: StringContext) { def nl(args: Any*): String = { val sb = new StringBuilder() var j = 0 while (j < args.size) { sb append sc.parts(j) sb append args(j) sb append "\n" j += 1 } sb append sc.parts(j) sb.toString } }
Klasa NewLinesString definiuje niejawną konwersję, która pozwala na wywoływanie na instancjach klasy StringContext metody nl. Tym samym można wykorzystać identyfikator nl wraz z literałami przetwarzanych łańcuchów znakowych. Metoda nl tworzy napis, w którym po każdej wartości interpolowanej wstawiany jest znak nowego wiersza. Poniższy przykład ilustuje jej wykorzystanie.
scala> :load NewLinesString.scala Loading NewLinesString.scala... defined class NewLinesString scala> nl"Adding $a to $b gives $c." res2: String = Adding 2 to 5 gives 7 .
Metoda nl ma dostęp do poszczególnych interpolowanych wartości za pomocą powtórzonego parametru args, natomiast do fragmentów łańcucha znakowego znajdujących się pomiędzy nimi za pomocą sekwencji zwracanej przez metodę parts instancji klasy StringContext. Metoda nl buduje wynikowy łańcuch znakowy używając instancji klasy StringBuilder, która pozwala budować łańcuch znakowy krok po kroku poprzez dodawanie kolejnych fragmentów za pomocą metody append. Metoda nl zwraca w wyniku łańcuch znakowy. Przykład z pliku ArrayString.scala pokazuje inną metodę, która zwraca wartość typu innego niż String.
Plik ArrayString.scala: implicit class ArrayString(sc: StringContext) { def arr(args: Any*): Array[String] = sc.parts(0).split(" ") }
Klasa ArrayString definiuje niejawną konwersję, która pozwala na wywoływanie na instancjach klasy StringContext metody arr. Metoda arr interpretuje pierwszy z łańcuchów znakowych zwracanych przez metodę parts jako sekwencję wartości oddzielonych spacjami i zwraca te wartości w tablicy. Poniższy przykład pokazuje wykorzystanie metody.
scala> :load ArrayString.scala Loading ArrayString.scala... defined class ArrayString scala> arr"Hello World !" res3: Array[String] = Array(Hello, World, !)
Ponieważ metoda arr wykorzystuje jedynie pierwszy element sekwencji zwracanej przez metodę parts, w literałach używających tej metody nie należy umieszczać interpolowanych wyrażeń, ponieważ ich wartości zostaną zignorowane, jak w poniższym przykładzie.
scala> arr"Adding 2 to $b gives $c." res4: Array[String] = Array(Adding, 2, to)
Plik NewLinesString.scala:
implicit class NewLinesString(sc: StringContext) {
def nl(args: Any*): String = {
val sb = new StringBuilder()
var j = 0
while (j < args.size) {
sb append sc.parts(j)
sb append args(j)
sb append "\n"
j += 1
}
sb append sc.parts(j)
sb.toString
}
}
