Blog (30)
Komentarze (5.6k)
Recenzje (0)
@mikolaj_sScala — pierwsze kroki cz.4

Scala — pierwsze kroki cz.4

22.09.2014 | aktual.: 22.09.2014 09:25

Dzięki opanowaniu materiału z poprzednich części kursu. Jesteśmy już blisko, aby móc wykorzystać zdobyte już umiejętności do konkretnego zastosowania w skryptach. Do tego potrzebujemy jeszcze kilku elementów całej układanki. W tym w szczególności umiejętności czytania i zapisywania do plików. Po przeczytaniu tej części powinniśmy być już w stanie to zrobić. Pisanie programów w IDE kompilowanych do bytecodu wymagać będzie znajomości klas i obiektów, co przedstawię w następnej ostatniej już części.

Krok ósmy - operatory

529782

Język C++ pozwala na przeładowywanie operatorów, umożliwiając pisanie klas, w których operacje na nich mogą być wykonywane naturalnie za pomocą tychże operatorów. W Javie natomiast nie można zdefiniować operatorów dlatego zastępuje się operatory metodami o nazwach takich jak add, mutliple itp. W Scali natomiast wszystko zostało zupełnie przeprojektowane. Nie istnieje podział na metody i operatory. Polega to na tym, że metody mogą mieć prawie dowolne nazwy, w tym mogą to być różne znaki uznawane za operatory. Natomiast metody, które mają tylko jeden argument można wywoływać bez użycia kropki i nawiasów:

[code=Scala]1 + 2[/code]

Możemy równie dobrze napisać jako:

[code=Scala]1.+(2)[/code]

Aby zrozumieć przykład prześledźmy w jaki sposób można wywoływać metody.

Definiujemy liczbę typu Long (litera L na końcu)

[code=Scala]val liczba = 0L liczba: Long = 0[/code]

Teraz możemy zamienić ją na String, wywołując wbudowaną metodę toString

[code=Scala]liczba.toString() res3: java.lang.String = 0[/code]

Równie dobrze możemy tę samą metodę wywołać bez nawiasów. Dotyczy to wszystkich metod bez parametrów:

[code=Scala]liczba.toString[/code]

Więcej, możemy wywołać tę samą metodę na literałach. Kompilator gdy zorientuje się jakiego są typu wywoła metodę z odpowiedniego obiektu:

[code=Scala]12345.toString res4: java.lang.String = 12345 "34564".toInt res5: Int = 3456[/code]

W przypadku obiektów i metod możemy również zrezygnować z użycia nawiasów i kropki, oddzielając nazwę metody od parametru spacją.

Więc 1 + 2 to wywołanie metody + na obiekcie Int reprezentowanej przez jej obiekt o wartości 1. czyli 1.+(2)

529798

Tego efektu nie można stosować w stosunku do funkcji, musimy użyć nawiasów. Funkcje natomiast mogą zawierać wiele różnych znaków w nazwie w tym cyfry np:

[code=Scala]def !#(a:Int) = a*10 $bang$hash: (a: Int)Int !#(30) res8: Int = 300[/code]

Zdefiniowano tutaj i użyto funkcji o nazwie !#.

Nazwy funkcji i metod mają jednak pewne ograniczenia. Nie mogą zaczynać się od cyfry i niektórych znaków jak np. #, $, &. Oczywiście nie mogą być nazwami kluczowymi, w tym pojedynczymi znakami używanymi przez składnie. Przykładowo funkcja nie może się nazywać : ani :nazwa ponieważ dwukropek wykorzystuje się przy określaniu typu zmiennej. Ale już użycie nazwy :# lub :: jest dopuszczalne ponieważ te znaki nie występują nigdy razem w składni. Często dodaje się na końcu metod znaku zapytania ? jednak musimy poprzedzić go znakiem podkreślnika:

[code=Scala]def parzysta_?(i:Int) = i % 2 == 0 parzysta_$qmark: (i: Int)Boolean[/code]

[code=Scala]parzysta_?(567) res13: Boolean = false[/code]

529805

W przypadku metod umożliwiają one tworzenie bardziej intuicyjnych bibliotek i  DSLi. Więcej na temat nazw metod dowiemy się przy okazji omawiania definicji klas i obiektów.

Krok dziewiąty - rozpoznawanie stylu funkcyjnego

529808

Jak wiemy Scala nie jest językiem wymuszającym pisanie w stylu funkcyjnym. Ułatwia to przenoszenie kodu z innych języków jak np. Javy. Pomaga też początkującym programistom odnaleźć się szybko w składni Scali i nie tracić zbytnio produktywności rozgryzając nieustannie jak napisać dany kod funkcyjnie. Ma to również swoje minusy i może nie podobać się purystom językowym. Jednak Scala powstała z czysto praktycznych powodów i nie stara się utrudnić życia programiście w imię jakiś wyższych ideałów.

Jednym z elementów stylu funkcyjnego jest unikanie iterowania po dodatkowej zmiennej np.:

[code=Scala]def drukujArg(args: Array[String]):Unit = { var i = 0 while (i < args.length) { println(args(i)) i += 1 } }[/code]

Możemy zastąpić:

[code=Scala]def drukujArg(args: Array[String]):Unit = { for (arg <- args.length) println(args(i)) } [/code]

Lub jeszcze zwięźlej:

[code=Scala]def drukujArg(args: Array[String]):Unit = { args.foreach(println) } [/code]

W tym ostatnim używamy wbudowanej w kolekcje metody foreach i uproszczonej składni anonimowej funkcji. (możemy ją rozpisać jako; (arg) => println(arg) )

Ostatnia wersja jest najkrótsza i możemy ją przetłumaczyć w prosty sposób jako: każdy argument zmiennej args drukuj w nowej linii. Taki sposób pisania na dłuższą metę jest opłacalny, ponieważ skraca długość kodu i pomaga wyłapywać błędy jakie powstają przy bardziej rozwlekłym kodzie. Nie wymaga również stosowania pośrednich zmiennych.

Powyższe funkcje mają jednak pewien minus, generują uboczny efekt w postaci wydruku danych na konsolę. Nie jest to jeszcze idealnie funkcyjny styl. Lepiej byłoby zrobić następująco:

[code=Scala]def formatujArg(args: Array[String]) = args.mkString("\n")

println(formatujArg(args)) [/code]

Metoda formatujArg jest w pełni funkcyjna tzn.: nie używa zmiennych mutowalnych i nie ma efektów ubocznych. Tworzy nowy obiekt w formie Stringu, który można użyć np. do wydrukowania na ekran. Oryginalna tablica nie ulega zmianie i jest bezpieczna. (Domyślnie każdy parametr metody lub funkcji jest typu val, jeśli nie chcemy aby tak było dopisujemy na początku var.)

Powyższa metoda ułatwia również testowanie kodu, ponieważ można łatwo sprawdzić, czy logika działania jest prawidłowa:

[code=Scala]val doDruku = formatujArg(Array("jeden", "dwa", "trzy")) assert(doDruku == "jeden\ndwa\ntrzy"[/code]

Przykład oczywiście jest banalny, ale ukazuje ideę, że kod pisany w stylu funkcyjnym jest łatwiejszy do testowania.

Warto pamiętać, że sam wygląd kodu polegający na łańcuchowym przetwarzaniu danych przez wbudowane funkcje nie jest jeszcze prawdziwym programowaniem funkcyjnym. Przykładowo podobny kod do funkcyjnego pisze się używając jQuery. Jednak mimo podobieństwa trudno nazwać go stylem funkcyjnym, ponieważ operuje się na zmiennych mutowalnych. Głównym wyróżnikiem stylu funkcyjnego jest właśnie używanie niemutowalnych danych. Gdy chcemy zmienić te dane to tworzymy i zwracamy nowy obiekt nie zmieniając starego. Obiekty powinny być zabezpieczone przez zmianą. Dlatego w stylu funkcyjnym używamy typu zmiennych val.

Krok dziesiąty - czytanie danych z pliku

Aby pisać proste skrypty najczęściej niezbędna jest praca na plikach oraz możliwość odczytu i zapisu danych. Do czytania danych z pliku używamy obiektu scala.io.Source

[code=Scala]import scala.io.Source

if(args.length > 0) { for(line <- Source.fromFile(args(0)).getLines) print(line.length + " " + line) } else Console.err.println("Błędna nazwa pliku")[/code]

Po zapisaniu kodu w pliku i uruchomieniu z argumentem będącym ścieżką do pliku:

scala nazwa_programu.scala sciezka_do_pliku

uzyskujemy wydruk zawartości pliku z ilością danych w każdej linii.

Source potrafi również czytać z sieci (po podaniu URL), strumieni oraz tablic z danymi (Array[Byte])

np:.

[code=Scala]import scala.io.Source

if(args.length > 0) { for(line <- Source.fromURL(args(0)).getLines) print(line.length + " " + line) } else Console.err.println("Brak URL")[/code]

Podajemy w tym przypadku pełny URL (z http).

Metoda fromFile zwraca Iterator[String] dający dostęp do danych. Aby zamienić go na kolekcję wykonujemy metodę:

[code=Scala]val linie = Source.fromFile("plik").getLines.toList[/code]

Czytanie z konsoli

Do czytania używamy metody readLine("Pytanie"):

[code=Scala]import scala.io.Source val http = readLine("Podaj HTTP: ") for(line <- Source.fromURL(http).getLines) print(line.length + " " + line)[/code]

Zapis do pliku

W standardowych bibliotekach Scali nie ma osobnego sposobu zapisu danych do pliku. Korzysta się z klas Javy. W przypadku pliku tekstowego możemy wykonać zapis następująco:

[code=Scala]val wyjscie= new java.io.FileWriter("plik.txt") wyjscie.write("Witaj pliczku!") wyjscie.close[/code]

Jak już wspomniałem jest to przedostatnia już część kursu. Z góry zapraszam na ostatnią, w której przedstawię aspekty programowania obiektowego w Scali.

Wybrane dla Ciebie
Komentarze (12)