Blog (30)
Komentarze (5.6k)
Recenzje (0)

Scala — pierwsze kroki cz.3

@mikolaj_sScala — pierwsze kroki cz.306.09.2014 00:49

W poprzednich częściach kursu poznaliśmy kilka podstawowych konstrukcji w Scali. Aby można było operować na jakiś realnych danych i pisać użyteczne skrypty musimy zapoznać się z podstawowymi typami tablicowymi (nazywanymi też kolekcjami). W Scali są one wszystkie typami generycznymi. Oznacza to, że zostały napisane z wykorzystaniem metaprogamowania (generics). Parametryzujemy je podając typ obiektu jaki będą przechowywać. Niektóre z kolekcji jak mapy itp. mogą wymagać określenia większej ilości typów.

Krok siódmy - podstawowe kolekcje

Wszystkie typy kolekcji można podzielić na mutowalne (mutable) czyli takie, których elementy można zmieniać, oraz niemutowalne (immutable). W tych ostatnich nie zmienimy (podmienimy) elementu tablicy na inny, tylko musimy stworzyć nowy obiekt kolekcji sklejając ze starych i nowych elementów. W programowaniu funkcyjnym należy używać te drugie i dlatego pakiet, w których się one znajdują jest domyślnie importowany. Jednak ze względu na to, że Scala ma umożliwiać wykorzystanie kodu Javy , oraz nie narzucać pisania w stylu funkcyjnym dodano również tablice mutowalne, które importujemy z pakietu scala.collection.mutable

Array - tablica stałej długości

Jest to odpowiednik prostej tablicy w Javie. Musimy z góry ustalić jej rozmiar i typ.

val powitanieNapisy = new Array[String](3)
powitanieNapisy(0) = "Witaj"
powitanieNapisy(1) = "w świecie"
powitanieNapisy(2) = "programowania!"

println(powitanieNapisy.mkString(" "))

W pierwszej linii przykładu tworzymy tablicę napisów o 3 elementach. O ilości elementów mówi liczba typu Int podana w nawiasach okrągłych. Powstała tablica jest typu Array[String]. Programującym w C++ i Javie od razu rzuci się w oczy sposób zapisu typu tablicy, w którym zamiast nawiasów kątowych < > użyto nawiasów kwadratowych. Wszędzie w metaprogramowaniu oraz kodzie wykorzystującym go, używa się właśnie nawiasów kwadratowych zamiast kątowych. Natomiast gdy chcemy się odwołać do konkretnego elementu tablicy, to zamiast nawiasów kwadratowych, użyjemy nawiasów okrągłych, co widać w 2, 3 i 4 linii. Typy będące kolekcjami mają wbudowane szereg wspólnych wygodnych funkcji, służących do manipulacji nimi, takich jak przedstawiona w ostatniej linii metoda mkString. Metoda ta zamieni całą tablicę w napis i zadba o rozdzielenie go napisem podanym jako parametr tej funkcji, dbając równocześnie, aby nie dodać go na jego końcu.

Przypisanie do kolejnych elementów tablicy może się odbyć za pomocą metody update:

powitanieNapisy.update(0, "Witaj")

Jest to równoważne drugiej linii z pierwszego przykładu. Co więcej linia ta jest w trakcie kompilacji podmieniana właśnie na metodę update. Jest to część mechanizmu, który omówimy w następnym kroku.

Tablicę, której wszystkie elementy są znane na samym początku możemy zadeklarować w następujący sposób:

val tablica = Array("zero", "jeden", "dwa")

Charakterystyczny jest tutaj brak słowa kluczowego new, ponieważ używamy tutaj metody apply napisanej w obiekcie Array (będącej czymś w rodzaju singletonu zespolonego z klasą Array - wyjaśnienie tego pojawi się przy omawianiu klas)

Listy

Jedną z najczęściej używanych kolekcji przy typowym programowaniu w Scali jest typ List. Jest to lista jednokierunkowa. Należy ona do typów niemutowalnych w przeciwieństwie do Array. Polega to na tym, że każdą wartość w tablicy Array można zmienić na inną. Natomiast w List nie. Zamiast podmieniać wartość, tworzymy nową listę łącząc dowolnie różne jej elementy z nowymi, porzucając zbędne. Przypomina to sposób przetwarzania napisów w Javie w klasie String. Aby zadeklarować listę piszemy:

val lista = List(1, 2, 3)

Powstała lista jest typu List[Int] i składa się z 3 elementów.

Łączenie dwóch list i dodawanie elementu do listy:

val lista1 = List(1, 2)
val lista2 = List(3, 4)
val lista3 = lista1 ::: lista2
val toSamoCoLista3 = lista1 ++ lista2
val lista4 = 5 :: lista3
val lista5 = 1 :: 2 :: 3 :: 4 :: 5 :: Nil

Za każdym razem powstaje nowa lista, a stara pozostaje bez zmiany. W trzeciej i czwartej linii pokazane są dwa alternatywne sposoby tworzenia nowej listy z dwóch innych. Dodawanie elementu z przodu listy realizuje podwójny dwukropek. W ostatniej linii widać jak można w inny sposób stworzyć listę. Ostatni element Nil to pusta lista. List nie posiada metody dodawania elementu na jej końcu. Jest to spowodowane faktem, że w liście jednokierunkowej czas realizacji tej operacji byłby długi i proporcjonalny do ilości elementów. Jeśli potrzebujemy takiej możliwości to musimy użyć innej kolekcji. Jednak w dobrze stosowanym stylu funkcyjnym jest to zazwyczaj zbędne i List jest optymalnym rozwiązaniem. Co najwyżej czasem potrzebujemy użyć metody reverse, której wyniku łatwo się można domyśleć.

Tworzenie List i jej metody

  • List() lub Nil - pusta lista
  • List(435454L, 34546534L, 24345448548495L) - tworzenie listy z elementami (tutaj typu Long)
  • List(1, 2) ::: List(3, 4) - łączenie list i tworzenie nowej (lub ++)
  • lista(2) - zwraca 3 element listy (liczone od zera)
  • lista.drop(2) - porzuca wszystkie elementy z przodu włącznie z 2 (NIE liczone od zera)
  • lista.take(2) - bierze dwie pierwsze elementy listy
  • lista.dropRight(2) - podobna do drop tylko porzuca elementy z tyłu
  • lista.last - ostatni element listy
  • lista.head - pierwszy element listy
  • lista.init - wszystkie elementy oprócz ostatniego
  • lista.tail - zwraca wszystkie elementy oprócz pierwszego
  • val pierwszy :: lista2 = lista - przypisuje pierwszy element listy do zmiennej pierwszy, a resztę do lista2
  • lista.reverse - odwraca kolejność elementów w liście
  • lista.isEmpty - zwraca prawdę lub fałsz w zależności czy lista jest pusta
  • lista.length - zwraca ilość elementów listy
  • lista.map(elem => s * 2) - używa funkcji anonimowej do przetworzenia elementów, w tym przypadku zwiększa każdy element 2 krotnie
  • lista.mkString(", ") - łączy elementy tworząc napis oddzielony podanym parametrem
  • lista.forall(x => x > 3) - zwraca prawdę jeśli wszystkie elementy są większe od 3
  • lista.exists(x => x > 3) - zwraca prawdę jeśli chociaż jeden element jest większy od 3
  • lista.filter(x => x > 3) - zwraca listę wszystkich elementów większych od 3
  • lista.filterNot(x => x > 3) - przeciwny do filter
  • lista.foreach(x => printnln(x.toString + " wartość")) - iteruje po wszystkich elementach (niczego nie zwraca)
  • lista.sort((x, y) => x > y) - zwraca listę posortowaną w porządku malejącym

Dla przypomnienia: wszystkie te metody nie zmieniają pierwotnej listy i musimy przypisać wynik do nowej listy.

Krotki - tuples

Ten typ danych jest podobny do znanych krotek w Pythonie. Każdy element krotki może być innego typu. Może to być przydatne przy zwracaniu rezultatów przez funkcje i metody, jednak nie należy nadużywać tego mechanizmu, ponieważ kod może stawać się mało czytelny.

val krotka = (3, "trzy",  'a')
 println(krotka._1)
 println(krotka._2)
 println(krotka._3) 

Krotka powstaje przez wpisanie danych w nawias, a odwołujemy się do elementu podając jego numer po znaku podkreślenia (_) ale licząc od 1.

Może się wydawać, że ciężko się połapać kiedy liczyć elementy od zera, a kiedy od 1. Jest na to reguła, którą będziemy potrafili stosować kiedy zrozumiemy kiedy dany kod napisany jest w stylu funkcyjnym. Tradycyjnie w językach pochodzących od C używa się numerowania tablic od zera. Natomiast w językach funkcyjnych jak Heskell numeruje się od jeden. Zatem w Scali tam gdzie mamy tradycyjne odwołanie podobne do tablicy liczymy elementy od zera, a tam gdzie użycie jest funkcyjne (jak w metodzie listy drop, take itp.) liczy się od jeden.

Mapy i sekwencje

Jak już wspomniałem, Scala wspiera zarówno programowanie imperatywne jak też funkcjonalne, dlatego posiada wersje kolekcji mutowalnych i niemutowalnych. Jeśli jednak dla pozostałych kolekcji zazwyczaj różnią się one nazwą, to dla sekwecji i map nazywają się tak samo.

Sekwencja zachowuje się podobnie do list jednak jej wewnętrzna implementacja jest inna.

var pojazdy = Seq("samochód", "rower")
pojazdy ++ "samolot"
println(pojazdy.contains("samolot")

W drugiej linii do kolekcji dodajemy nowy element, jednak sprawdzenie jego obecności da fałsz, ponieważ obiekt pojazdy jest niemutowalny. Musielibyśmy przypisać go z powrotem do pojazdy, co można zrobić jako, że zadeklarowaliśmy go ze słowem kluczowym var.

pojazdy = pojazdy ++ "samolot"

W rzeczywistości obiekty zadeklarowane jako sekwencje są typami pochodnymi od Seq i lepiej mieć kontrolę nad nimi, wybierając samodzielnie ten typ z pominięciem Seq.

Map składa się klucza i wartości, których typ może być dowolny:

import scala.collection.mutable.Map
var kurs = Map("USD" -> 3.14, "GBP" -> 5.34)
kurs += ("EURO" -> 4.23)
kurs("JEN") = 2.34
println(kurs("EURO")*123.34)

Do mutowalnej mapy możemy dodawać pary klucz i wartość (3 i 4 linia), a potem pobierać według klucza. Mapa, tak jak i inne klasy z kolekcji, zawiera też wiele metod wspólnych dla większości kolekcji takich jak map, foreach itd. Można więc operować na niej podobnie do List, a także dzięki wbudowanym metodom, zmienić ją w listę par (krotek).

Był to dość długi krok, ale ważny na drodze do poznania Scali ;)

Szanowna Użytkowniczko! Szanowny Użytkowniku!
×
Aby dalej móc dostarczać coraz lepsze materiały redakcyjne i udostępniać coraz lepsze usługi, potrzebujemy zgody na dopasowanie treści marketingowych do Twojego zachowania. Twoje dane są u nas bezpieczne, a zgodę możesz wycofać w każdej chwili na podstronie polityka prywatności.

Kliknij "PRZECHODZĘ DO SERWISU" lub na symbol "X" w górnym rogu tej planszy, jeżeli zgadzasz się na przetwarzanie przez Wirtualną Polskę i naszych Zaufanych Partnerów Twoich danych osobowych, zbieranych w ramach korzystania przez Ciebie z usług, portali i serwisów internetowych Wirtualnej Polski (w tym danych zapisywanych w plikach cookies) w celach marketingowych realizowanych na zlecenie naszych Zaufanych Partnerów. Jeśli nie zgadzasz się na przetwarzanie Twoich danych osobowych skorzystaj z ustawień w polityce prywatności. Zgoda jest dobrowolna i możesz ją w dowolnym momencie wycofać zmieniając ustawienia w polityce prywatności (w której znajdziesz odpowiedzi na wszystkie pytania związane z przetwarzaniem Twoich danych osobowych).

Od 25 maja 2018 roku obowiązuje Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 (określane jako "RODO"). W związku z tym chcielibyśmy poinformować o przetwarzaniu Twoich danych oraz zasadach, na jakich odbywa się to po dniu 25 maja 2018 roku.

Kto będzie administratorem Twoich danych?

Administratorami Twoich danych będzie Wirtualna Polska Media Spółka Akcyjna z siedzibą w Warszawie, oraz pozostałe spółki z grupy Wirtualna Polska, jak również nasi Zaufani Partnerzy, z którymi stale współpracujemy. Szczegółowe informacje dotyczące administratorów znajdują się w polityce prywatności.

O jakich danych mówimy?

Chodzi o dane osobowe, które są zbierane w ramach korzystania przez Ciebie z naszych usług, portali i serwisów internetowych udostępnianych przez Wirtualną Polskę, w tym zapisywanych w plikach cookies, które są instalowane na naszych stronach przez Wirtualną Polskę oraz naszych Zaufanych Partnerów.

Dlaczego chcemy przetwarzać Twoje dane?

Przetwarzamy je dostarczać coraz lepsze materiały redakcyjne, dopasować ich tematykę do Twoich zainteresowań, tworzyć portale i serwisy internetowe, z których będziesz korzystać z przyjemnością, zapewniać większe bezpieczeństwo usług, udoskonalać nasze usługi i maksymalnie dopasować je do Twoich zainteresowań, pokazywać reklamy dopasowane do Twoich potrzeb. Szczegółowe informacje dotyczące celów przetwarzania Twoich danych znajdują się w polityce prywatności.

Komu możemy przekazać dane?

Twoje dane możemy przekazywać podmiotom przetwarzającym je na nasze zlecenie oraz podmiotom uprawnionym do uzyskania danych na podstawie obowiązującego prawa – oczywiście tylko, gdy wystąpią z żądaniem w oparciu o stosowną podstawę prawną.

Jakie masz prawa w stosunku do Twoich danych?

Masz prawo żądania dostępu, sprostowania, usunięcia lub ograniczenia przetwarzania danych. Możesz wycofać zgodę na przetwarzanie, zgłosić sprzeciw oraz skorzystać z innych praw wymienionych szczegółowo w polityce prywatności.

Jakie są podstawy prawne przetwarzania Twoich danych?

Podstawą prawną przetwarzania Twoich danych w celu świadczenia usług jest niezbędność do wykonania umów o ich świadczenie (tymi umowami są zazwyczaj regulaminy). Podstawą prawną przetwarzania danych w celu pomiarów statystycznych i marketingu własnego administratorów jest tzw. uzasadniony interes administratora. Przetwarzanie Twoich danych w celach marketingowych realizowanych przez Wirtualną Polskę na zlecenie Zaufanych Partnerów i bezpośrednio przez Zaufanych Partnerów będzie odbywać się na podstawie Twojej dobrowolnej zgody.