diff --git a/pl/tutorials/tour/abstract-types.md b/pl/tutorials/tour/abstract-types.md new file mode 100644 index 0000000000..4d58abd20c --- /dev/null +++ b/pl/tutorials/tour/abstract-types.md @@ -0,0 +1,79 @@ +--- +layout: tutorial +title: Typy abstrakcyjne + +disqus: true + +tutorial: scala-tour +num: 22 +languages: [ba, es, ko, pl] +language: pl +tutorial-next: compound-types +tutorial-previous: inner-classes +--- + +W Scali, klasy są parametryzowane wartościami (parametry konstruktora) oraz typami (jeżeli klasa jest [generyczna](generic-classes.html)). Aby zachować regularność, zarówno typy jak i wartości są elementami klasy. Analogicznie mogą one być konkretne albo abstrakcyjne. + +Poniższy przykład definiuje wartość określaną przez abstrakcyjny typ będący elementem [cechy](traits.html) `Buffer`: + +```tut +trait Buffer { + type T + val element: T +} +``` + +*Typy abstrakcyjne* są typami, które nie są jednoznacznie określone. W powyższym przykładzie wiemy tylko, że każdy obiekt klasy `Buffer` posiada typ `T`, ale definicja klasy `Buffer` nie zdradza jakiemu konkretnie typowi on odpowiada. Tak jak definicje wartości, możemy także nadpisać definicje typów w klasach pochodnych. Pozwala to nam na odkrywanie dodatkowych informacji o abstrakcyjnym typie poprzez zawężanie jego ograniczeń (które opisują możliwe warianty konkretnego typu). + +W poniższym programie definiujemy klasę `SeqBuffer`, która ogranicza możliwe typy `T` do pochodnych sekwencji `Seq[U]` dla nowego typu `U`: + +```tut +abstract class SeqBuffer extends Buffer { + type U + type T <: Seq[U] + def length = element.length +} +``` + +Cechy oraz [klasy](classes.html) z abstrakcyjnymi typami są często używane w połączeniu z anonimowymi klasami. Aby to zilustrować wykorzystamy program, w którym utworzymy bufor sekwencji ograniczony do listy liczb całkowitych: + +```tut +abstract class IntSeqBuffer extends SeqBuffer { + type U = Int +} + +object AbstractTypeTest1 extends App { + def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer = + new IntSeqBuffer { + type T = List[U] + val element = List(elem1, elem2) + } + val buf = newIntSeqBuf(7, 8) + println("length = " + buf.length) + println("content = " + buf.element) +} +``` + +Typ zwracany przez metodę `newIntSeqBuf` nawiązuje do specjalizacji cechy `Buffer`, w której typ `U` jest równy `Int`. Podobnie w anonimowej klasie tworzonej w metodzie `newIntSeqBuf` określamy `T` jako `List[Int]`. + +Warto zwrócić uwagę na to, że często jest możliwa zamiana abstrakcyjnych typów w parametry typów klas i odwrotnie. Poniższy przykład stosuje wyłącznie parametry typów: + +```tut +abstract class Buffer[+T] { + val element: T +} +abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] { + def length = element.length +} +object AbstractTypeTest2 extends App { + def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] = + new SeqBuffer[Int, List[Int]] { + val element = List(e1, e2) + } + val buf = newIntSeqBuf(7, 8) + println("length = " + buf.length) + println("content = " + buf.element) +} +``` + +Należy też pamiętać o zastosowaniu [adnotacji wariancji](variance.html). Inaczej nie byłoby możliwe ukrycie konkretnego typu sekwencji obiektu zwracanego przez metodę `newIntSeqBuf`. diff --git a/pl/tutorials/tour/annotations.md b/pl/tutorials/tour/annotations.md new file mode 100644 index 0000000000..230241cd71 --- /dev/null +++ b/pl/tutorials/tour/annotations.md @@ -0,0 +1,146 @@ +--- +layout: tutorial +title: Adnotacje + +disqus: true + +tutorial: scala-tour +num: 31 +tutorial-next: default-parameter-values +tutorial-previous: automatic-closures +language: pl +--- + +Adnotacje dodają meta-informacje do różnego rodzaju definicji. + +Podstawową formą adnotacji jest `@C` lub `@C(a1, ..., an)`. Tutaj `C` jest konstruktorem klasy `C`, który musi odpowiadać klasie `scala.Annotation`. Wszystkie argumenty konstruktora `a1, ..., an` muszą być stałymi wyrażeniami (czyli wyrażeniami takimi jak liczby, łańcuchy znaków, literały klasowe, enumeracje Javy oraz ich jednowymiarowe tablice). + +Adnotację stosuje się do pierwszej definicji lub deklaracji która po niej następuje. Możliwe jest zastosowanie więcej niż jednej adnotacji przed definicją lub deklaracją. Kolejność według której są one określone nie ma istotnego znaczenia. + +Znaczenie adnotacji jest zależne od implementacji. Na platformie Java, poniższe adnotacje domyślnie oznaczają: + +| Scala | Java | +| ------ | ------ | +| [`scala.SerialVersionUID`](http://www.scala-lang.org/api/2.9.1/scala/SerialVersionUID.html) | [`serialVersionUID`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html#navbar_bottom) (pole) | +| [`scala.cloneable`](http://www.scala-lang.org/api/2.9.1/scala/cloneable.html) | [`java.lang.Cloneable`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Cloneable.html) | +| [`scala.deprecated`](http://www.scala-lang.org/api/2.9.1/scala/deprecated.html) | [`java.lang.Deprecated`](http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Deprecated.html) | +| [`scala.inline`](http://www.scala-lang.org/api/2.9.1/scala/inline.html) (since 2.6.0) | brak odpowiednika | +| [`scala.native`](http://www.scala-lang.org/api/2.9.1/scala/native.html) (since 2.6.0) | [`native`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.remote`](http://www.scala-lang.org/api/2.9.1/scala/remote.html) | [`java.rmi.Remote`](http://java.sun.com/j2se/1.5.0/docs/api/java/rmi/Remote.html) | +| [`scala.serializable`](http://www.scala-lang.org/api/2.9.1/index.html#scala.annotation.serializable) | [`java.io.Serializable`](http://java.sun.com/j2se/1.5.0/docs/api/java/io/Serializable.html) | +| [`scala.throws`](http://www.scala-lang.org/api/2.9.1/scala/throws.html) | [`throws`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.transient`](http://www.scala-lang.org/api/2.9.1/scala/transient.html) | [`transient`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.unchecked`](http://www.scala-lang.org/api/2.9.1/scala/unchecked.html) (od 2.4.0) | brak odpowiednika | +| [`scala.volatile`](http://www.scala-lang.org/api/2.9.1/scala/volatile.html) | [`volatile`](http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html) (słowo kluczowe) | +| [`scala.reflect.BeanProperty`](http://www.scala-lang.org/api/2.9.1/scala/reflect/BeanProperty.html) | [`Design pattern`](http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html) | + +W poniższym przykładzie dodajemy adnotację `throws` do definicji metody `read` w celu obsługi rzuconego wyjątku w programie w Javie. + +> Kompilator Javy sprawdza czy program zawiera obsługę dla [wyjątków kontrolowanych](http://docs.oracle.com/javase/tutorial/essential/exceptions/index.html) poprzez sprawdzenie, które wyjątki mogą być wynikiem wykonania metody lub konstruktora. Dla każdego kontrolowanego wyjątku który może być wynikiem wykonania, adnotacja **throws** musi określić klasę tego wyjątku lub jedną z jej klas bazowych. +> Ponieważ Scala nie pozwala na definiowanie wyjątków kontrolowanych, jeżeli chcemy obsłużyć wyjątek z kodu w Scali w Javie, należy dodać jedną lub więcej adnotacji `throws` określającej klasy wyjątków przez nią rzucanych. + +``` +package examples +import java.io._ +class Reader(fname: String) { + private val in = new BufferedReader(new FileReader(fname)) + @throws(classOf[IOException]) + def read() = in.read() +} +``` + +Poniższy program w Javie wypisuje zawartość pliku, którego nazwa jest podana jako pierwszy argument w metodzie `main`: + +``` +package test; +import examples.Reader; // Klasa Scali !! +public class AnnotaTest { + public static void main(String[] args) { + try { + Reader in = new Reader(args[0]); + int c; + while ((c = in.read()) != -1) { + System.out.print((char) c); + } + } catch (java.io.IOException e) { + System.out.println(e.getMessage()); + } + } +} +``` + +Zakomentowanie adnotacji `throws` w klasie `Reader` spowoduje poniższy błąd kompilacji głównego programu w Javie: + +``` +Main.java:11: exception java.io.IOException is never thrown in body of +corresponding try statement + } catch (java.io.IOException e) { + ^ +1 error +``` + +### Adnotacje Javy ### + +Java w wersji 1.5 wprowadziła możliwość definiowania metadanych przez użytkownika w postaci [adnotacji](https://docs.oracle.com/javase/tutorial/java/annotations/). Kluczową cechą adnotacji jest to, że polegają one na określaniu par nazwa-wartość w celu inicjalizacji jej elementów. Na przykład, jeżeli potrzebujemy adnotacji w celu śledzenia źródeł pewnej klasy, możemy ją zdefiniować w następujący sposób: + +``` +@interface Source { + public String URL(); + public String mail(); +} +``` + +I następnie zastosować w taki sposób: + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +Zastosowanie adnotacji w Scali wygląda podobnie jak wywołanie konstruktora, gdzie wymagane jest podanie nazwanych argumentów: + +``` +@Source(URL = "http://coders.com/", + mail = "support@coders.com") +class MyScalaClass ... +``` + +Składnia ta może się wydawać nieco nadmiarowa, jeżeli adnotacja składa się tylko z jednego elementu (bez wartości domyślnej), zatem jeżeli nazwa pola jest określona jako `value`, może być ona stosowana w Javie stosując składnię podobną do konstruktora: + +``` +@interface SourceURL { + public String value(); + public String mail() default ""; +} +``` + +Następnie ją można zastosować: + +``` +@SourceURL("http://coders.com/") +public class MyClass extends HisClass ... +``` + +W tym przypadku, Scala daje taką samą możliwość: + +``` +@SourceURL("http://coders.com/") +class MyScalaClass ... +``` + +Element `mail` został zdefiniowany z wartością domyślną, zatem nie musimy jawnie określać wartości dla niego. Jednakże, jeżeli chcemy tego dokonać, Java nie pozwala nam na mieszanie tych styli: + +``` +@SourceURL(value = "http://coders.com/", + mail = "support@coders.com") +public class MyClass extends HisClass ... +``` + +Scala daje nam większą elastyczność w tym aspekcie: + +``` +@SourceURL("http://coders.com/", + mail = "support@coders.com") + class MyScalaClass ... +``` diff --git a/pl/tutorials/tour/anonymous-function-syntax.md b/pl/tutorials/tour/anonymous-function-syntax.md new file mode 100644 index 0000000000..e292844f1b --- /dev/null +++ b/pl/tutorials/tour/anonymous-function-syntax.md @@ -0,0 +1,54 @@ +--- +layout: tutorial +title: Funkcje anonimowe + +disqus: true + +tutorial: scala-tour +num: 6 +language: pl +tutorial-next: higher-order-functions +tutorial-previous: mixin-class-composition +--- + +Scala posiada lekką składnię pozwalającą na definiowanie funkcji anonimowych. Poniższe wyrażenie tworzy funkcję następnika dla liczb całkowitych: + +```tut +(x: Int) => x + 1 +``` + +Jest to krótsza forma deklaracji anonimowej klasy: + +```tut +new Function1[Int, Int] { + def apply(x: Int): Int = x + 1 +} +``` + +Możliwe jest także zdefiniowanie funkcji z wieloma parametrami: + +```tut +(x: Int, y: Int) => "(" + x + ", " + y + ")" +``` + +lub też bez parametrów: + +```tut +() => { System.getProperty("user.dir") } +``` + +Istnieje także prosty sposób definicji typów funkcji. Dla powyższych funkcji można je określić w następujący sposób: + +``` +Int => Int +(Int, Int) => String +() => String +``` + +Jest to skrócona forma dla poniższych typów: + +``` +Function1[Int, Int] +Function2[Int, Int, String] +Function0[String] +``` diff --git a/pl/tutorials/tour/automatic-closures.md b/pl/tutorials/tour/automatic-closures.md new file mode 100644 index 0000000000..38c7358d96 --- /dev/null +++ b/pl/tutorials/tour/automatic-closures.md @@ -0,0 +1,73 @@ +--- +layout: tutorial +title: Automatyczna konstrukcja domknięć + +disqus: true + +tutorial: scala-tour +num: 30 +language: pl +tutorial-next: annotations +tutorial-previous: operators +--- + +Scala pozwala na przekazywanie funkcji bezparametrycznych jako argumenty dla metod. Kiedy tego typu metoda jest wywołana, właściwe parametry dla funkcji bezparametrycznych nie są ewaluowane i przekazywana jest pusta funkcja, która enkapsuluje obliczenia odpowiadającego parametru (tzw. *wywołanie-przez-nazwę*). + +Poniższy kod demonstruje działanie tego mechanizmu: + +```tut +object TargetTest1 extends App { + def whileLoop(cond: => Boolean)(body: => Unit): Unit = + if (cond) { + body + whileLoop(cond)(body) + } + var i = 10 + whileLoop (i > 0) { + println(i) + i -= 1 + } +} +``` + +Funkcja `whileLoop` pobiera dwa parametry: `cond` i `body`. Kiedy funkcja jest aplikowana, jej właściwe parametry nie są ewaluowane. Lecz gdy te parametry są wykorzystane w ciele `whileLoop`, zostanie ewaluowana niejawnie utworzona funkcja, zwracająca ich prawdziwą wartość. Zatem metoda `whileLoop` implementuje rekursywnie pętlę while w stylu Javy. + +Możemy połączyć ze sobą wykorzystanie [operatorów infiksowych/postfiksowych](operators.html) z tym mechanizmem aby utworzyć bardziej złożone wyrażenia. + +Oto implementacja pętli w stylu wykonaj-dopóki: + +```tut +object TargetTest2 extends App { + def loop(body: => Unit): LoopUnlessCond = + new LoopUnlessCond(body) + protected class LoopUnlessCond(body: => Unit) { + def unless(cond: => Boolean) { + body + if (!cond) unless(cond) + } + } + var i = 10 + loop { + println("i = " + i) + i -= 1 + } unless (i == 0) +} +``` + +Funkcja `loop` przyjmuje ciało pętli oraz zwraca instancję klasy `LoopUnlessCond` (która enkapsuluje to ciało). Warto zwrócić uwagę, że ciało tej funkcji nie zostało jeszcze ewaluowane. Klasa `LoopUnlessCond` posiada metodę `unless`, którą możemy wykorzystać jako *operator infiksowy*. W ten sposób, uzyskaliśmy całkiem naturalną składnię dla naszej nowej pętli: `loop { < stats > } unless ( < cond > )`. + +Oto wynik działania programu `TargetTest2`: + +``` +i = 10 +i = 9 +i = 8 +i = 7 +i = 6 +i = 5 +i = 4 +i = 3 +i = 2 +i = 1 +``` + diff --git a/pl/tutorials/tour/case-classes.md b/pl/tutorials/tour/case-classes.md new file mode 100644 index 0000000000..998fa6022d --- /dev/null +++ b/pl/tutorials/tour/case-classes.md @@ -0,0 +1,143 @@ +--- +layout: tutorial +title: Klasy przypadków + +disqus: true + +tutorial: scala-tour +num: 10 +language: pl +tutorial-next: pattern-matching +tutorial-previous: currying +--- + +Scala wspiera mechanizm _klas przypadków_. Klasy przypadków są zwykłymi klasami z dodatkowymi założeniami: + +* Domyślnie niemutowalne +* Można je dekomponować poprzez [dopasowanie wzorca](pattern-matching.html) +* Porównywane poprzez podobieństwo strukturalne zamiast przez referencje +* Zwięzła składnia tworzenia obiektów i operacji na nich + +Poniższy przykład obrazuje hierarchię typów powiadomień, która składa się z abstrakcyjnej klasy `Notification` oraz trzech konkretnych rodzajów zaimplementowanych jako klasy przypadków `Email`, `SMS` i `VoiceRecording`: + +```tut +abstract class Notification +case class Email(sourceEmail: String, title: String, body: String) extends Notification +case class SMS(sourceNumber: String, message: String) extends Notification +case class VoiceRecording(contactName: String, link: String) extends Notification +``` + +Tworzenie obiektu jest bardzo proste: (Zwróć uwagę na to, że słowo `new` nie jest wymagane) + +```tut +val emailFromJohn = Email("john.doe@mail.com", "Greetings From John!", "Hello World!") +``` + +Parametry konstruktora klasy przypadków są traktowane jako publiczne wartości i można się do nich odwoływać bezpośrednio: + +```tut +val title = emailFromJohn.title +println(title) // wypisuje "Greetings From John!" +``` + +W klasach przypadków nie można modyfikować wartości pól. (Z wyjątkiem sytuacji kiedy dodasz `var` przed nazwą pola) + +```tut:fail +emailFromJohn.title = "Goodbye From John!" // Jest to błąd kompilacji, gdyż pola klasy przypadku są domyślnie niezmienne +``` + +Zamiast tego, możesz utworzyć kopię używając metody `copy`: + +```tut +val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!") + +println(emailFromJohn) // wypisuje "Email(john.doe@mail.com,Greetings From John!,Hello World!)" +println(editedEmail) // wypisuje "Email(john.doe@mail.com,I am learning Scala,It's so cool!)" +``` + +Dla każdej klasy przypadku kompilator Scali wygeneruje metodę `equals`, która implementuje strukturalne porównanie obiektów oraz metodę `toString`. Przykład: + +```tut +val firstSms = SMS("12345", "Hello!") +val secondSms = SMS("12345", "Hello!") + +if (firstSms == secondSms) { + println("They are equal!") +} + +println("SMS is: " + firstSms) +``` + +Wypisze: + +``` +They are equal! +SMS is: SMS(12345, Hello!) +``` + +Jednym z najważniejszych zastosowań klas przypadków (skąd też się wzięła ich nazwa), jest **dopasowanie wzorca**. Poniższy przykład pokazuje działanie funkcji, która zwraca różne komunikaty, w zależności od rodzaju powiadomienia: + +```tut +def showNotification(notification: Notification): String = { + notification match { + case Email(email, title, _) => + "You got an email from " + email + " with title: " + title + case SMS(number, message) => + "You got an SMS from " + number + "! Message: " + message + case VoiceRecording(name, link) => + "you received a Voice Recording from " + name + "! Click the link to hear it: " + link + } +} + +val someSms = SMS("12345", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") + +println(showNotification(someSms)) +println(showNotification(someVoiceRecording)) + +// wypisuje: +// You got an SMS from 12345! Message: Are you there? +// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +``` + +Poniżej bardziej skomplikowany przykład używający `if` w celu określenia dodatkowych warunków dopasowania: + +```tut +def showNotificationSpecial(notification: Notification, specialEmail: String, specialNumber: String): String = { + notification match { + case Email(email, _, _) if email == specialEmail => + "You got an email from special someone!" + case SMS(number, _) if number == specialNumber => + "You got an SMS from special someone!" + case other => + showNotification(other) // nic szczególnego, wywołaj domyślną metodę showNotification + } +} + +val SPECIAL_NUMBER = "55555" +val SPECIAL_EMAIL = "jane@mail.com" +val someSms = SMS("12345", "Are you there?") +val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123") +val specialEmail = Email("jane@mail.com", "Drinks tonight?", "I'm free after 5!") +val specialSms = SMS("55555", "I'm here! Where are you?") + +println(showNotificationSpecial(someSms, SPECIAL_EMAIL, SPECIAL_NUMBER)) +println(showNotificationSpecial(someVoiceRecording, SPECIAL_EMAIL, SPECIAL_NUMBER)) +println(showNotificationSpecial(specialEmail, SPECIAL_EMAIL, SPECIAL_NUMBER)) +println(showNotificationSpecial(specialSms, SPECIAL_EMAIL, SPECIAL_NUMBER)) + +// wypisuje: +// You got an SMS from 12345! Message: Are you there? +// you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123 +// You got an email from special someone! +// You got an SMS from special someone! + +``` + +Programując w Scali, zachęca się abyś jak najszerzej używał klas przypadków do modelowania danych, jako że kod który je wykorzystuje jest bardziej ekspresywny i łatwiejszy do utrzymania: + +* Obiekty niemutowalne uwalniają cię od potrzeby śledzenia zmian stanu +* Porównanie przez wartość pozwala na porównywanie instancji tak jakby były prymitywnymi wartościami +* Dopasowanie wzorca znacząco upraszcza logikę rozgałęzień, co prowadzi do mniejszej ilości błędów i czytelniejszego kodu + + diff --git a/pl/tutorials/tour/classes.md b/pl/tutorials/tour/classes.md new file mode 100644 index 0000000000..dee935059d --- /dev/null +++ b/pl/tutorials/tour/classes.md @@ -0,0 +1,53 @@ +--- +layout: tutorial +title: Klasy + +disqus: true + +tutorial: scala-tour +num: 3 +language: pl +tutorial-next: traits +tutorial-previous: unified-types +--- + +Klasy w Scali określają schemat obiektów podczas wykonania programu. Oto przykład definicji klasy `Point`: + +```tut +class Point(var x: Int, var y: Int) { + def move(dx: Int, dy: Int): Unit = { + x = x + dx + y = y + dy + } + override def toString: String = + "(" + x + ", " + y + ")" +} +``` + +Klasy w Scali są sparametryzowane poprzez argumenty konstruktora. Powyższy kod wymaga podania dwóch argumentów konstruktora: `x` i `y`. Oba parametry są widoczne w zasięgu ciała klasy. + +Klasa `Point` zawiera także dwie metody: `move` i `toString`. `move` pobiera dwa argumenty w postaci liczb całkowitych, ale nie zwraca żadnej wartości (zwracany typ `Unit` odpowiada `void` w językach takich jak Java). Z drugiej strony `toString` nie wymaga żadnych parametrów, ale zwraca łańcuch znaków typu `String`. Ponieważ `toString` przesłania predefiniowaną metodę `toString`, jest ona oznaczona słowem kluczowym `override`. + +Należy dodać, że w Scali nie jest wymagane podanie słowa kluczowego `return` w celu zwrócenia wartości. Dzięki temu, że każdy blok kodu w Scali jest wyrażeniem, wartością zwracaną przez metodę jest ostatnie wyrażenie w ciele metody. Dodatkowo proste wyrażenia, takie jak zaprezentowane na przykładzie implementacji `toString` nie wymagają podania klamer, zatem można je umieścić bezpośrednio po definicji metody. + +Instancje klasy można tworzyć w następujący sposób: + +```tut +object Classes { + def main(args: Array[String]) { + val pt = new Point(1, 2) + println(pt) + pt.move(10, 10) + println(pt) + } +} +``` + +Program definiuje wykonywalną aplikację w postaci [obiektu singleton](singleton-objects.html) z główną metodą `main`. Metoda `main` tworzy nową instancję typu `Point` i zapisuje ją do wartości `pt`. Istotną rzeczą jest to, że wartości zdefiniowane z użyciem słowa kluczowego `val` różnią się od zmiennych określonych przez `var` (jak w klasie `Point` powyżej), tym że nie dopuszczają aktualizacji ich wartości. + +Wynik działania programu: + +``` +(1, 2) +(11, 12) +``` diff --git a/pl/tutorials/tour/compound-types.md b/pl/tutorials/tour/compound-types.md new file mode 100644 index 0000000000..c3d90b8484 --- /dev/null +++ b/pl/tutorials/tour/compound-types.md @@ -0,0 +1,52 @@ +--- +layout: tutorial +title: Typy złożone + +disqus: true + +tutorial: scala-tour +num: 23 +language: pl +tutorial-next: explicitly-typed-self-references +tutorial-previous: abstract-types +--- + +Czasami konieczne jest wyrażenie, że dany typ jest podtypem kilku innych typów. W Scali wyraża się to za pomocą *typów złożonych*, które są częścią wspólną typów obiektów. + +Załóżmy, że mamy dwie cechy `Cloneable` i `Resetable`: + +```tut +trait Cloneable extends java.lang.Cloneable { + override def clone(): Cloneable = { + super.clone().asInstanceOf[Cloneable] + } +} +trait Resetable { + def reset: Unit +} +``` + +Teraz chcielibyśmy napisać funkcję `cloneAndReset`, która przyjmuje obiekt, klonuje go i resetuje oryginalny obiekt: + +``` +def cloneAndReset(obj: ?): Cloneable = { + val cloned = obj.clone() + obj.reset + cloned +} +``` + +Pojawia się pytanie, jakiego typu powinen być parametr `obj`. Jeżeli jest to `Cloneable`, to dany obiekt może zostać sklonowany, ale nie zresetowany. W przypadku gdy jest to to `Resetable`, możemy go zresetować, ale nie mamy dostępu do operacji klonowania. Aby uniknąć rzutowania typów w tej sytuacji, możemy określić typ `obj` tak, aby był jednocześnie `Cloneable` i `Resetable`. Ten złożony typ jest zapisywany w taki sposób: `Cloneable with Resetable`. + +Zaktualizowana funkcja: + +``` +def cloneAndReset(obj: Cloneable with Resetable): Cloneable = { + //... +} +``` + +Typy złożone mogą składać się z kilku typów obiektów i mogą mieć tylko jedno wyrafinowanie, które może być użyte do zawężenia sygnatury istniejących elementów obiektu. +Przyjmują one postać: `A with B with C ... { wyrafinowanie }` + +Przykład użycia wyrafinowania typów jest pokazany na stronie o [typach abstrakcyjnych](abstract-types.html). diff --git a/pl/tutorials/tour/currying.md b/pl/tutorials/tour/currying.md new file mode 100644 index 0000000000..60fc81961d --- /dev/null +++ b/pl/tutorials/tour/currying.md @@ -0,0 +1,41 @@ +--- +layout: tutorial +title: Rozwijanie funkcji (Currying) + +disqus: true + +tutorial: scala-tour +num: 9 +language: pl +tutorial-next: case-classes +tutorial-previous: nested-functions +--- + +Funkcja może określić dowolną ilość list parametrów. Kiedy jest ona wywołana dla mniejszej liczby niż zostało to zdefiniowane, wtedy zwraca ona funkcję pobierającą dalsze listy parametrów jako jej argument. + +Przykład rozwijania funkcji: + +```tut +object CurryTest extends App { + + def filter(xs: List[Int], p: Int => Boolean): List[Int] = + if (xs.isEmpty) xs + else if (p(xs.head)) xs.head :: filter(xs.tail, p) + else filter(xs.tail, p) + + def modN(n: Int)(x: Int) = ((x % n) == 0) + + val nums = List(1, 2, 3, 4, 5, 6, 7, 8) + println(filter(nums, modN(2))) + println(filter(nums, modN(3))) +} +``` + +_Uwaga: metoda `modN` jest częściowo zastosowana dla dwóch wywołań `filter`, gdyż jest wywołana tylko dla jej pierwszego argumentu. Wyrażenie `modN(2)` zwraca funkcję typu `Int => Boolean` stąd też może być przekazane jako drugi argument funkcji `filter`._ + +Wynik działania powyższego programu: + +``` +List(2,4,6,8) +List(3,6) +``` diff --git a/pl/tutorials/tour/default-parameter-values.md b/pl/tutorials/tour/default-parameter-values.md new file mode 100644 index 0000000000..23ed9c0d2f --- /dev/null +++ b/pl/tutorials/tour/default-parameter-values.md @@ -0,0 +1,72 @@ +--- +layout: tutorial +title: Domyślne wartości parametrów + +disqus: true + +tutorial: scala-tour +num: 32 +language: pl +tutorial-next: named-parameters +tutorial-previous: annotations +--- + +Scala zezwala na określenie domyślnych wartości dla parametrów, co pozwala wyrażeniu wywołującemu ją na pominięcie tych parametrów. + +W Javie powszechną praktyką jest definiowanie implementacji metod, które służa wyłącznie określeniu domyślnych wartości dla pewnych parametrów dużych metod. Najczęściej stosuje się to w konstruktorach: + +```java +public class HashMap { + public HashMap(Map m); + /** Utwórz mapę z domyślną pojemnością (16) + * i loadFactor (0.75) + */ + public HashMap(); + /** Utwórz mapę z domyślnym loadFactor (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); +} +``` + +Mamy tutaj do czynienia tylko z dwoma konstruktorami. Pierwszy przyjmuje inną mapę, a drugi wymaga podania pojemności i load factor. Trzeci oraz czwarty konstruktor pozwala użytkownikom `HashMap` na tworzenie instancji z domyślnymi wartościami tych parametrów, które są prawdopodobnie dobre w większości przypadków. + +Bardziej problematyczne jest to, że domyślne wartości zapisane są zarówno w Javadoc oraz w kodzie. Można łatwo zapomnieć o odpowiedniej aktualizacji tych wartości. Dlatego powszechnym wzorcem jest utworzenie publicznych stałych, których wartości pojawią się w Javadoc: + +```java +public class HashMap { + public static final int DEFAULT_CAPACITY = 16; + public static final float DEFAULT_LOAD_FACTOR = 0.75; + + public HashMap(Map m); + /** Utwórz mapę z domyślną pojemnością (16) + * i loadFactor (0.75) + */ + public HashMap(); + /** Utwórz mapę z domyślnym loadFactor (0.75) */ + public HashMap(int initialCapacity); + public HashMap(int initialCapacity, float loadFactor); +} +``` + +Mimo że powstrzymuje to nas od powtarzania się, to podejście nie jest zbyt wyraziste. + +Scala wprowadza bezpośrednie wsparcie dla domyślnych parametrów: + +```tut +class HashMap[K,V](initialCapacity: Int = 16, loadFactor: Float = 0.75f) { +} + +// Używa domyślnych wartości +val m1 = new HashMap[String,Int] + +// initialCapacity 20, domyślny loadFactor +val m2 = new HashMap[String,Int](20) + +// nadpisujemy oba +val m3 = new HashMap[String,Int](20,0.8f) + +// nadpisujemy tylko loadFactor przez argumenty nazwane +val m4 = new HashMap[String,Int](loadFactor = 0.8f) +``` + +Należy zwrócić uwagę na to, w jaki sposób możemy wykorzystać *dowolną* domyślną wartość poprzez użycie [parametrów nazwanych](named-parameters.html). diff --git a/pl/tutorials/tour/explicitly-typed-self-references.md b/pl/tutorials/tour/explicitly-typed-self-references.md new file mode 100644 index 0000000000..c7ca092432 --- /dev/null +++ b/pl/tutorials/tour/explicitly-typed-self-references.md @@ -0,0 +1,124 @@ +--- +layout: tutorial +title: Jawnie typowane samoreferencje + +disqus: true + +tutorial: scala-tour +num: 24 +language: pl +tutorial-next: implicit-parameters +tutorial-previous: compound-types +--- + +Dążąc do tego, aby nasze oprogramowanie było rozszerzalne, często przydatne okazuje się jawne deklarowanie typu `this`. Aby to umotywować, spróbujemy opracować rozszerzalną reprezentację grafu w Scali. + +Oto definicja opisująca grafy: + +```tut +abstract class Graph { + type Edge + type Node <: NodeIntf + abstract class NodeIntf { + def connectWith(node: Node): Edge + } + def nodes: List[Node] + def edges: List[Edge] + def addNode: Node +} +``` + +Grafy składają się z listy węzłów oraz krawędzi, gdzie zarówno typ węzła jak i krawędzi jest abstrakcyjny. Użycie [typów abstrakcyjnych](abstract-types.html) pozwala implementacjom cechy `Graph` na to, by określały swoje konkretne klasy dla węzłów i krawędzi. Ponadto graf zawiera metodę `addNode`, której celem jest dodanie nowych węzłów do grafu. Węzły są połączone z użyciem metody `connectWith`. + +Przykład implementacji klasy `Graph`: + +```tut:fail +abstract class DirectedGraph extends Graph { + type Edge <: EdgeImpl + class EdgeImpl(origin: Node, dest: Node) { + def from = origin + def to = dest + } + class NodeImpl extends NodeIntf { + def connectWith(node: Node): Edge = { + val edge = newEdge(this, node) + edges = edge :: edges + edge + } + } + protected def newNode: Node + protected def newEdge(from: Node, to: Node): Edge + var nodes: List[Node] = Nil + var edges: List[Edge] = Nil + def addNode: Node = { + val node = newNode + nodes = node :: nodes + node + } +} +``` + +Klasa `DirectedGraph` częściowo implementuje i jednocześnie specjalizuje klasę `Graph`. Implementacja jest tylko częsciowa, ponieważ chcemy pozwolić na dalsze jej rozszerzanie. Dlatego szczegóły implementacyjne są pozostawione dla klas pochodnych co wymaga też określenia typu krawędzi oraz wierzchołków jako abstrakcyjne. Niemniej klasa `DirectedGraph` zawęża te typy do klas `EdgeImpl` oraz `NodeImpl`. + +Ponieważ konieczne jest udostępnienie możliwości tworzenia wierzchołków i krawędzi w naszej częściowej implementacji grafu, dodane zostały metody fabrykujące `newNode` oraz `newEdge`. Metody `addNode` wraz z `connectWith` są zdefiniowane na podstawie tych metod fabrykujących. + +Jeżeli przyjrzymy się bliżej implementacji metody `connectWith`, możemy dostrzec, że tworząc krawędź, musimy przekazać samoreferencję `this` do metody fabrykującej `newEdge`. Lecz `this` jest już powązany z typem `NodeImpl`, który nie jest kompatybilny z typem `Node`, ponieważ jest on tylko ograniczony z góry typem `NodeImpl`. Wynika z tego, iż powyższy program nie jest prawidłowy i kompilator Scali wyemituje błąd kompilacji. + +Scala rozwiązuje ten problem pozwalając na powiązanie klasy z innym typem poprzez jawne typowanie samoreferencji. Możemy użyć tego mechanizmu, aby naprawić powyższy kod: + +```tut + abstract class DirectedGraph extends Graph { + type Edge <: EdgeImpl + class EdgeImpl(origin: Node, dest: Node) { + def from = origin + def to = dest + } + class NodeImpl extends NodeIntf { + self: Node => // określenie typu "self" + def connectWith(node: Node): Edge = { + val edge = newEdge(this, node) // w tej chwili się skompiluje + edges = edge :: edges + edge + } + } + protected def newNode: Node + protected def newEdge(from: Node, to: Node): Edge + var nodes: List[Node] = Nil + var edges: List[Edge] = Nil + def addNode: Node = { + val node = newNode + nodes = node :: nodes + node + } + } +``` + +W nowej definicji klasy `NodeImpl` referencja `this` jest typu `Node`. Ponieważ typ `Node` jest abstrakcyjny i stąd nie wiemy jeszcze, czy `NodeImpl` w rzeczywistości odpowiada `Node`, system typów w Scali nie pozwoli nam na utworzenie tego typu. Mimo wszystko za pomocą jawnej adnotacji typu stwierdzamy, że w pewnym momencie klasa pochodna od `NodeImpl` musi odpowiadać typowi `Node`, aby dało się ją utworzyć. + +Oto konkretna specjalizacja `DirectedGraph`, gdzie abstrakcyjne elementy klasy mają ustalone ścisłe znaczenie: + +```tut +class ConcreteDirectedGraph extends DirectedGraph { + type Edge = EdgeImpl + type Node = NodeImpl + protected def newNode: Node = new NodeImpl + protected def newEdge(f: Node, t: Node): Edge = + new EdgeImpl(f, t) +} +``` + +Należy dodać, że w tej klasie możemy utworzyć `NodeImpl`, ponieważ wiemy już teraz, że `NodeImpl` określa klasę pochodną od `Node` (która jest po prostu aliasem dla `NodeImpl`). + +Poniżej przykład zastosowania klasy `ConcreteDirectedGraph`: + +```tut +object GraphTest extends App { + val g: Graph = new ConcreteDirectedGraph + val n1 = g.addNode + val n2 = g.addNode + val n3 = g.addNode + n1.connectWith(n2) + n2.connectWith(n3) + n1.connectWith(n3) +} +``` diff --git a/pl/tutorials/tour/extractor-objects.md b/pl/tutorials/tour/extractor-objects.md new file mode 100644 index 0000000000..3f100ca99d --- /dev/null +++ b/pl/tutorials/tour/extractor-objects.md @@ -0,0 +1,42 @@ +--- +layout: tutorial +title: Obiekty ekstraktorów + +disqus: true + +tutorial: scala-tour +num: 15 +language: pl +tutorial-next: sequence-comprehensions +tutorial-previous: regular-expression-patterns +--- + +W Scali, wzorce mogą być zdefiniowane niezależnie od klas przypadków. Obiekt posiadający metodę `unapply` może funkcjonować jako tak zwany ekstraktor. Jest to szczególna metoda, która pozwala na odwrócenie zastosowania obiektu dla pewnych danych. Jego celem jest ekstrakcja danych, z których został on utworzony. Dla przykładu, poniższy kod definiuje ekstraktor dla [obiektu](singleton-objects.html) `Twice`: + +```tut +object Twice { + def apply(x: Int): Int = x * 2 + def unapply(z: Int): Option[Int] = if (z%2 == 0) Some(z/2) else None +} + +object TwiceTest extends App { + val x = Twice(21) + x match { case Twice(n) => Console.println(n) } +} +``` + +Mamy tutaj do czynienia z dwiema konwencjami syntaktycznymi: + +Wyrażenie `case Twice(n)` prowadzi do wywołania `Twice.unapply`, który dopasowuje liczby parzyste. Wartość zwrócona przez metodę `unapply` określa czy argument został dopasowany lub nie, oraz wartość `n` wykorzystaną dalej w dopasowaniu danego przypadku. Tutaj jest to wartość `z/2`. + +Metoda `apply` nie jest konieczna do dopasowania wzorców. Jest jedynie wykorzystywana do udawania konstruktora. `Twice(21)` jest równoważne `Twice.apply(21)`. + +Typ zwracany przez `unapply` powinien odpowiadać jednemu przypadkowi: + +* Jeżeli jest to tylko test, należy zwrócić Boolean. Na przykład: `case even()` +* Jeżeli zwraca pojedynczą wartość typu T, powinien zwrócić `Option[T]` +* Jeżeli zwraca kilka wartości typów: `T1, ..., Tn`, należy je pogrupować jako opcjonalna krotka `Option[(T1, ..., Tn)]` + +Zdarza się, że chcielibyśmy dopasować określoną liczbę wartości oraz sekwencję. Z tego powodu, możesz także zdefiniować wzorce poprzez metodę `unapplySeq`. Ostatnia wartość typu `Tn` powinna być `Seq[S]`. Ten mechanizm pozwala na dopasowanie wzorców takich jak `case List(x1, ..., xn)`. + +Ekstraktory sprawiają, że kod jest łatwiejszy do utrzymania. Aby dowiedzieć się więcej, możesz przeczytać publikację ["Matching Objects with Patterns"](http://lamp.epfl.ch/~emir/written/MatchingObjectsWithPatterns-TR.pdf) (zobacz sekcję 4) autorstwa Emir, Odersky i Williams (styczeń 2007). diff --git a/pl/tutorials/tour/generic-classes.md b/pl/tutorials/tour/generic-classes.md new file mode 100644 index 0000000000..e17a1374ac --- /dev/null +++ b/pl/tutorials/tour/generic-classes.md @@ -0,0 +1,49 @@ +--- +layout: tutorial +title: Klasy generyczne + +disqus: true + +tutorial: scala-tour +num: 17 +language: pl +tutorial-next: variances +tutorial-previous: sequence-comprehensions +--- + +Scala posiada wbudowaną obsługą klas parametryzowanych przez typy. Tego typu klasy generyczne są szczególnie użyteczne podczas tworzenia klas kolekcji. + +Poniższy przykład demonstruje zastosowanie parametrów generycznych: + +```tut +class Stack[T] { + var elems: List[T] = Nil + def push(x: T) { elems = x :: elems } + def top: T = elems.head + def pop() { elems = elems.tail } +} +``` + +Klasa `Stack` modeluje zmienny stos zawierający elementy dowolnego typu `T`. Parametr `T` narzuca ograniczenie dla metod takie, że tylko elementy typu `T` mogą zostać dodane do stosu. Podobnie metoda `top` może zwrócić tylko elementy danego typu. + +Przykłady zastosowania: + +```tut +object GenericsTest extends App { + val stack = new Stack[Int] + stack.push(1) + stack.push('a') + println(stack.top) + stack.pop() + println(stack.top) +} +``` + +Wyjściem tego programu będzie: + +``` +97 +1 +``` + +_Uwaga: podtypowanie typów generycznych jest domyślnie określane jako invariant (niezmienne). Oznacza to, że mając stos znaków typu `Stack[Char]`, nie można go użyć jako stos typu `Stack[Int]`. Byłoby to błędne, ponieważ pozwalałoby to nam na wprowadzenie liczb całkowitych do stosu znaków. Zatem `Stack[T]` jest tylko podtypem `Stack[S]` jeżeli `S = T`. Ponieważ jednak jest to dość ograniczające, Scala posiada [mechanizm adnotacji parametrów typów](variances.html) pozwalający na kontrolę zachowania podtypowania typów generycznych._ diff --git a/pl/tutorials/tour/higher-order-functions.md b/pl/tutorials/tour/higher-order-functions.md new file mode 100644 index 0000000000..10afa7ec53 --- /dev/null +++ b/pl/tutorials/tour/higher-order-functions.md @@ -0,0 +1,42 @@ +--- +layout: tutorial +title: Funkcje wyższego rzędu + +disqus: true + +tutorial: scala-tour +num: 7 +language: pl +tutorial-next: nested-functions +tutorial-previous: anonymous-function-syntax +--- + +Scala pozwala na definiowanie funkcji wyższego rzędu. Są to funkcje, które przyjmują funkcje jako parametry lub których wynik jest też funkcją. Poniżej znajduje się przykład funkcji `apply` który pobiera inną funkcję `f` i wartość `v`, po to by zwrócić wynik zastosowania `f` do `v`: + +```tut +def apply(f: Int => String, v: Int) = f(v) +``` + +_Uwaga: metody są automatycznie zamieniane na funkcje, jeżeli wymaga tego kontekst_ + +Praktyczny przykład: + +```tut +class Decorator(left: String, right: String) { + def layout[A](x: A) = left + x.toString() + right +} + +object FunTest extends App { + def apply(f: Int => String, v: Int) = f(v) + val decorator = new Decorator("[", "]") + println(apply(decorator.layout, 7)) +} +``` + +Wykonanie zwraca poniższy wynik: + +``` +[7] +``` + +W tym przykładzie, metoda `decorator.layout` jest automatycznie konwertowana do funkcji typu `Int => String`, czego wymaga funkcja `apply`. Warto dodać, że metoda `decorator.layout` jest polimorficzna, co oznacza, że jej sygnatura jest odpowiednio dopasowana przez kompilator, dzięki czemu gdy jest przekazana do funkcji `apply`, jest ona traktowana jako `Int => String`. diff --git a/pl/tutorials/tour/implicit-conversions.md b/pl/tutorials/tour/implicit-conversions.md new file mode 100644 index 0000000000..bf37ba5805 --- /dev/null +++ b/pl/tutorials/tour/implicit-conversions.md @@ -0,0 +1,52 @@ +--- +layout: tutorial +title: Konwersje niejawne + +disqus: true + +tutorial: scala-tour +num: 26 +language: pl +tutorial-next: polymorphic-methods +tutorial-previous: implicit-parameters +--- + +Konwersja niejawna z typu `S` do `T` jest określona przez wartość domniemaną, która jest funkcją typu `S => T` lub przez metodę domniemaną odpowiadającą funkcji tego typu. + +Konwersje niejawne mogą być są zastosowane w jednej z dwóch sytuacji: + +* Jeżeli wyrażenie `e` jest typu `S` i `S` nie odpowiada wymaganemu typowi `T`. +* W przypadku wyboru `e.m` z `e` typu `T`, jeżeli `m` nie jest elementem `T`. + +W pierwszym przypadku wyszukiwana jest konwersja `c`, którą można zastosować do `e` aby uzyskać wynik typu `T`. +W drugim przypadku wyszukiwana jest konwersja `c`, którą można zastosować do `e` i której wynik zawiera element nazwany `m`. + +Poniższa operacja na dwóch listach `xs` oraz `ys` typu `List[Int]` jest dopuszczalna: + +``` +xs <= ys +``` + +Zakładając, że metody niejawne `list2ordered` oraz `int2ordered` zdefiniowane poniżej znajdują się w danym zakresie: + +``` +implicit def list2ordered[A](x: List[A]) + (implicit elem2ordered: A => Ordered[A]): Ordered[List[A]] = + new Ordered[List[A]] { /* .. */ } + +implicit def int2ordered(x: Int): Ordered[Int] = + new Ordered[Int] { /* .. */ } +``` + +Domyślnie importowany obiekt `scala.Predef` deklaruje kilka predefiniowanych typów (np. `Pair`) i metod (np. `assert`) ale także wiele użytecznych konwersji niejawnych. + +Przykładowo, kiedy wywołujemy metodę Javy, która wymaga typu `java.lang.Integer`, dopuszczalne jest przekazanie typu `scala.Int`. Dzieje się tak ponieważ `Predef` definiuje poniższe konwersje niejawne: + +```tut +import scala.language.implicitConversions + +implicit def int2Integer(x: Int) = + java.lang.Integer.valueOf(x) +``` + +Aby zdefiniować własne konwersje niejawne, należy zaimportować `scala.language.implicitConversions` (albo uruchomić kompilator z opcją `-language:implicitConversions`). Ta funkcjonalność musi być włączona jawnie, ze względu na problemy jakie mogą się wiązać z ich nadmiernym stosowaniem. diff --git a/pl/tutorials/tour/implicit-parameters.md b/pl/tutorials/tour/implicit-parameters.md new file mode 100644 index 0000000000..e64d460041 --- /dev/null +++ b/pl/tutorials/tour/implicit-parameters.md @@ -0,0 +1,58 @@ +--- +layout: tutorial +title: Parametry domniemane + +disqus: true + +tutorial: scala-tour +num: 25 +language: pl +tutorial-next: implicit-conversions +tutorial-previous: explicitly-typed-self-references +--- + +Metodę z _parametrami domniemanymi_ można stosować tak samo jak każdą zwyczajną metodę. W takim przypadku etykieta `implicit` nie ma żadnego znaczenia. Jednak jeżeli odpowiednie argumenty dla parametrów domniemanych nie zostaną jawnie określone, to kompilator dostarczy je automatycznie. + +Argumenty które mogą być przekazywane jako parametry domniemane można podzielić na dwie kategorie: + +* Najpierw dobierane są takie identyfikatory, które są dostępne bezpośrednio w punkcie wywołania metody i które określają definicję lub parametr domniemany. +* W drugiej kolejności dobrane mogą być elementy modułów towarzyszących odpowiadających typom tych parametrów domniemanych, które są oznaczone jako `implicit`. + +W poniższym przykładzie zdefiniujemy metodę `sum`, która oblicza sumę listy elementów wykorzystując operacje `add` i `unit` obiektu `Monoid`. Należy dodać, że wartości domniemane nie mogą być zdefiniowane globalnie, tylko muszą być elementem pewnego modułu. + +```tut +/** Ten przykład wykorzystuje strukturę z algebry abstrakcyjnej aby zilustrować działanie parametrów domniemanych. Półgrupa jest strukturą algebraiczną na zbiorze A z łączną operacją (czyli taką, która spełnia warunek: add(x, add(y, z)) == add(add(x, y), z)) nazwaną add, która łączy parę obiektów A by zwrócić inny obiekt A. */ +abstract class SemiGroup[A] { + def add(x: A, y: A): A +} +/** Monoid jest półgrupą z elementem neutralnym typu A, zwanym unit. Jest to element, który połączony z innym elementem (przez metodę add) zwróci ten sam element. */ +abstract class Monoid[A] extends SemiGroup[A] { + def unit: A +} +object ImplicitTest extends App { + /** Aby zademonstrować jak działają parametry domniemane, najpierw zdefiniujemy monoidy dla łańcuchów znaków oraz liczb całkowitych. Słowo kluczowe implicit sprawia, że oznaczone nimi wartości mogą być użyte aby zrealizować parametry domniemane. */ + implicit object StringMonoid extends Monoid[String] { + def add(x: String, y: String): String = x concat y + def unit: String = "" + } + implicit object IntMonoid extends Monoid[Int] { + def add(x: Int, y: Int): Int = x + y + def unit: Int = 0 + } + /** Metoda sum pobiera List[A] i zwraca A, który jest wynikiem zastosowania monoidu do wszystkich kolejnych elementów listy. Oznaczając parametr m jako domniemany, sprawiamy że potrzebne jest tylko podanie parametru xs podczas wywołania, ponieważ mamy już List[A], zatem wiemy jakiego typu jest w rzeczywistości A, zatem wiemy też jakiego typu Monoid[A] potrzebujemy. Możemy więc wyszukać wartość val lub obiekt w aktualnym zasięgu, który ma odpowiadający typu i użyć go bez jawnego określania referencji do niego. */ + def sum[A](xs: List[A])(implicit m: Monoid[A]): A = + if (xs.isEmpty) m.unit + else m.add(xs.head, sum(xs.tail)) + + /** Wywołamy tutaj dwa razy sum, podając za każdym razem tylko listę. Ponieważ drugi parametr (m) jest domniemany, jego wartość jest wyszukiwana przez kompilator w aktualnym zasięgu, na podstawie typu monoidu wymaganego w każdym przypadku, co oznacza że oba wyrażenia mogą być w pełni ewaluowane. */ + println(sum(List(1, 2, 3))) // używa IntMonoid + println(sum(List("a", "b", "c"))) // używa StringMonoid +} +``` + +Wynik powyższego programu: + +``` +6 +abc +``` diff --git a/pl/tutorials/tour/inner-classes.md b/pl/tutorials/tour/inner-classes.md new file mode 100644 index 0000000000..034191715c --- /dev/null +++ b/pl/tutorials/tour/inner-classes.md @@ -0,0 +1,98 @@ +--- +layout: tutorial +title: Klasy wewnętrzne + +disqus: true + +tutorial: scala-tour +num: 21 +language: pl +tutorial-next: abstract-types +tutorial-previous: lower-type-bounds +--- + +W Scali możliwe jest zdefiniowanie klasy jako element innej klasy. W przeciwieństwie do języków takich jak Java, gdzie tego typu wewnętrzne klasy są elementami ujmujących ich klas, w Scali są one związane z zewnętrznym obiektem. Aby zademonstrować tę różnicę, stworzymy teraz prostą implementację grafu: + +```tut +class Graph { + class Node { + var connectedNodes: List[Node] = Nil + def connectTo(node: Node) { + if (connectedNodes.find(node.equals).isEmpty) { + connectedNodes = node :: connectedNodes + } + } + } + var nodes: List[Node] = Nil + def newNode: Node = { + val res = new Node + nodes = res :: nodes + res + } +} +``` + +W naszym programie grafy są reprezentowane przez listę wierzchołków. Wierzchołki są obiektami klasy wewnętrznej `Node`. Każdy wierzchołek zawiera listę sąsiadów, które są przechowywane w liście `connectedNodes`. Możemy teraz skonfigurować graf z kilkoma wierzchołkami i połączyć je ze sobą: + +```tut +object GraphTest extends App { + val g = new Graph + val n1 = g.newNode + val n2 = g.newNode + val n3 = g.newNode + n1.connectTo(n2) + n3.connectTo(n1) +} +``` + +Teraz wzbogacimy nasz przykład o jawne typowanie, aby można było zobaczyć powiązanie typów wierzchołków z grafem: + +```tut +object GraphTest extends App { + val g: Graph = new Graph + val n1: g.Node = g.newNode + val n2: g.Node = g.newNode + val n3: g.Node = g.newNode + n1.connectTo(n2) + n3.connectTo(n1) +} +``` + +Ten kod pokazuje, że typ wierzchołka jest prefiksowany przez swoją zewnętrzną instancję (która jest obiektem `g` w naszym przykładzie). Jeżeli mielibyśmy dwa grafy, system typów w Scali nie pozwoli nam na pomieszanie wierzchołków jednego z wierzchołkami drugiego, ponieważ wierzchołki drugiego grafu są określone przez inny typ. + +Przykład niedopuszczalnego programu: + +```tut:fail +object IllegalGraphTest extends App { + val g: Graph = new Graph + val n1: g.Node = g.newNode + val n2: g.Node = g.newNode + n1.connectTo(n2) // dopuszczalne + val h: Graph = new Graph + val n3: h.Node = h.newNode + n1.connectTo(n3) // niedopuszczalne! +} +``` + +Warto zwrócić uwagę na to, że ostatnia linia poprzedniego przykładu byłaby poprawnym programem w Javie. Dla wierzchołków obu grafów Java przypisałaby ten sam typ `Graph.Node`. W Scali także istnieje możliwość wyrażenia takiego typu, zapisując go w formie: `Graph#Node`. Jeżeli byśmy chcieli połączyć wierzchołki różnych grafów, musielibyśmy wtedy zmienić definicję implementacji naszego grafu w następujący sposób: + +```tut +class Graph { + class Node { + var connectedNodes: List[Graph#Node] = Nil + def connectTo(node: Graph#Node) { + if (connectedNodes.find(node.equals).isEmpty) { + connectedNodes = node :: connectedNodes + } + } + } + var nodes: List[Node] = Nil + def newNode: Node = { + val res = new Node + nodes = res :: nodes + res + } +} +``` + +> Ważne jest, że ten program nie pozwala nam na dołączenie wierzchołka do dwóch różnych grafów. Jeżeli byśmy chcieli znieść to ograniczenie, należy zmienić typ zmiennej `nodes` na `Graph#Node`. diff --git a/pl/tutorials/tour/local-type-inference.md b/pl/tutorials/tour/local-type-inference.md new file mode 100644 index 0000000000..6cb61a7914 --- /dev/null +++ b/pl/tutorials/tour/local-type-inference.md @@ -0,0 +1,63 @@ +--- +layout: tutorial +title: Lokalna inferencja typów + +disqus: true + +tutorial: scala-tour +num: 28 +language: pl +tutorial-next: operators +tutorial-previous: polymorphic-methods +--- + +Scala posiada wbudowany mechanizm inferencji typów, który pozwala programiście pominąć pewne informacje o typach. Przykładowo zazwyczaj nie wymaga się podawania typów zmiennych, gdyż kompilator sam jest w stanie go wydedukować na podstawie typu wyrażenia inicjalizacji zmiennej. Także typy zwracane przez metody mogą być często pominięte, ponieważ odpowiadają one typowi ciała metody, który jest inferowany przez kompilator. + +Oto przykład: + +```tut +object InferenceTest1 extends App { + val x = 1 + 2 * 3 // typem x jest Int + val y = x.toString() // typem y jest String + def succ(x: Int) = x + 1 // metoda succ zwraca wartości typu Int +} +``` + +Dla metod rekurencyjnych kompilator nie jest w stanie określić zwracanego typu. Oto przykład programu, który zakończy się niepowodzeniem kompilacji z tego powodu: + +```tut:fail +object InferenceTest2 { + def fac(n: Int) = if (n == 0) 1 else n * fac(n - 1) +} +``` + +Nie jest też konieczne określenie parametrów typu, kiedy są wywoływane [metody polimorficzne](polymorphic-methods.html) lub kiedy tworzymy [klasy generyczne](generic-classes.html). Kompilator Scali sam określi typ brakujących parametrów typów na podstawie kontekstu oraz typów właściwych parametrów metody/konstruktora. + +Oto przykład który to ilustruje: + +``` +case class MyPair[A, B](x: A, y: B); +object InferenceTest3 extends App { + def id[T](x: T) = x + val p = MyPair(1, "scala") // typ: MyPair[Int, String] + val q = id(1) // typ: Int +} +``` + +Dwie ostatnie linie tego programu są równoważne poniższemu kodu, gdzie wszystkie inferowane typy są określone jawnie: + +``` +val x: MyPair[Int, String] = MyPair[Int, String](1, "scala") +val y: Int = id[Int](1) +``` + +W niektórych sytuacjach poleganie na inferencji typów w Scali może być niebezpieczne, tak jak demonstruje to poniższy program: + +```tut:fail +object InferenceTest4 { + var obj = null + obj = new Object() +} +``` + +Ten program się nie skompiluje, ponieważ typ określony dla zmiennej `obj` jest `Null`. Ponieważ jedyną wartością tego typu jest `null`, nie jest możliwe przypisanie tej zmiennej innej wartości. diff --git a/pl/tutorials/tour/lower-type-bounds.md b/pl/tutorials/tour/lower-type-bounds.md new file mode 100644 index 0000000000..dc8985226c --- /dev/null +++ b/pl/tutorials/tour/lower-type-bounds.md @@ -0,0 +1,56 @@ +--- +layout: tutorial +title: Dolne ograniczenia typów + +disqus: true + +tutorial: scala-tour +num: 20 +language: pl +tutorial-next: inner-classes +tutorial-previous: upper-type-bounds +--- + +Podczas gdy [górne ograniczenia typów](upper-type-bounds.html) zawężają typ do podtypu innego typu, *dolne ograniczenia typów* określają dany typ jako typ bazowy innego typu. Sformułowanie `T >: A` wyraża, że parametr typu `T` lub typ abstrakcyjny `T` odwołuje się do typu bazowego `A`. + +Oto przykład w którym jest to użyteczne: + +```tut +case class ListNode[T](h: T, t: ListNode[T]) { + def head: T = h + def tail: ListNode[T] = t + def prepend(elem: T): ListNode[T] = + ListNode(elem, this) +} +``` + +Powyższy program implementuje listę jednokierunkową z operacją dodania elementu na jej początek. Niestety typ ten jest niezmienny według parametru typu klasy `ListNode`, tzn. `ListNode[String]` nie jest podtypem `ListNode[Any]`. Z pomocą [adnotacji wariancji](variances.html) możemy wyrazić semantykę podtypowania: + +``` +case class ListNode[+T](h: T, t: ListNode[T]) { ... } +``` + +Niestety ten program się nie skompiluje, ponieważ adnotacja kowariancji może być zastosowana tylko jeżeli zmienna typu jest używana wyłącznie w pozycji kowariantnej. Jako że zmienna typu `T` występuje jako parametr typu metody `prepend`, ta zasada jest złamana. Z pomocą *dolnego ograniczenia typu*, możemy jednak zaimplementować tą metodę w taki sposób, że `T` występuje tylko w pozycji kowariantnej: + +```tut +case class ListNode[+T](h: T, t: ListNode[T]) { + def head: T = h + def tail: ListNode[T] = t + def prepend[U >: T](elem: U): ListNode[U] = + ListNode(elem, this) +} +``` + +_Uwaga:_ nowa wersja metody `prepend` ma mniej ograniczający typ. Przykładowo pozwala ona na dodanie obiektu typu bazowego elementów istniejącej listy. Wynikowa lista będzie listą tego typu bazowego. + +Przykład który to ilustruje: + +```tut +object LowerBoundTest extends App { + val empty: ListNode[Null] = ListNode(null, null) + val strList: ListNode[String] = empty.prepend("hello") + .prepend("world") + val anyList: ListNode[Any] = strList.prepend(12345) +} +``` + diff --git a/pl/tutorials/tour/mixin-class-composition.md b/pl/tutorials/tour/mixin-class-composition.md new file mode 100644 index 0000000000..cfe673f398 --- /dev/null +++ b/pl/tutorials/tour/mixin-class-composition.md @@ -0,0 +1,57 @@ +--- +layout: tutorial +title: Kompozycja domieszek + +disqus: true + +tutorial: scala-tour +num: 5 +language: pl +tutorial-next: anonymous-function-syntax +tutorial-previous: traits +--- + +W przeciwieństwie do języków które wspierają jedynie pojedyncze dziedziczenie, Scala posiada bardziej uogólniony mechanizm ponownego wykorzystania klas. Scala umożliwia na wykorzystanie _nowych elementów klasy_ (różnicy w stosunku do klasy bazowej) w definicji nowej klasy. Wyraża się to przy pomocy _kompozycji domieszek_. + +Rozważmy poniższe uogólnienie dla iteratorów: + +```tut +abstract class AbsIterator { + type T + def hasNext: Boolean + def next: T +} +``` + +Następnie, rozważmy klasę domieszkową, która doda do klasy `AbsIterator` metodę `foreach` wykonującą podaną funkcję dla każdego elementu zwracanego przez iterator. Aby zdefiniować klasę domieszkową użyjemy słowa kluczowego `trait`: + +```tut +trait RichIterator extends AbsIterator { + def foreach(f: T => Unit) { while (hasNext) f(next) } +} +``` + +Oto przykład konkretnego iteratora, który zwraca kolejne znaki w podanym łańcuchu znaków: + +```tut +class StringIterator(s: String) extends AbsIterator { + type T = Char + private var i = 0 + def hasNext = i < s.length() + def next = { val ch = s charAt i; i += 1; ch } +} +``` + +Chcielibyśmy także połączyć funkcjonalność `StringIterator` oraz `RichIterator` w jednej klasie. Z pojedynczym dziedziczeniem czy też samymi interfejsami jest to niemożliwe, gdyż obie klasy zawierają implementacje metod. Scala pozwala na rozwiązanie tego problemu z użyciem _kompozycji domieszek_. Umożliwia ona na ponowne wykorzystanie różnicy definicji klas, tzn. wszystkich definicji które nie zostały odziedziczone. Ten mechanizm pozwala nam na połączenie `StringIterator` z `RichIterator`, tak jak w poniższym przykładzie, gdzie chcielibyśmy wypisać w kolumnie wszystkie znaki z danego łańcucha: + +```tut +object StringIteratorTest { + def main(args: Array[String]) { + class Iter extends StringIterator(args(0)) with RichIterator + val iter = new Iter + iter foreach println + } +} +``` + +Klasa `iter` w funkcji `main` jest skonstruowana wykorzystując kompozycję domieszek `StringIterator` oraz `RichIterator` z użyciem słowa kluczowego `with`. Pierwszy rodzic jest nazywany _klasą bazową_ `Iter`, podczas gdy drugi (i każdy kolejny) rodzic jest nazywany _domieszką_. diff --git a/pl/tutorials/tour/named-parameters.md b/pl/tutorials/tour/named-parameters.md new file mode 100644 index 0000000000..31ed29217f --- /dev/null +++ b/pl/tutorials/tour/named-parameters.md @@ -0,0 +1,37 @@ +--- +layout: tutorial +title: Parametry nazwane + +disqus: true + +tutorial: scala-tour +num: 33 +language: pl +tutorial-previous: default-parameter-values +--- + +Wywołując metody i funkcje, możesz użyć nazwy parametru jawnie podczas wywołania: + +```tut + def printName(first:String, last:String) = { + println(first + " " + last) + } + + printName("John", "Smith") + // Wypisuje "John Smith" + printName(first = "John", last = "Smith") + // Wypisuje "John Smith" + printName(last = "Smith", first = "John") + // Wypisuje "John Smith" +``` + +Warto zwrócić uwagę na to, że kolejność wyboru parametrów podczas wywołania nie ma znaczenia, dopóki wszystkie parametry są nazwane. Ta funkcjonalność jest dobrze zintegrowana z [domyślnymi wartościami parametrów](default-parameter-values.html): + +```tut + def printName(first: String = "John", last: String = "Smith") = { + println(first + " " + last) + } + + printName(last = "Jones") + // Wypisuje "John Jones" +``` diff --git a/pl/tutorials/tour/nested-functions.md b/pl/tutorials/tour/nested-functions.md new file mode 100644 index 0000000000..40d349c004 --- /dev/null +++ b/pl/tutorials/tour/nested-functions.md @@ -0,0 +1,35 @@ +--- +layout: tutorial +title: Funkcje zagnieżdżone + +disqus: true + +tutorial: scala-tour +num: 8 +language: pl +tutorial-next: currying +tutorial-previous: higher-order-functions +--- + +Scala pozwala na zagnieżdżanie definicji funkcji. Poniższy obiekt określa funkcję `filter`, która dla danej listy filtruje elementy większe bądź równe podanemu progowi `threshold`: + +```tut +object FilterTest extends App { + def filter(xs: List[Int], threshold: Int) = { + def process(ys: List[Int]): List[Int] = + if (ys.isEmpty) ys + else if (ys.head < threshold) ys.head :: process(ys.tail) + else process(ys.tail) + process(xs) + } + println(filter(List(1, 9, 2, 8, 3, 7, 4), 5)) +} +``` + +_Uwaga: zagnieżdżona funkcja `process` odwołuje się do zmiennej `threshold` określonej w zewnętrznym zasięgu jako parametr `filter`_ + +Wynik powyższego programu: + +``` +List(1,2,3,4) +``` diff --git a/pl/tutorials/tour/operators.md b/pl/tutorials/tour/operators.md new file mode 100644 index 0000000000..e999f27a3b --- /dev/null +++ b/pl/tutorials/tour/operators.md @@ -0,0 +1,38 @@ +--- +layout: tutorial +title: Operatory + +disqus: true + +tutorial: scala-tour +num: 29 +language: pl +tutorial-next: automatic-closures +tutorial-previous: local-type-inference +--- + +Każda metoda, która przyjmuje jeden parametr może być użyta jako *operator infiksowy*. Oto definicja klasy `MyBool` która zawiera metody `and` i `or`: + +```tut +case class MyBool(x: Boolean) { + def and(that: MyBool): MyBool = if (x) that else this + def or(that: MyBool): MyBool = if (x) this else that + def negate: MyBool = MyBool(!x) +} +``` + +Można teraz użyć `and` i `or` jako operatory infiksowe: + +```tut +def not(x: MyBool) = x.negate +def xor(x: MyBool, y: MyBool) = (x or y) and not(x and y) +``` + +Można zauważyć, że dzięki zastosowaniu operatorów infiksowych metoda `xor` jest czytelniejsza. + +Dla porównania, oto kod który nie wykorzystuje operatorów infiksowych: + +```tut +def not(x: MyBool) = x.negate +def xor(x: MyBool, y: MyBool) = x.or(y).and(x.and(y).negate) +``` diff --git a/pl/tutorials/tour/pattern-matching.md b/pl/tutorials/tour/pattern-matching.md new file mode 100644 index 0000000000..886d11362c --- /dev/null +++ b/pl/tutorials/tour/pattern-matching.md @@ -0,0 +1,45 @@ +--- +layout: tutorial +title: Dopasowanie wzorców (Pattern matching) + +disqus: true + +tutorial: scala-tour +num: 11 +language: pl +tutorial-next: singleton-objects +tutorial-previous: case-classes +--- + +Scala posiada wbudowany mechanizm dopasowania wzorców. Umożliwia on dopasowanie dowolnego rodzaju danych, na podstawie zasady że zwracamy zawsze pierwsze dopasowanie. Przykład dopasowania liczby całkowitej: + +```tut +object MatchTest1 extends App { + def matchTest(x: Int): String = x match { + case 1 => "one" + case 2 => "two" + case _ => "many" + } + println(matchTest(3)) +} +``` + +Blok kodu z wyrażeniami `case` definiuje funkcję, która przekształca liczby całkowite na łańcuchy znaków. Słowo kluczowe `match` pozwala w wygodny sposób zastosować dopasowanie wzorca do obiektu. + +Wzorce można także dopasowywać do różnych typów wartości: + +```tut +object MatchTest2 extends App { + def matchTest(x: Any): Any = x match { + case 1 => "one" + case "two" => 2 + case y: Int => "scala.Int" + } + println(matchTest("two")) +} +``` + +Pierwszy przypadek jest dopasowany, gdy `x` jest liczbą całkowitą równą `1`. Drugi określa przypadek, gdy `x` jest równe łańcuchowi znaków `"two"`. Ostatecznie mamy wzorzec dopasowania typu. Jest on spełniony gdy `x` jest dowolną liczbą całkowitą oraz gwarantuje, że `y` jest (statycznie) typu liczby całkowitej. + +Dopasowanie wzorca w Scali jest najbardziej użyteczne z wykorzystaniem typów algebraicznych modelowanych przez [klasy przypadków](case-classes.html). +Scala także pozwala też na używanie wzorców niezależnie od klas przypadków, używając metody `unapply` definiowanej przez [obiekty ekstraktorów](extractor-objects.html). diff --git a/pl/tutorials/tour/polymorphic-methods.md b/pl/tutorials/tour/polymorphic-methods.md new file mode 100644 index 0000000000..ba662f8b4b --- /dev/null +++ b/pl/tutorials/tour/polymorphic-methods.md @@ -0,0 +1,30 @@ +--- +layout: tutorial +title: Metody polimorficzne + +disqus: true + +tutorial: scala-tour +num: 27 +language: pl +tutorial-next: local-type-inference +tutorial-previous: implicit-conversions +--- + +Metody w Scali mogą być parametryzowane zarówno przez wartości jak i typy. Tak jak na poziomie klas, wartości parametrów zawierają się w parze nawiasów okrągłych, podczas gdy parametry typów są deklarawane w parze nawiasów kwadratowych. + +Przykład poniżej: + +```tut +def dup[T](x: T, n: Int): List[T] = { + if (n == 0) + Nil + else + x :: dup(x, n - 1) +} + +println(dup[Int](3, 4)) +println(dup("three", 3)) +``` + +Metoda `dup` jest sparametryzowana przez typ `T` i parametry wartości `x: T` oraz `n: Int`. W pierwszym wywołaniu `dup` są przekazane wszystkie parametry, ale jak pokazuje kolejna linijka, nie jest wymagane jawne podanie właściwych parametrów typów. System typów w Scali może inferować tego rodzaju typy. Dokonuje się tego poprzez sprawdzenie jakiego typu są parametry dane jako wartości argumentów oraz na podstawie kontekstu wywołania metody. diff --git a/pl/tutorials/tour/regular-expression-patterns.md b/pl/tutorials/tour/regular-expression-patterns.md new file mode 100644 index 0000000000..c3caf9a43c --- /dev/null +++ b/pl/tutorials/tour/regular-expression-patterns.md @@ -0,0 +1,39 @@ +--- +layout: tutorial +title: Wzorce wyrażeń regularnych + +disqus: true + +tutorial: scala-tour +num: 14 +language: pl + +tutorial-next: extractor-objects +tutorial-previous: xml-processing +--- + +## Wzorce sekwencji ignorujące prawą stronę ## + +Wzorce ignorujące prawą stronę są użyteczne przy dekomponowaniu danych, które mogą być podtypem `Seq[A]` lub klasą przypadku z iterowalnym parametrem, jak w poniższym przykładzie: + +``` +Elem(prefix:String, label:String, attrs:MetaData, scp:NamespaceBinding, children:Node*) +``` + +W tych przypadkach, Scala pozwala wzorcom na zastosowanie symbolu `_*` w ostatniej pozycji aby dopasować sekwencje dowolnej długości. +Poniższy przykład demonstruje dopasowanie wzorca, który rozpoznaje początek sekwencji i wiąże resztę do zmiennej `rest`: + +```tut +object RegExpTest1 extends App { + def containsScala(x: String): Boolean = { + val z: Seq[Char] = x + z match { + case Seq('s','c','a','l','a', rest @ _*) => + println("rest is " + rest) + true + case Seq(_*) => + false + } + } +} +``` diff --git a/pl/tutorials/tour/sequence-comprehensions.md b/pl/tutorials/tour/sequence-comprehensions.md new file mode 100644 index 0000000000..32b9d900a7 --- /dev/null +++ b/pl/tutorials/tour/sequence-comprehensions.md @@ -0,0 +1,68 @@ +--- +layout: tutorial +title: Instrukcje for (For comprehension) + +disqus: true + +tutorial: scala-tour +num: 16 +language: pl +tutorial-next: generic-classes +tutorial-previous: extractor-objects +--- + +Scala posiada lekką składnię do wyrażania instrukcji for. Tego typu wyrażania przybierają formę `for (enumerators) yield e`, gdzie `enumerators` oznacza listę enumeratorów oddzielonych średnikami. *Enumerator* może być generatorem wprowadzającym nowe zmienne albo filtrem. Wyrażenie `e` określa wynik dla każdego powiązania wygenerowanego przez enumeratory i zwraca sekwencję tych wartości. + +Przykład: + +```tut +object ComprehensionTest1 extends App { + def even(from: Int, to: Int): List[Int] = + for (i <- List.range(from, to) if i % 2 == 0) yield i + Console.println(even(0, 20)) +} +``` + +To wyrażenie for w funkcji `even` wprowadza nową zmienną `i` typu `Int`, która jest kolejno wiązana ze wszystkimi wartościami listy `List(from, from + 1, ..., to - 1)`. Instrukcja `if i % 2 == 0` filtruje wszystkie liczby nieparzyste, tak aby ciało tego wyrażenia było obliczane tylko dla liczb parzystych. Ostatecznie całe to wyrażenie zwraca listę liczb parzystych. + +Program zwraca następujący wynik: + +``` +List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18) +``` + +Poniżej bardziej skomplikowany przykład, który oblicza wszystkie pary liczb od `0` do `n-1`, których suma jest równa danej wartości `v`: + +```tut +object ComprehensionTest2 extends App { + def foo(n: Int, v: Int) = + for (i <- 0 until n; + j <- i until n if i + j == v) yield + (i, j); + foo(20, 32) foreach { + case (i, j) => + println(s"($i, $j)") + } +} +``` + +Ten przykład pokazuje, że wyrażenia for nie są ograniczone do list. Każdy typ danych, który wspiera operacje: `withFilter`, `map` oraz `flatMap` (z odpowiednimi typami) może być użyty w instrukcjach for. + +Wynik powyższego programu: + +``` +(13, 19) +(14, 18) +(15, 17) +(16, 16) +``` + +Istnieje szczególna postać instrukcji for, które zwracają `Unit`. Tutaj wiązania utworzone przez listę generatorów i filtrów są użyte do wykonania efektów ubocznych. Aby to osiągnąć należy pominąć słowo kluczowe `yield`: + +``` +object ComprehensionTest3 extends App { + for (i <- Iterator.range(0, 20); + j <- Iterator.range(i, 20) if i + j == 32) + println(s"($i, $j)") +} +``` diff --git a/pl/tutorials/tour/singleton-objects.md b/pl/tutorials/tour/singleton-objects.md new file mode 100644 index 0000000000..4f97924d7e --- /dev/null +++ b/pl/tutorials/tour/singleton-objects.md @@ -0,0 +1,70 @@ +--- +layout: tutorial +title: Obiekty singleton + +disqus: true + +tutorial: scala-tour +num: 12 +language: pl + +tutorial-next: xml-processing +tutorial-previous: pattern-matching +--- + +Metody i wartości które nie są powiązane z konkretną instancją [klasy](classes.html) należą do *obiektów singleton*, określanych za pomocą słowa kluczowego `object` zamiast `class`. + +``` +package test + +object Blah { + def sum(l: List[Int]): Int = l.sum +} +``` + +Metoda `sum` jest dostępna globalnie i można się do niej odwołać lub importować jako `test.Blah.sum`. + +Obiekty singleton są swego rodzaju skrótem do definiowania pojedynczej instancji klasy, która nie powinna być bezpośrednio tworzona i która sama w sobie stanowi referencję do tego obiektu, jakby była określona jako `val`. + +Obiekt singleton może rozszerzać klasę lub cechę. Przykładowo [klasa przypadku](case-class.html) bez [parametrów typu](generic-class.html), domyślnie generuje obiekt singleton o tej samej nazwie, który implementuje cechę [`Function*`](http://www.scala-lang.org/api/current/scala/Function1.html). + +## Obiekt towarzyszący ## + +Duża część obiektów singleton nie istnieje samodzielnie, ale jest powiązana z klasą o tej samej nazwie. Obiekt singleton generowany dla klasy przypadku jest tego przykładem. Kiedy tak się dzieje, obiekt singleton jest zwany *obiektem towarzyszącym*. + +Klasa i jej obiekt towaryszący mogą być zdefiniowane tylko w tym samym pliku, przykład: + +```tut +class IntPair(val x: Int, val y: Int) + +object IntPair { + import math.Ordering + + implicit def ipord: Ordering[IntPair] = + Ordering.by(ip => (ip.x, ip.y)) +} +``` + +Bardzo powszechne jest użycie wzorca typeclass w połączeniu z [wartościami domniemanymi](implicit-parameters.html), takich jak `ipord` powyżej, zdefiniowanych w obiekcie towarzyszącym. Dzieje się tak, ponieważ elementy obiektu towarzyszącego są uwzględniane w procesie wyszukiwania domyślnych wartości domniemanych. + +## Uwagi dla programistów Javy ## + +`static` nie jest słowem kluczowym w Scali. Zamiast tego, wszystkie elementy, które powinny być statyczne (wliczając w to klasy) powinny zostać zamieszczone w obiekcie singleton. + +Często spotykanym wzorcem jest definiowanie statycznych elementów, np. jako prywatne, pomocniczo dla ich instancji. W Scali przenosi się je do obiektu towarzyszącego: + +``` +class X { + import X._ + + def blah = foo +} + +object X { + private def foo = 42 +} +``` + +Ten przykład ilustruje inną właściwość Scali: w kontekście zasięgu prywatnego, klasa i jej obiekt towarzyszący mają wzajemny dostęp do swoich pól. Aby sprawić, żeby dany element klasy jest *naprawdę* stał się prywatny należy go zadeklarować jako `private[this]`. + +Dla wygodnej współpracy z Javą, metody oraz pola klasy w obiekcie singleton, mają także statyczne metody zdefiniowane w obiekcie towarzyszącym, nazywane *static forwarder*. Dostęp do innych elementów można uzyskać poprzez statyczne pole `X$.MODULE$` dla obiektu `X`. diff --git a/pl/tutorials/tour/tour-of-scala.md b/pl/tutorials/tour/tour-of-scala.md new file mode 100644 index 0000000000..7026b44557 --- /dev/null +++ b/pl/tutorials/tour/tour-of-scala.md @@ -0,0 +1,46 @@ +--- +layout: tutorial +title: Wprowadzenie + +disqus: true + +tutorial: scala-tour +num: 1 +outof: 33 +language: pl +tutorial-next: unified-types +--- + +Scala jest nowoczesnym, wieloparadygmatowym językiem programowania zaprojektowanym do wyrażania powszechnych wzorców programistycznych w zwięzłym, eleganckim i bezpiecznie typowanym stylu. Scala płynnie integruje ze sobą cechy języków funkcyjnych i zorientowanych obiektowo. + +## Scala jest zorientowana obiektowo ## +Scala jest czysto obiektowym językiem w tym sensie, że każda [wartość jest obiektem](unified-types.html). Typy oraz zachowania obiektów są opisane przez [klasy](classes.html) oraz [cechy](traits.html). Klasy są rozszerzane przez podtypowanie i elastyczny mechanizm [kompozycji domieszek](mixin-class-composition.html) jako zastępnik dla wielodziedziczenia. + +## Scala jest funkcyjna ## +Scala jest też funkcyjnym językiem w tym sensie, że [każda funkcja jest wartością](unified-types.html). Scala dostarcza [lekką składnię](anonymous-function-syntax.html) do definiowana funkcji anonimowych, wspiera [funkcje wyższego rzędu](higher-order-functions.html), pozwala funkcjom by były [zagnieżdżone](nested-functions.html) oraz umożliwia [rozwijanie funkcji](currying.html). [Klasy przypadków](case-classes.html) oraz wbudowane wsparcie dla [dopasowania wzorców](pattern-matching.html) wprowadzają do Scali mechanizm typów algebraicznych, stosowany w wielu funkcyjnych językach programowania. [Obiekty singleton](singleton-objects) są wygodną metodą grupowania funkcji, które nie należą do żadnej klasy. + +Ponadto, mechanizm dopasowania wzorca w naturalny sposób rozszerza się do obsługi [przetwarzania danych w formacie XML](xml-processing.html) z pomocą [wzorców sekwencji ignorujących prawą stronę](regular-expression-patterns.html), z wykorzystaniem rozszerzeń [obiektów ekstraktorów](extractor-objects.html). W tym kontekście, [instrukcje for](sequence-comprehensions.html) są użyteczne w formułowaniu zapytań. Ta funkcjonalność sprawia, że Scala jest idealnym językiem do tworzenia aplikacji takich jak usługi sieciowe. + +## Scala jest statycznie typowana ## +Scala posiada ekspresywny system typów, który zapewnia, że abstrakcje są używane w sposób zgodny oraz bezpieczny. W szczególności, system typów wspiera: + +* [klasy generyczne](generic-classes.html) +* [adnotacje wariancji](variances.html) +* [górne](upper-type-bounds.html) oraz [dolne](lower-type-bounds.html) ograniczenia typów +* [klasy zagnieżdżone](inner-classes.html) i [typy abstrakcyjne](abstract-types.html) jako elementy obiektów +* [typy złożone](compound-types.html) +* [jawnie typowane samoreferencje](explicitly-typed-self-references.html) +* [parametry domniemane](implicit-parameters.html) i [konwersje niejawne](implicit-conversions.html) +* [metody polimorficzne](polymorphic-methods.html) + +[Mechanizm lokalnej inferencji typów](local-type-inference.html) sprawia, że nie jest konieczne podawanie nadmiarowych informacji o typach w programie. W połączeniu, te funkcje języka pozwalają na bezpiecznie typowane ponowne wykorzystanie programistycznych abstrakcji. + +## Scala jest rozszerzalna ## +W praktyce, rozwiązania specyficzne dla domeny wymagają odpowiednich rozszerzeń języka. Scala dostarcza unikalne mechanizmy, dzięki którym można łatwo dodawać nowe konstrukcje do języka w postaci bibliotek: + +* każda metoda może być używana jako [operator infiksowy lub prefiksowy](operators.html) +* [domknięcia są konstruowane automatycznie zależnie od wymaganego typu](automatic-closures.html) + +Powyższe mechanizmy pozwalają na definicję nowych rodzajów wyrażeń, bez potrzeby rozszerzania składni języka czy też wykorzystywania meta-programowania w postaci makr. + +Scala jest zaprojektowana tak, aby współpracować dobrze ze środowiskiem uruchomieniowym JRE oraz językiem Java. Funkcje Javy takie jak [adnotacje](annotations.html) oraz typy generyczne posiadają swoje bezpośrednie odwzorowanie w Scali. Unikalne funkcje Scali, jak na przykład [domyślne wartości parametrów](default-parameter-values.html) oraz [nazwane parametry](named-parameters.html) są kompilowane w taki sposób, aby zachować jak największą zgodność z Javą. Scala ma także taki sam model kompilacji (oddzielna kompilacja, dynamiczne ładowanie klas) dzięki czemu umożliwia korzystanie z całego ekosystemu Javy. diff --git a/pl/tutorials/tour/traits.md b/pl/tutorials/tour/traits.md new file mode 100644 index 0000000000..21d528df7b --- /dev/null +++ b/pl/tutorials/tour/traits.md @@ -0,0 +1,56 @@ +--- +layout: tutorial +title: Cechy + +disqus: true + +tutorial: scala-tour +num: 4 +language: pl +tutorial-next: mixin-class-composition +tutorial-previous: classes +--- + +Zbliżone do interfejsów Javy, cechy są wykorzystywane do definiowania typów obiektów poprzez określenie sygnatur wspieranych metod. Podobnie jak w Javie 8, Scala pozwala cechom na częściową implementację, tzn. jest możliwe podanie domyślnej implementacji dla niektórych metod. W przeciwieństwie do klas, cechy nie mogą posiadać parametrów konstruktora. + +Przykład definicji cechy której zadaniem jest określanie podobieństwa z innym obiektem: + +```tut +trait Similarity { + def isSimilar(x: Any): Boolean + def isNotSimilar(x: Any): Boolean = !isSimilar(x) +} +``` + +Powyższa cecha składa się z dwóch metod: `isSimilar` oraz `isNotSimilar`. Mimo że `isSimilar` nie posiada implementacji (odpowiada metodzie abstrakcyjnej w Javie), `isNotSimilar` definiuje konkretną implementację. W ten sposób klasy które łączą tą cechę, muszą tylko zdefiniować implementacją dla metody `isSimilar`. Zachowanie `isNotSimilar` jest dziedziczone bezpośrednio z tej cechy. Cechy są zazwyczaj łączone z [klasami](classes.html) lub innymi cechami poprzez [kompozycję domieszek](mixin-class-composition.html): + +```tut +class Point(xc: Int, yc: Int) extends Similarity { + var x: Int = xc + var y: Int = yc + def isSimilar(obj: Any) = + obj.isInstanceOf[Point] && + obj.asInstanceOf[Point].x == x +} + +object TraitsTest extends App { + val p1 = new Point(2, 3) + val p2 = new Point(2, 4) + val p3 = new Point(3, 3) + val p4 = new Point(2, 3) + println(p1.isSimilar(p2)) + println(p1.isSimilar(p3)) + // Metoda isNotSimilar jest zdefiniowana w Similarity + println(p1.isNotSimilar(2)) + println(p1.isNotSimilar(p4)) +} +``` + +Wynik działania programu: + +``` +true +false +true +false +``` diff --git a/pl/tutorials/tour/unified-types.md b/pl/tutorials/tour/unified-types.md new file mode 100644 index 0000000000..7b385047ab --- /dev/null +++ b/pl/tutorials/tour/unified-types.md @@ -0,0 +1,48 @@ +--- +layout: tutorial +title: Hierarchia typów + +disqus: true + +tutorial: scala-tour +num: 2 +language: pl +tutorial-next: classes +tutorial-previous: tour-of-scala +--- + +W przeciwieństwie do Javy, wszystkie wartości w Scali są obiektami (wliczając w to wartości numeryczne i funkcje). Ponieważ Scala bazuje na klasach, wszystkie wartości są instancjami klasy. Można zatem powiedzieć, że Scala posiada zunifikowany system typów. Poniższy diagram ilustruje hierarchię klas Scali: + +![Scala Type Hierarchy]({{ site.baseurl }}/resources/images/classhierarchy.img_assist_custom.png) + +## Hierarchia Klas Scali ## + +Klasa bazowa dla wszystkich klas `scala.Any` posiada dwie bezpośrednie klasy pochodne: `scala.AnyVal` oraz `scala.AnyRef`, które reprezentują dwie różne rodziny klas: klasy wartości oraz klasy referencji. Klasy wartości są predefiniowane i odpowiadają one typom prymitywnym z języków takich jak Java. Wszystkie inne klasy definiują typy referencyjne. Klasy zdefiniowane przez użytkownika są domyślnie typami referencyjnymi, tzn. są one zawsze podtypem klasy `scala.AnyRef`. W kontekście maszyny wirtualnej Javy, `scala.AnyRef` odpowiada typowi `java.lang.Object`. Powyższy diagram ilustruje także konwersje implicit pomiędzy klasami wartości. + +Poniższy przykład pokazuje, że liczby, znaki, wartości logiczne oraz funkcje są obiektami: + + +```tut +object UnifiedTypes extends App { + val fun: Int => Int = _ + 1 // deklaracja funkcji + val set = new scala.collection.mutable.LinkedHashSet[Any] + set += "To jest łańcuch znaków" // dodaj łańcuch znaków + set += 732 // dodaj liczbę + set += 'c' // dodaj znak + set += true // dodaj wartość logiczną + set += fun _ // dodaj funkcję + set foreach println +} +``` + +Program deklaruje aplikację `UnifiedTypes` w postaci [obiektu singleton](singleton-objects.html) rozszerzającego klasę `App`. Aplikacja definiuje zmienną lokalną `set` odwołującą się do instancji klasy `LinkedHashSet[Any]`, która reprezentuje zbiór obiektów dowolnego typu (`Any`). Ostatecznie program wypisuje wszystkie elementy tego zbioru. + +Wynik działania programu: + +``` +To jest łańcuch znaków +732 +c +true + +``` diff --git a/pl/tutorials/tour/upper-type-bounds.md b/pl/tutorials/tour/upper-type-bounds.md new file mode 100644 index 0000000000..8053bcde2a --- /dev/null +++ b/pl/tutorials/tour/upper-type-bounds.md @@ -0,0 +1,51 @@ +--- +layout: tutorial +title: Górne ograniczenia typów + +disqus: true + +tutorial: scala-tour +num: 19 +language: pl +tutorial-next: lower-type-bounds +tutorial-previous: variances +--- + +W Scali [parametry typów](generic-classes.html) oraz [typy abstrakcyjne](abstract-types.html) mogą być warunkowane przez ograniczenia typów. Tego rodzaju ograniczenia pomagają określić konkretne wartości zmiennych typu oraz odkryć więcej informacji na temat elementów tych typów. _Ograniczenie górne typu_ `T <: A` zakładają, że zmienna `T` jest podtypem typu `A`. + +Poniższy przykład demonstruje zastosowanie ograniczeń górnych typu dla parametru typu klasy `Cage`: + +```tut +abstract class Animal { + def name: String +} + +abstract class Pet extends Animal {} + +class Cat extends Pet { + override def name: String = "Cat" +} + +class Dog extends Pet { + override def name: String = "Dog" +} + +class Lion extends Animal { + override def name: String = "Lion" +} + +class Cage[P <: Pet](p: P) { + def pet: P = p +} + +object Main extends App { + var dogCage = new Cage[Dog](new Dog) + var catCage = new Cage[Cat](new Cat) + /* Nie można włożyć Lion do Cage, jako że Lion nie jest typu Pet. */ +// var lionCage = new Cage[Lion](new Lion) +} +``` + +Instancja klasy `Cage` może zawierać `Animal` z górnym ograniczeniem `Pet`. Obiekt typu `Lion` nie należy do klasy `Pet` zatem nie może być włożony do obiektu `Cage`. + +Zastosowanie dolnych ograniczeń typów jest opisane [tutaj](lower-type-bounds.html). diff --git a/pl/tutorials/tour/variances.md b/pl/tutorials/tour/variances.md new file mode 100644 index 0000000000..a8148c4bc2 --- /dev/null +++ b/pl/tutorials/tour/variances.md @@ -0,0 +1,41 @@ +--- +layout: tutorial +title: Wariancje + +disqus: true + +tutorial: scala-tour +num: 18 +language: pl +tutorial-next: upper-type-bounds +tutorial-previous: generic-classes +--- + +Scala wspiera adnotacje wariancji parametrów typów [klas generycznych](generic-classes.html). W porównaniu do Javy, adnotacje wariancji mogą zostać dodane podczas definiowania abstrakcji klasy, gdy w Javie adnotacje wariancji są podane przez użytkowników tych klas. + +Na stronie o [klasach generycznych](generic-classes.html) omówiliśmy przykład zmiennego stosu. Wyjaśniliśmy, że typ definiowany przez klasę `Stack[T]` jest poddany niezmiennemu podtypowaniu w stosunku do parametru typu. Może to ograniczyć możliwość ponownego wykorzystania abstrakcji tej klasy. Spróbujemy teraz opracować funkcyjną (tzn. niemutowalną) implementację dla stosów, które nie posiadają tego ograniczenia. Warto zwrócić uwagę na to, że jest to zaawansowany przykład łączący w sobie zastosowanie [funkcji polimorficznych](polymorphic-methods.html), [dolnych ograniczeń typu](lower-type-bounds.html) oraz kowariantnych adnotacji parametru typu. Dodatkowo stosujemy też [klasy wewnętrzne](inner-classes.html), aby połączyć ze sobą elementy stosu bez jawnych powiązań. + +```tut +class Stack[+A] { + def push[B >: A](elem: B): Stack[B] = new Stack[B] { + override def top: B = elem + override def pop: Stack[B] = Stack.this + override def toString() = elem.toString() + " " + + Stack.this.toString() + } + def top: A = sys.error("no element on stack") + def pop: Stack[A] = sys.error("no element on stack") + override def toString() = "" +} + +object VariancesTest extends App { + var s: Stack[Any] = new Stack().push("hello"); + s = s.push(new Object()) + s = s.push(7) + println(s) +} +``` + +Adnotacja `+T` określa typ `T` tak, aby mógł być zastosowany wyłącznie w pozycji kowariantnej. Podobnie `-T` deklaruje `T` w taki sposób, że może być użyty tylko w pozycji kontrawariantnej. Dla kowariantnych parametrów typu uzyskujemy kowariantną relację podtypowania w stosunku do tego parametru typu. W naszym przykładzie oznacza to, że `Stack[T]` jest podtypem `Stack[S]` jeżeli `T` jest podtypem `S`. Odwrotnie relacja jest zachowana dla parametrów typu określonych przez `-`. + +Dla przykładu ze stosem chcielibyśmy użyć kowariantnego parametru typu `T` w kontrawariantnej pozycji, aby móc zdefiniować metodę `push`. Ponieważ chcemy uzyskać kowariantne podtypowanie dla stosów, użyjemy sposobu polegającego na abstrahowaniu parametru typu w metodzie `push`. Wynikiem tego jest metoda polimorficzna, w której element typu `T` jest wykorzystany jako ograniczenie dolne zmiennej typu metody `push`. Dzięki temu wariancja parametru `T` staje się zsynchronizowana z jej deklaracją jako typ kowariantny. Teraz stos jest kowariantny, ale nasze rozwiązanie pozwala na przykładowo dodanie łańcucha znaków do stosu liczb całkowitych. Wynikiem takiej operacji będzie stos typu `Stack[Any]`, więc jeżeli ma on być zastosowany w kontekście, gdzie spodziewamy się stosu liczb całkowitych, zostanie wykryty błąd. W innym przypadku uzyskamy stos o bardziej uogólnionym typie elementów. diff --git a/pl/tutorials/tour/xml-processing.md b/pl/tutorials/tour/xml-processing.md new file mode 100644 index 0000000000..a817494214 --- /dev/null +++ b/pl/tutorials/tour/xml-processing.md @@ -0,0 +1,62 @@ +--- +layout: tutorial +title: Przetwarzanie XML + +disqus: true + +tutorial: scala-tour +num: 13 +language: pl +tutorial-next: regular-expression-patterns +tutorial-previous: singleton-objects +--- + +Scala pozwala na łatwe tworzenie, parsowanie oraz przetwarzanie dokumentów w formacie XML. Dane XML mogą być przedstawiane używając generycznych reprezentacji danych lub też z wykorzystaniem specyficznych powiązań za pomocą narzędzia [scalaxb](http://scalaxb.org/). + +Rozważmy następujący dokument XML: + +```html + + + Hello XHTML world + + +

Hello world

+

Scala talks XHTML

+ + +``` + +Ten dokument łatwo można stworzyć pisząc program w Scali: + +```tut +object XMLTest1 extends App { + val page = + + + Hello XHTML world + + +

Hello world

+

Scala talks XHTML

+ + ; + println(page.toString()) +} +``` + +Możliwe jest mieszanie wyrażeń Scali oraz XML: + +```tut +object XMLTest2 extends App { + import scala.xml._ + val df = java.text.DateFormat.getDateInstance() + val dateString = df.format(new java.util.Date()) + def theDate(name: String) = + + Hello, { name }! Today is { dateString } + ; + println(theDate("John Doe").toString()) +} +``` +