Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00405 007434 15355977 na godz. na dobę w sumie
Scala od podszewki - książka
Scala od podszewki - książka
Autor: Liczba stron: 304
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-5188-7 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> java - programowanie
Porównaj ceny (książka, ebook, audiobook).

Scala to słowo, które ostatnio nie schodzi z ust programistów Javy. Pod tą nazwą kryje się język łączący światy programowania funkcyjnego i obiektowego. Jego ogromną zaletą jest działanie w oparciu o wirtualną maszynę Javy. Pozwala to między innymi na bezproblemową komunikację i współdzielenie kodu między oboma językami. James Gosling, twórca Javy, zapytany o to, jakiego języka oprogramowania działającego w ten sposób użyłby obecnie (gdyby nie mógł wykorzystać Javy), odparł bez zastanowienia: 'Scala!'. To chyba najlepszy dowód na to, że ten język wart jest Twojego czasu!

Dzięki tej książce opanujesz Scalę szybko i bezboleśnie, więc będziesz mógł wykorzystać jej zalety już w najbliższym projekcie. W trakcie lektury poznasz składnię, fundamentalne zasady tworzenia oprogramowania w Scali oraz konwencje kodowania w tym języku. W kolejnych rozdziałach dowiesz się, czym są niejawne widoki, jakie typy danych masz do dyspozycji i jakie są ich ograniczenia. Co jeszcze? Integracja Scali z Javą to niezwykle istotny temat, dający Ci pole do popisu! Ponadto poznasz wzorce stosowane w programowaniu funkcyjnym. Słowo wstępne do tej niezwykłej książki napisał sam Martin Odersky - twórca języka Scala! Niniejsza książka jest najlepszym kompendium wiedzy na temat programowania w tym języku. Musisz ją mieć!

Poznaj:

Poznaj i wykorzystaj potęgę programowania funkcyjnego!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: Scala in Depth Tłumaczenie: Justyna Walkowska Projekt okładki: Anna Mitka Materiały graficzne na okładce zostały wykorzystane za zgodą Shutterstock Images LLC. ISBN: 978-83-246-5188-7 Original edition copyright 2012 by Manning Publications, Co. All rights reserved. Polish edition copyright 2013 by HELION SA. All rights reserved. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/scalao Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis treĂci Rozdziaï 1. Scala: jÚzyk mieszany 1.1.1. 1.1.2. 1.3.1. 1.3.2. 1.3.3. Sïowo wstÚpne ............................................................................................................................................ 7 Przedmowa ................................................................................................................................................. 9 PodziÚkowania .......................................................................................................................................... 11 O ksiÈĝce ................................................................................................................................................... 13 17 1.1. Programowanie funkcyjne i obiektowe w jednym .................................................................... 18 Koncepty funkcyjne ...................................................................................................... 20 Analiza konceptów funkcyjnych w Google Collections .............................................. 22 1.2. Statyczne typowanie a ekspresywnoĂÊ kodu ............................................................................. 23 1.2.1. Zamiana stron ................................................................................................................ 24 1.2.2. Wnioskowanie na temat typów ..................................................................................... 24 1.2.3. Uproszczona skïadnia .................................................................................................... 25 1.2.4. WartoĂci i konwersje domniemane .............................................................................. 26 Sïowo kluczowe implicit ............................................................................................... 27 1.2.5. 1.3. Wygodna wspóïpraca z JVM ....................................................................................................... 28 Java w Scali .................................................................................................................... 28 Scala w Javie .................................................................................................................. 29 Zalety JVM ..................................................................................................................... 30 1.4. Podsumowanie .............................................................................................................................. 31 33 2.1. Eksperymenty w Ărodowisku REPL ........................................................................................... 33 2.1.1. Programowanie sterowane eksperymentami ............................................................... 34 2.1.2. ObejĂcie zachïannego parsowania ................................................................................ 36 2.1.3. Elementy jÚzyka niedostÚpne w REPL ....................................................................... 37 2.2. MyĂlenie wyraĝeniami ................................................................................................................. 38 2.2.1. Unikanie instrukcji return ............................................................................................ 39 2.2.2. ModyfikowalnoĂÊ ........................................................................................................... 41 2.3. Obiekty niemodyfikowalne ......................................................................................................... 43 2.3.1. RównowaĝnoĂÊ obiektów .............................................................................................. 44 2.3.2. WspóïbieĝnoĂÊ ............................................................................................................... 48 2.4. None zamiast null ......................................................................................................................... 51 Zaawansowane techniki wykorzystania klasy Option ................................................. 52 2.5. RównowaĝnoĂÊ polimorficzna ..................................................................................................... 55 Przykïad: biblioteka obsïugujÈca kalendarz ................................................................. 55 Polimorficzna implementacja metody equals .............................................................. 57 2.6. Podsumowanie .............................................................................................................................. 59 Rozdziaï 2. Podstawowe zasady 2.4.1. 2.5.1. 2.5.2. 4 Spis treĂci Rozdziaï 3. ParÚ sïów na temat konwencji kodowania Rozdziaï 4. ObiektowoĂÊ Rozdziaï 6. System typów 3.1.1. 61 3.1. Unikanie konwencji pochodzÈcych z innych jÚzyków .............................................................. 62 Poraĝka z blokami kodu ................................................................................................ 63 3.2. WiszÈce operatory i wyraĝenia w nawiasach ............................................................................ 66 3.3. ZnaczÈce nazwy zmiennych ........................................................................................................ 67 3.3.1. Unikanie w nazwach znaku $ ....................................................................................... 68 3.3.2. Parametry nazwane i wartoĂci domyĂlne ..................................................................... 71 3.4. Oznaczanie przesïaniania metod ................................................................................................ 73 3.5. Adnotacje optymalizacyjne ......................................................................................................... 78 3.5.1. Optymalizacja tableswitch ............................................................................................ 79 3.5.2. Optymalizacja wywoïañ ogonowych ............................................................................ 81 3.6. Podsumowanie .............................................................................................................................. 84 85 4.1. W ciele obiektu lub cechy — tylko kod inicjalizujÈcy ............................................................. 86 4.1.1. Opóěniona inicjalizacja ................................................................................................. 86 4.1.2. Wielokrotne dziedziczenie ........................................................................................... 87 4.2. Puste implementacje metod abstrakcyjnych w cechach .......................................................... 89 4.3. Kompozycja moĝe obejmowaÊ dziedziczenie ............................................................................ 93 Kompozycja i dziedziczenie razem .............................................................................. 96 Klasyczne konstruktory… z niespodziankÈ ................................................................. 97 4.4. Wydzielenie interfejsu abstrakcyjnego do postaci osobnej cechy .......................................... 99 4.4.1. Interfejsy, z którymi moĝna porozmawiaÊ ................................................................. 101 4.4.2. Nauka pïynÈca z przeszïoĂci ....................................................................................... 102 4.5. OkreĂlanie typów zwracanych przez publiczne API .............................................................. 103 4.6. Podsumowanie ............................................................................................................................ 105 107 5.1. Sïowo kluczowe implicit ............................................................................................................ 108 Identyfikatory (dygresja) ............................................................................................. 109 5.1.1. Zakres i wiÈzania ......................................................................................................... 111 5.1.2. 5.1.3. Wyszukiwanie wartoĂci domniemanych .................................................................... 115 5.2. Wzmacnianie klas za pomocÈ domniemanych widoków ....................................................... 119 5.3. Parametry domniemane i domyĂlne ........................................................................................ 124 5.4. Ograniczanie zakresu encji domniemanych ........................................................................... 130 Przygotowywanie encji domniemanych do zaimportowania .................................... 131 Parametry i widoki domniemane bez podatku od importu ...................................... 133 5.5. Podsumowanie ............................................................................................................................ 137 139 6.1. Typy ............................................................................................................................................. 140 Typy i Ăcieĝki ............................................................................................................... 141 Sïowo kluczowe type ................................................................................................... 143 Typy strukturalne ........................................................................................................ 144 6.2. Ograniczenia typów ................................................................................................................... 151 6.3. Parametry typu i typy wyĝszego rzÚdu .................................................................................... 153 6.3.1. Ograniczenia parametrów typu .................................................................................. 153 6.3.2. Typy wyĝszego rzÚdu .................................................................................................. 155 6.1.1. 6.1.2. 6.1.3. 4.3.1. 4.3.2. 5.4.1. 5.4.2. Rozdziaï 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu Spis treĂci 5 6.4.1. 6.5.1. 7.1.1. Rozdziaï 8. Wybór odpowiedniej kolekcji Rozdziaï 7. ’Èczenie typów z wartoĂciami i widokami domniemanymi 7.3.1. 7.3.2. 6.4. Wariancja .................................................................................................................................... 156 Zaawansowane adnotacje wariancji ........................................................................... 160 6.5. Typy egzystencjalne ................................................................................................................... 163 Formalna skïadnia typów egzystencjalnych .............................................................. 165 6.6. Podsumowanie ............................................................................................................................ 167 169 7.1. Ograniczenia kontekstu i ograniczenia widoku ...................................................................... 170 Kiedy stosowaÊ domniemane ograniczenia typu? ..................................................... 171 7.2. Dodawanie typów do parametrów domniemanych ................................................................ 172 7.2.1. Manifesty ..................................................................................................................... 172 7.2.2. Korzystanie z manifestów ........................................................................................... 173 7.2.3. Ograniczenia typu ....................................................................................................... 175 7.2.4. Wyspecjalizowane metody ......................................................................................... 177 7.3. Klasy typu .................................................................................................................................... 178 FileLike jako klasa typu .............................................................................................. 181 Zalety klas typu ............................................................................................................ 184 7.4. Egzekucja warunkowa z uĝyciem systemu typów ................................................................... 185 7.4.1. Heterogeniczne listy typowane .................................................................................. 187 7.4.2. Cecha IndexedView .................................................................................................... 190 7.5. Podsumowanie ............................................................................................................................ 196 197 8.1. Wybór odpowiedniego rodzaju kolekcji .................................................................................. 198 8.1.1. Hierarchia kolekcji ...................................................................................................... 198 Traversable .................................................................................................................. 200 8.1.2. 8.1.3. Iterable ......................................................................................................................... 203 Seq ............................................................................................................................... 204 8.1.4. LinearSeq .................................................................................................................... 205 8.1.5. IndexedSeq .................................................................................................................. 207 8.1.6. 8.1.7. Set ................................................................................................................................ 208 8.1.8. Map .............................................................................................................................. 208 8.2. Kolekcje niemodyfikowalne ...................................................................................................... 210 Vector ........................................................................................................................... 210 List ............................................................................................................................... 212 Stream .......................................................................................................................... 213 8.3. Kolekcje modyfikowalne ........................................................................................................... 216 8.3.1. ArrayBuffer .................................................................................................................. 217 8.3.2. Nasïuchiwanie zdarzeñ zmiany kolekcji za pomocÈ domieszek ............................... 217 8.3.3. Synchronizacja z uĝyciem domieszek ........................................................................ 218 8.4. Zmiana czasu ewaluacji za pomocÈ widoków i kolekcji równolegïych ................................. 218 8.4.1. Widoki .......................................................................................................................... 219 8.4.2. Kolekcje równolegïe ................................................................................................... 221 8.5. Pisanie metod, które moĝna wykorzystaÊ na wszystkich typach kolekcji ............................ 223 8.5.1. Optymalizacja algorytmów dla róĝnych typów kolekcji ............................................ 226 8.6. Podsumowanie ............................................................................................................................ 229 8.2.1. 8.2.2. 8.2.3. 6 Spis treĂci Rozdziaï 9. Aktorzy Rozdziaï 11. Wzorce w programowaniu funkcyjnym Skorowidz Rozdziaï 10. Integracja Scali z JavÈ 9.1.1. 9.4.1. 9.2.1. 231 9.1. Kiedy stosowaÊ aktorów? ........................................................................................................... 232 Zastosowanie aktorów do wyszukiwania .................................................................... 232 9.2. Typowane, przezroczyste referencje ....................................................................................... 235 Realizacja algorytmu rozprosz-zgromadě przy uĝyciu OutputChannel ................... 236 9.3. Ograniczanie bïÚdów do stref ................................................................................................... 240 9.3.1. Strefy bïÚdu w przykïadzie rozprosz-zgromadě ........................................................ 240 9.3.2. Ogólne zasady obsïugi awarii ..................................................................................... 243 9.4. Ograniczanie przeciÈĝeñ za pomocÈ stref planowania .......................................................... 244 Strefy planowania ........................................................................................................ 245 9.5. Dynamiczna topologia aktorów ................................................................................................ 247 9.6. Podsumowanie ............................................................................................................................ 251 253 10.1. Róĝnice jÚzykowe pomiÚdzy ScalÈ a JavÈ ................................................................................ 254 10.1.1. Róĝnice w opakowywaniu typów prostych ................................................................ 255 10.1.2. WidocznoĂÊ .................................................................................................................. 259 10.1.3. Nieprzekïadalne elementy jÚzyka .............................................................................. 260 10.2. Uwaga na domniemane konwersje ........................................................................................... 263 10.2.1. ToĝsamoĂÊ i równowaĝnoĂÊ obiektów ........................................................................ 263 10.2.2. ’añcuchy domniemanych widoków ........................................................................... 265 10.3. Uwaga na serializacjÚ w Javie ................................................................................................... 267 10.3.1. Serializacja klas anonimowych ................................................................................... 269 10.4. Adnotowanie adnotacji .............................................................................................................. 271 10.4.1. Cele adnotacji .............................................................................................................. 272 10.4.2. Scala i pola statyczne ................................................................................................... 273 10.5. Podsumowanie ............................................................................................................................ 274 277 11.1. Teoria kategorii w informatyce ................................................................................................ 278 11.2. Funktory i monady oraz ich zwiÈzek z kategoriami ............................................................... 281 11.2.1. Monady ........................................................................................................................ 284 11.3. Rozwijanie funkcji i styl aplikacyjny ........................................................................................ 286 11.3.1. Rozwijanie funkcji ....................................................................................................... 286 11.3.2. Styl aplikacyjny ........................................................................................................... 288 11.4. Monady jako przepïywy pracy .................................................................................................. 291 11.5. Podsumowanie ............................................................................................................................ 295 297 Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu W tym rozdziale: Q wprowadzenie do domniemanych parametrów i widoków, Q mechanizm odnajdywania wartoĞci domniemanych, Q wykorzystanie domniemanych konwersji do rozszerzania klas, Q ograniczanie zakresu. System domniemanych parametrów i konwersji w Scali pozwala kompilatorowi na prze- twarzanie kodu na bazie dobrze zdefiniowanego mechanizmu wyszukiwania. Programista moĝe pominÈÊ czÚĂÊ informacji, a kompilator spróbuje wywnioskowaÊ je w czasie kom- pilacji. Takie wnioskowanie jest moĝliwe w jednej z dwóch sytuacji: Q przy wywoïaniu metody lub konstruktora bez podania któregoĂ z parametrów, Q przy domniemanej konwersji pomiÚdzy typami (domniemanym widoku) — dotyczy to takĝe obiektu, na którym jest wywoïywana metoda. W obu przypadkach kompilator stosuje zestaw reguï w celu pozyskania brakujÈcych in- formacji, by moĝliwa byïa kompilacja kodu. MoĝliwoĂÊ pominiÚcia parametrów jest niesamowicie przydatna. CzÚsto wykorzystujÈ jÈ biblioteki Scali. Bardziej kontrowersyjne, 108 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu lub wrÚcz niebezpieczne, jest zmienianie typu przez kompilator w celu umoĝliwienia poprawnej kompilacji. System domniemañ to jeden z najwiÚkszych atutów Scali. RozsÈdnie stosowany moĝe radykalnie zmniejszyÊ rozmiar Twojego kodu. Moĝe takĝe zostaÊ wykorzystany do ele- ganckiego wymuszenia ograniczeñ projektowych. Spójrzmy najpierw na domniemane parametry. 5.1. Sáowo kluczowe implicit Scala udostÚpnia sïowo kluczowe implicit (z ang. domniemany, niejawny), które moĝna stosowaÊ na dwa sposoby: podczas definiowania metod lub zmiennych albo na liĂcie pa- rametrów metody. Uĝyte w definicji metody lub zmiennej sïowo to informuje kompilator o tym, ĝe dana metoda lub zmienna moĝe zostaÊ wykorzystana podczas wnioskowania na temat domniemañ. Wyszukiwanie wartoĂci domniemanych jest przeprowadzane, gdy kom- pilator zauwaĝa, ĝe w kodzie brakuje pewnej informacji. JeĂli sïowo implicit zostanie uĝyte na poczÈtku listy parametrów pewnej metody, kompilator przyjmie, ĝe ta lista moĝe nie zo- staÊ podana i ĝe konieczne moĝe siÚ okazaÊ odgadniÚcie parametrów na podstawie reguï. Przeanalizujmy mechanizm wnioskowania na przykïadzie metody z brakujÈcÈ listÈ parametrów: scala def findAnInt(implicit x : Int) = x findAnInt: (implicit x: Int)Int Metoda findAnInt (znajdě liczbÚ caïkowitÈ) deklaruje jeden parametr x typu Int. Zwróci ona bez zmian kaĝdÈ przekazanÈ do niej wartoĂÊ. Lista parametrów zostaïa oznaczona sïowem implicit, co oznacza, ĝe nie jest konieczne jej podawanie. JeĂli opuĂcimy listÚ parametrów, kompilator poszuka zmiennej typu Int w zakresie domniemanym. Oto przykïad wywoïania tej metody: scala findAnInt console :7: error: could not find implicit value for parameter x: Int findAnInt ^ Metoda findAnInt zostaïa wywoïana bez listy parametrów. Kompilator skarĝy siÚ, ĝe nie jest w stanie odnaleěÊ wartoĂci domniemanej parametru x. Dostarczmy mu takÈ wartoĂÊ: scala implicit val test = 5 test: Int = 5 WartoĂÊ test zostaïa zdefiniowana z uĝyciem sïowa implicit. Oznacza to, ĝe moĝe ona zostaÊ uwzglÚdniona we wnioskowaniu na temat domniemañ. Skoro dziaïamy w Ăro- dowisku REPL, zmienna test bÚdzie dostÚpna aĝ do koñca naszej sesji. Oto co siÚ wy- darzy, gdy wywoïamy findAnInt: scala findAnInt res3: Int = 5 Tym razem wywoïanie siÚ powiedzie — metoda zwróci wartoĂÊ zmiennej test. Kompila- torowi udaïo siÚ odnaleěÊ brakujÈce fragmenty ukïadanki. OczywiĂcie jeĂli chcemy, mo- ĝemy wywoïaÊ funkcjÚ, przekazujÈc jej parametr: scala findAnInt(2) res4: Int = 2 5.1. Sïowo kluczowe implicit 109 Poniewaĝ tym razem nie brakuje parametru, kompilator nie rozpoczyna procesu wyszu- kiwania wartoĂci na podstawie reguï. ZapamiÚtaj, ĝe domniemane parametry zawsze moĝna jawnie podaÊ. Wrócimy do tego w podrozdziale 5.6. W celu zrozumienia sposobu, w jaki kompilator okreĂla, czy zmienna moĝe zostaÊ uwzglÚdniona w procesie wyszukiwania wartoĂci domniemanych, trzeba wgryěÊ siÚ trochÚ w to, jak sÈ obsïugiwane identyfikatory i zakresy. 5.1.1. Identyfikatory (dygresja) Zanim zagïÚbimy siÚ w szczegóïy mechanizmu wnioskowania, warto zrozumieÊ, w jaki sposób kompilator rozpoznaje identyfikatory w danym zakresie. Ta sekcja jest oparta na rozdziale 2. specyfikacji jÚzyka Scala1. ZachÚcam CiÚ do przeczytania specyfikacji, gdy juĝ zapoznasz siÚ z podstawami. Identyfikatory odgrywajÈ kluczowÈ rolÚ podczas wybie- rania zmiennych domniemanych, dlatego poĂwiÚÊmy im nieco uwagi. W specyfikacji pojawia siÚ sïowo entity (z ang. encja, byt), obejmujÈce znaczeniem: typy, wartoĂci, zmienne oraz klasy. SÈ to podstawowe elementy uĝywane do budowania programów. Odwoïujemy siÚ do nich za pomocÈ identyfikatorów czy teĝ nazw. Mówimy wówczas o wiÈzaniu (ang. binding) pomiÚdzy identyfikatorem a danÈ encjÈ. Rozwaĝ na- stÚpujÈcy fragment kodu: class Foo { def val x = 5 } Sama encja Foo to klasa zawierajÈca metodÚ x. PowiÈzaliĂmy jÈ z identyfikatorem Foo. JeĂli zadeklarujemy tÚ klasÚ lokalnie wewnÈtrz REPL, bÚdziemy mogli utworzyÊ jej in- stancjÚ, poniewaĝ nazwa i encja zostaïy lokalnie powiÈzane: scala val y = new Foo y: Foo = Foo@33262bf4 Moĝemy utworzyÊ nowÈ zmiennÈ o nazwie y i typie Foo, odwoïujÈc siÚ do nazwy Foo. PowtórzÚ: jest tak dlatego, ĝe klasa Foo zostaïa zdefiniowana lokalnie wewnÈtrz REPL i lokalnie powiÈzano jÈ z nazwÈ Foo. Skomplikujmy nieco sprawy, umieszczajÈc Foo we- wnÈtrz pakietu. package test; class Foo { val x = 5 } Klasa Foo naleĝy teraz do pakietu test. JeĂli spróbujemy odwoïaÊ siÚ do niej w REPL za pomocÈ nazwy Foo, poniesiemy klÚskÚ: scala new Foo console :7: error: not found: type Foo new Foo Utworzenie nowej instancji Foo nie powiodïo siÚ, poniewaĝ w naszym zakresie nazwa Foo nie zostaïa powiÈzana z ĝadnÈ encjÈ. Klasa Foo znajduje siÚ w pakiecie test. Aby siÚ do 1 http://www.scala-lang.org/docu/files/ScalaReference.pdf. 110 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu niej odwoïaÊ, musimy albo skorzystaÊ z nazwy test.Foo, albo powiÈzaÊ nazwÚ Foo z klasÈ test.Foo w aktualnym zakresie. Druga z wspomnianych opcji jest dostÚpna dziÚki sïowu kluczowemu import: scala import test.Foo import test.Foo scala new Foo res3: test.Foo = test.Foo@60e1e567 Instrukcja import pobiera encjÚ test.Foo i wiÈĝe jÈ z nazwÈ Foo w zakresie lokalnym. DziÚki temu moĝliwe staje siÚ utworzenie instancji test.Foo za pomocÈ wywoïania new Foo. Podobnie dziaïa instrukcja import w Javie i using w C++. Mechanizm dostÚpny w Scali jest jednak nieco bardziej elastyczny. InstrukcjÚ import moĝna zastosowaÊ w dowolnym miejscu pliku ěródïowego, gdzie stworzy ona wiÈzanie jedynie w zakresie lokalnym. DziÚki temu mamy kontrolÚ nad tym, gdzie sÈ uĝywane zaimportowane nazwy. Ta funkcjonalnoĂÊ pozwala dodatkowo na ogra- niczenie zakresu domniemanych widoków i zmiennych. WiÚcej na ten temat znajdziesz w podrozdziale 5.4. Jednym z przejawów elastycznoĂci mechanizmu wiÈzania encji w Scali jest moĝliwoĂÊ wykorzystania arbitralnych nazw. W Javie czy w C# jest moĝliwe jedynie przeniesienie do bieĝÈcego zakresu nazwy zdefiniowanej w innym zakresie bÈdě pakiecie. KlasÚ test.Foo moglibyĂmy zaimportowaÊ lokalnie jako Foo. Natomiast instrukcja import w Scali pozwala na zdefiniowanie nowej nazwy przy uĝyciu skïadni {OryginalneWiæzanie = NoweWiæzanie}. Zaimportujmy naszÈ encjÚ test.Foo, nadajÈc jej nowÈ nazwÚ: scala import test.{Foo= Bar} import test.{Foo= Bar} scala new Bar res1: test.Foo = test.Foo@596b753 Pierwsza instrukcja import wiÈĝe klasÚ test.Foo z nazwÈ Bar w bieĝÈcym zakresie. W nastÚpnej linii tworzymy nowÈ instancjÚ test.Foo, wywoïujÈc new Bar. Mechanizm ten pozwala na unikniÚcie konfliktów nazw podczas importowania encji z róĝnych pa- kietów. Dobrym przykïadem sÈ java.util.List i scala.List. W celu unikniÚcia nie- porozumieñ w kodzie wspóïpracujÈcym z kodem Javy czÚsto stosuje siÚ konstrukcjÚ im- port java.util.{List= JList}. Zmiana nazwy pakietu InstrukcjĊ import w Scali moĪna zastosowaü takĪe w celu zmiany nazwy pakietu. Przydaje siĊ to podczas korzystania z bibliotek Javy. Sam, gdy korzystam z pakietu java.io, czĊsto zaczynam od nastĊpującego kodu: import java.{io= jio} def someMethod( input : jio.InputStream ) = ... WiÈzanie pozwala na nadanie encji okreĂlonej nazwy w konkretnym zakresie. Istotne jest tutaj zrozumienie, czym jest zakres i jakie wiÈzania moĝna w nim znaleěÊ. 5.1. Sïowo kluczowe implicit 111 5.1.2. Zakres i wiązania Zakres to leksykalna granica, wewnÈtrz której sÈ dostÚpne wiÈzania. Zakresem moĝe byÊ ciaïo klasy, ciaïo metody, anonimowy blok kodu… Zasadniczo za kaĝdym razem, gdy uĝy- wasz nawiasów klamrowych, tworzysz w ich wnÚtrzu nowy zakres. W Scali jest moĝliwe zagnieĝdĝanie zakresów — jeden zakres moĝe wystÈpiÊ we- wnÈtrz drugiego. W zagnieĝdĝonym zakresie sÈ dostÚpne wiÈzania z zakresu szerszego. Moĝliwa jest zatem nastÚpujÈca operacja: class Foo(x : Int) { def tmp = { x } } Konstruktor klasy Foo pobiera parametr x. NastÚpnie definiujemy zagnieĝdĝonÈ metodÚ tmp. Parametry konstruktora sÈ dostÚpne z jej wnÚtrza — moĝemy odwoïaÊ siÚ do identyfikatora x. Zakres zagnieĝdĝony ma dostÚp do wiÈzañ w zakresie-rodzicu, ale moĝliwe jest teĝ tworzenie wiÈzañ, które je przesïoniÈ. Metoda tmp moĝe utworzyÊ nowe wiÈzanie o nazwie x. Wówczas x nie bÚdzie juĝ identyfikatorem prowadzÈcym do para- metru rodzica. Zobaczmy: scala class Foo(x : Int) { | def tmp = { | val x = 2 | x | } | } defined class Foo Klasa Foo ma definicjÚ takÈ samÈ jak wczeĂniej, jednak metoda tmp w zakresie zagnieĝ- dĝonym definiuje zmiennÈ o nazwie x. Nowe wiÈzanie przesïania parametr konstruktora x. W wyniku tego lokalnie widoczne jest tylko nowe wiÈzanie, a parametr konstruktora jest niedostÚpny — a przynajmniej nie przy uĝyciu nazwy x. W Scali wiÈzania o wyĝ- szym priorytecie przesïaniajÈ te o niĝszym w tym samym zakresie. Ponadto wiÈzania o wyĝszym lub tym samym priorytecie przesïaniajÈ wiÈzania zdefiniowane w zakresie zewnÚtrznym. Priorytety wiÈzañ w Scali sÈ nastÚpujÈce: 1. Najwyĝszy priorytet majÈ definicje lub deklaracje lokalne, odziedziczone lub udostÚpnione poprzez klauzulÚ pakietowÈ w tym samym pliku, w którym pojawia siÚ definicja. 2. NastÚpne w kolejnoĂci sÈ encje jawnie zaimportowane. 3. Dalej mamy encje zaimportowane z uĝyciem symboli wieloznacznych (import foo._). 4. Najniĝszy priorytet majÈ definicje udostÚpniane poprzez klauzulÚ pakietowÈ znajdujÈcÈ siÚ poza plikiem, w którym pojawia siÚ definicja. 112 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu Przesáanianie wiązaĔ W Scali wiązanie przesáania wiązania o niĪszym priorytecie w tym samym zakresie. Ponadto wiązanie przesáania wiązania o tym samym lub niĪszym priorytecie z zakresu zewnĊtrznego. DziĊki temu moĪemy napisaü: class Foo(x : Int) { def tmp = { val x = 2 x } } Metoda tmp bĊdzie zwracaáa wartoĞü 2. Sprawděmy priorytety na konkretnym przykïadzie. Zacznijmy od zdefiniowania pakietu test i obiektu x wewnÈtrz pliku ěródïowego, który nazwiemy externalbin- dings.scala (listing 5.1). Listing 5.1. Plik externalbindings.scala z wiązaniami zewnĊtrznymi package test; object x { override def toString = Zewnútrznie powiæzany obiekt x w pakiecie test } Plik definiuje pakiet test oraz zawarty w nim obiekt x. Obiekt x przesïania metodÚ toString, dziÚki czemu ïatwo go rozpoznaÊ. Zgodnie z przedstawionymi wczeĂniej re- guïami obiekt x powinien mieÊ najniĝszy moĝliwy priorytet wiÈzania. Stwórzmy teraz plik, który to sprawdzi (listing 5.2). Listing 5.2. Test wiązania w tym samym pakiecie package test; object Test { def main(args : Array[String]) : Unit = { testSamePackage() // Ten sam pakiet testWildcardImport() // Import poprzez symbol wieloznaczny testExplicitImport() // Jawny import testInlineDefinition() // Definicja w miejscu[JW1] } ... } Zaczynamy od deklaracji, zgodnie z którÈ treĂÊ pliku przynaleĝy do tego samego pakietu co nasza wczeĂniejsza definicja. NastÚpnie definiujemy metodÚ main wywoïujÈcÈ czte- ry metody testowe, po jednej dla kaĝdej reguïy okreĂlajÈcej priorytety wiÈzañ. Zacznij- my od zdefiniowania pierwszej z metod: def testSamePackage() { println(x) } 5.1. Sïowo kluczowe implicit 113 Metoda wypisuje encjÚ o nazwie x. Poniewaĝ obiekt Test zostaï zdefiniowany we- wnÈtrz pakietu test, stworzony wczeĂniej obiekt x jest dostÚpny i to on zostanie przeka- zany metodzie println. Oto dowód: scala test.Test.testSamePackage() Zewnútrznie powiæzany obiekt x w pakiecie test Wywoïanie metody testSamePackage generuje ïañcuch znaków zwiÈzany z obiektem x. Spójrzmy teraz, co siÚ zmieni, gdy do zaimportowania obiektu wykorzystamy symbol wieloznaczny (listing 5.3). Listing 5.3. Import z uĪyciem symbolu wieloznacznego object Wildcard { def x = Import x poprzez symbol wieloznaczny } def testWildcardImport() { import Wildcard._ println(x) } Obiekt Wildcard przechowuje encjÚ x. Encja x to metoda, która zwraca ïañcuch znaków Import x poprzez symbol wieloznaczny . Metoda testWildcardImport najpierw wy- woïuje import Wildcard._. DziÚki temu wywoïaniu nazwy i encje z obiektu Wildcard zo- stanÈ powiÈzane w bieĝÈcym zakresie. Poniewaĝ importowanie za pomocÈ symbolu wieloznacznego ma wyĝszy priorytet niĝ zasoby dostÚpne w tym samym pakiecie, ale innym pliku ěródïowym, encja Wildcard.x zostanie uĝyta zamiast test.x. Moĝemy to sprawdziÊ, wywoïujÈc funkcjÚ testWildcardImport: scala test.Test.testWildcardImport() Wildcard Import x Wywoïanie metody testWildcardImport powoduje wyĂwietlenie napisu Import x poprzez symbol wieloznaczny — wïaĂnie tego spodziewaliĂmy siÚ, znajÈc priorytety wiÈzañ. Sprawy stanÈ siÚ bardziej interesujÈce, gdy do przykïadu dorzucimy jeszcze jawne importowanie elementów (listing 5.4). Listing 5.4. Jawny import object Explicit { def x = Jawny import x } def testExplicitImport() { import Explicit.x import Wildcard._ println(x) } Obiekt Explicit stanowi przestrzeñ nazw dla kolejnej encji x. Metoda testExplicitImport najpierw importuje tÚ encjÚ bezpoĂrednio, a potem importuje jeszcze zawartoĂÊ encji obiektu Wildcard, korzystajÈc z symbolu wieloznacznego. Chociaĝ import z uĝyciem symbolu wieloznacznego jest drugi w kolejnoĂci, dziaïajÈ tu reguïy okreĂlajÈce priorytety wiÈzañ. Metoda zwróci wartoĂÊ x z obiektu Explicit. Sprawděmy: 114 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu scala test.Test.testExplicitImport() Jawny import x Zgodnie z oczekiwaniami zwrócona zostaïa wartoĂÊ Explicit.x. Widoczna tu reguïa okreĂlajÈca priorytety wiÈzañ ma duĝe znaczenie w kontekĂcie wyszukiwania wartoĂci domniemanych, do którego przejdziemy w sekcji 5.1.3. Ostatnia reguïa dotyczy deklaracji lokalnych. Zmieñmy metodÚ testExplicitImport tak, by definiowaïa lokalne wiÈzanie dla nazwy x (listing 5.5). Listing 5.5. Definicja lokalna def testInlineDefinition() { val x = Lokalna definicja x import Explicit.x import Wildcard._ println(x) } Pierwsza linia metody testInlineDefinition to deklaracja zmiennej lokalnej x. NastÚpnie linie w sposób jawny bÈdě domniemany (z wykorzystaniem symbolu wieloznacznego) importujÈ wiÈzania x z obiektów Explicit i Wildcard, pokazanych wczeĂniej. W ostatniej linii drukujemy wynik, by przekonaÊ siÚ, które wiÈzanie zwyciÚĝyïo. scala test.Test.testInlineDefinition() Lokalna definicja x Ponownie, mimo ĝe instrukcje import pojawiajÈ siÚ po instrukcji val x, wybór jest oparty na priorytecie, a nie na kolejnoĂci deklaracji. Wiązania nieprzesáaniane Jest moĪliwe stworzenie w tym samym zakresie dwóch wiązaĔ o tej samej nazwie. W takim wypadku kompilator ostrzeĪe o dwuznacznoĞci nazw. Oto przykáad zapoĪyczony bezpoĞrednio ze specyfikacji jĊzyka Scala: scala { | val x = 1; | { | import test.x; | x | } | } console :11: error: reference to x is ambiguous; it is both defined in value res7 and imported subsequently by import test.x x ^ Zmienna x jest tu wiązana w zakresie zewnĊtrznym. Jest ona takĪe importowana z pa- kietu test w zakresie zagnieĪdĪonym. ĩadne z wiązaĔ nie przesáania drugiego. Zmienna x z zakresu zewnĊtrznego nie moĪe przesáoniü zmiennej w zakresie zagnieĪdĪonym, a zaimportowana zmienna x równieĪ nie ma odpowiedniego priorytetu, by przesáoniü tĊ w zakresie zewnĊtrznym. 5.1. Sïowo kluczowe implicit 115 SkÈd taki nacisk na sposób rozwikïywania nazw przez kompilator? Otóĝ wnioskowanie na temat domniemañ jest ĂciĂle powiÈzane z wnioskowaniem na temat nazw. Zawiïe reguïy okreĂlajÈce priorytety nazw majÈ znaczenie takĝe w przypadku domniemañ. Przyjrzyjmy siÚ teraz, jak postÚpuje kompilator, napotykajÈc niepeïnÈ deklaracjÚ. 5.1.3. Wyszukiwanie wartoĞci domniemanych Specyfikacja jÚzyka Scala deklaruje dwie reguïy zwiÈzane z wyszukiwaniem encji ozna- czonych jako domniemane: Q WiÈzanie encji domniemanej jest dostÚpne na stronie wyszukiwania bez Q prefiksu — to znaczy nie jako foo.x, tylko jako x. JeĂli pierwsza reguïa nie prowadzi do rozwiÈzania problemu, to wszystkie skïadowe obiektu oznaczone jako implicit naleĝÈ do domniemanego zakresu zwiÈzanego z typem parametru domniemanego. Pierwsza reguïa ĂciĂle ïÈczy siÚ z reguïami wiÈzania przedstawionymi w poprzedniej sekcji. Druga jest nieco bardziej zïoĝona. Przyjrzymy siÚ jej dokïadnie w sekcji 5.1.4. Na poczÈtek wróÊmy do przedstawionego juĝ wczeĂniej przykïadu wyszukiwania wartoĂci domniemanych: scala def findAnInt(implicit x : Int) = x findAnInt: (implicit x: Int)Int scala implicit val test = 5 test: Int = 5 Metoda findAnInt zostaïa zadeklarowana z oznaczonÈ jako implicit listÈ parametrów skïadajÈcÈ siÚ z jednej wartoĂci typu caïkowitego. NastÚpnie definiujemy wartoĂÊ val test, takĝe oznaczonÈ jako implicit. DziÚki temu identyfikator, test, jest dostÚpny w za- kresie lokalnym bez prefiksu. JeĂli w REPL wpiszemy test, otrzymamy wartoĂÊ 5. JeĂli wywoïamy metodÚ, piszÈc findAnInt, kompilator przepisze jÈ jako findAnInt(test). Podczas wyszukiwania sÈ wykorzystywane reguïy wiÈzania, które zostaïy przeze mnie opisane wczeĂniej. Druga reguïa domniemanego wyszukiwania jest uĝywana, gdy kompilator nie moĝe znaleěÊ ĝadnej wartoĂci domniemanej, stosujÈc pierwszÈ z reguï. W takim wypadku kom- pilator spróbuje odnaleěÊ domniemane zmienne zdefiniowane wewnÈtrz dowolnego obiektu w domniemanym zakresie typu, którego szuka. Domniemany zakres typu defi- niuje siÚ jako wszystkie moduïy towarzyszÈce powiÈzane z danym typem. Oznacza to, ĝe jeĂli kompilator szuka parametru metody def foo (implicit param : Foo), to parametr musi byÊ zgodny z typem Foo. JeĂli pierwsza reguïa nie zwróci wartoĂci typu Foo, to kompilator sprawdzi domniemany zakres Foo. Domniemany zakres Foo to obiekt towa- rzyszÈcy Foo. Przeanalizuj kod na listingu 5.6. Listing 5.6. Obiekt towarzyszący a wyszukiwanie zmiennych domniemanych scala object holder { | trait Foo | object Foo { | implicit val x = new Foo { 116 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu | override def toString = Obiekt towarzyszæcy Foo | } | } | } defined module holder scala import holder.Foo import holder.Foo scala def method(implicit foo : Foo) = println(foo) method: (implicit foo: holder.Foo)Unit scala method Obiekt towarzyszæcy Foo Obiekt holder jest nam potrzebny do zdefiniowania cechy i obiektu towarzyszÈcego wewnÈtrz sesji REPL, podobnie jak robiliĂmy to w sekcji 2.1.2. WewnÈtrz niego defi- niujemy cechÚ Foo oraz obiekt towarzyszÈcy Foo. Obiekt towarzyszÈcy definiuje skïa- dowÈ x typu Foo, udostÚpnianÈ na potrzeby wnioskowania na temat domniemañ. Na- stÚpnie importujemy typ Foo z obiektu holder do zakresu bieĝÈcego. Ten krok nie jest wymagany, wykonujemy go w celu uproszczenia definicji metody. NastÚpnie definiu- jemy metodÚ method. Pobiera ona domniemany parametr typu Foo. JeĂli wywoïamy metodÚ z pustÈ listÈ argumentów, kompilator uĝyje zdefiniowanej w obiekcie towarzyszÈcym zmiennej implicit val x. Jako ĝe zakres domniemany jest sprawdzany w drugiej kolejnoĂci, moĝemy wykorzy- staÊ go do przechowywania wartoĂci domyĂlnych, a jednoczeĂnie umoĝliwiÊ uĝytkowni- kowi importowanie wïasnych wartoĂci, jeĂli jest mu to potrzebne. PoĂwiÚcimy temu za- gadnieniu wiÚcej uwagi w podrozdziale 7.2. Jak wspominaïem wczeĂniej, zakresem domniemanym dla typu T jest zbiór obiektów towarzyszÈcych dla wszystkich typów powiÈzanych z typem T — czyli istnieje zbiór ty- pów powiÈzanych z T. Wszystkie obiekty towarzyszÈce tym typom sÈ przeszukiwane pod- czas wnioskowania na temat domniemañ. Wedïug specyfikacji jÚzyka typ powiÈzany z klasÈ T to kaĝda klasa bÚdÈca klasÈ bazowÈ pewnej czÚĂci typu T. Poniĝsza lista przed- stawia istniejÈce czÚĂci typu T. Q Q Q Wszystkie podtypy T sÈ czÚĂciami T. JeĂli typ T zostaï zdefiniowany jako A with B with C, to A, B i C wszystkie sÈ czÚĂciami T, zatem ich obiekty towarzyszÈce zostanÈ przeszukane, gdy konieczne bÚdzie znalezienie domniemanej wartoĂci typu T. JeĂli T ma parametry, to wszystkie parametry typu i ich czÚĂci naleĝÈ do zbioru czÚĂci T. Przykïadowo wyszukiwanie domniemanej wartoĂci dla typu List[String] sprawdzi obiekt towarzyszÈcy List i obiekt towarzyszÈcy String. JeĂli T jest typem singletonowym p.type, to czÚĂci typu p naleĝÈ równieĝ do zbioru czÚĂci typu T. Oznacza to, ĝe jeĂli typ T zostaï zdefiniowany wewnÈtrz obiektu, to sam ten obiekt zostanie przeszukany pod kÈtem wartoĂci domniemanych. WiÚcej na temat typów singletonowych znajdziesz w sekcji 6.1.1. JeĂli T jest projekcjÈ typu S#T, to czÚĂci S sÈ takĝe czÚĂciami T. Oznacza to, ĝe jeĂli typ T zostaï zdefiniowany wewnÈtrz klasy lub cechy, to obiekty towarzyszÈce tej klasie lub cesze zostanÈ przeszukane pod kÈtem wartoĂci domniemanych. WiÚcej na temat projekcji typów znajdziesz w sekcji 6.1.1. Q 5.1. Sïowo kluczowe implicit 117 Zakres domniemany typu obejmuje wiele róĝnych lokalizacji i zapewnia duĝÈ elastycz- noĂÊ, jeĂli chodzi o dostarczanie wartoĂci domniemanych. Przeanalizujmy teraz co ciekawsze aspekty zakresu domniemanego. ZAKRES DOMNIEMANY POPRZEZ PARAMETRY TYPU Zgodnie ze specyfikacjÈ jÚzyka Scala zakres domniemany typu obejmuje wszystkie obiekty towarzyszÈce wszystkich typów i podtypów zawartych w parametrach typu. Oznacza to na przykïad, ĝe moĝemy zdefiniowaÊ wartoĂÊ domniemanÈ dla List[Foo], podajÈc jÈ w obiekcie towarzyszÈcym Foo. Oto przykïad: scala object holder { | trait Foo | object Foo { | implicit val list = List(new Foo{}) | } | } defined module holder scala implicitly[List[holder.Foo]] res0: List[holder.Foo] = List(holder$Foo$$anon$1@2ed4a1d3) Obiekt holder sïuĝy nam, tradycyjnie, do stworzenia obiektów stowarzyszonych we- wnÈtrz REPL. Zawiera on cechÚ Foo i jej obiekt towarzyszÈcy. Obiekt towarzyszÈcy za- wiera definicjÚ List[Foo] oznaczonÈ sïowem implicit. W nastÚpnej linii wywoïujemy funkcjÚ Scali o nazwie implicitly. Pozwoli ona na wyszukanie typu w aktualnym zakresie domniemanym. Definicja tej funkcji to def implicitly[T](implicit arg : T) = arg. Pa- rametr typu T pozwala nam wykorzystaÊ jÈ niezaleĝnie od tego, jakiego typu encji szukamy. WiÚcej o parametrach typów powiem w podrozdziale 6.2. Wywoïanie implicitly na typie List[holder.Foo] zwróci listÚ zdefiniowanÈ w obiekcie towarzyszÈcym Foo. Mechanizm ten sïuĝy do implementacji cech typów, nazywanych teĝ klasami typów. Cechy typów to abstrakcyjne interfejsy wykorzystujÈce parametry typu, które moĝna implementowaÊ przy uĝyciu dowolnych typów. Przykïadowo moĝemy zdefiniowaÊ cechÚ BinaryFormat[T]. NastÚpnie moĝna zaimplementowaÊ jÈ dla danego typu, definiujÈc w ten sposób jego serializacjÚ do postaci binarnej. Oto przykïad takiego interfejsu: trait BinaryFormat[T] { def asBinary(entity: T) : Array[Byte] } Cecha BinaryFormat definiuje jednÈ metodÚ, asBinary. Pobiera ona instancjÚ typu zgodnego z parametrem typu i zwraca tablicÚ bajtów reprezentujÈcÈ przekazany para- metr. Kod, który ma za zadanie przeprowadziÊ serializacjÚ i zapisaÊ obiekt na dysku, mo- ĝe odszukaÊ cechÚ typu BinaryFormat za poĂrednictwem mechanizmu domniemañ. Mo- ĝemy dodaÊ implementacjÚ dla naszego typu Foo, stosujÈc sïowo implicit w obiekcie towarzyszÈcym Foo: trait Foo {} object Foo { implicit lazy val binaryFormat = new BinaryFormat[Foo] { def asBinary(entity: Foo) = zserializowaneFoo .getBytes } } 118 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu Cecha Foo jest pusta. Jej obiekt towarzyszÈcy ma skïadowÈ implicit val przechowujÈcÈ im- plementacjÚ BinaryFormat. Teraz gdy kod wymagajÈcy BinaryFormat widzi typ Foo, moĝe w sposób domniemany odszukaÊ BinaryFormat. Szczegóïy tego mechanizmu i tej techniki projektowania zostanÈ dokïadniej omówione w podrozdziale 7.2. Domniemane wyszukiwanie na bazie parametrów typu i cech pozwala na uzyskanie eleganckiego kodu. Inny sposób dostarczania i odnajdywania argumentów domniema- nych jest oparty na typach zagnieĝdĝonych. ZAKRES DOMNIEMANY POPRZEZ TYPY ZAGNIEĩDĩONE Zakres domniemany obejmuje takĝe obiekty towarzyszÈce z zakresów zewnÚtrznych, jeĂli typ zostaï zdefiniowany w zakresie zagnieĝdĝonym. Pozwala nam to na stworzenie zestawu podrÚcznych zmiennych domniemanych dla typu w zakresie zewnÚtrznym. Oto przykïad: scala object Foo { | trait Bar | implicit def newBar = new Bar { | override def toString = Implicit Bar | } | } defined module Foo scala implicitly[Foo.Bar] res0: Foo.Bar = Implicit Bar Obiekt Foo to typ zewnÚtrzny. WewnÈtrz niego zdefiniowana zostaïa cecha Bar. Obiekt Foo dodatkowo zawiera opisanÈ jako implicit metodÚ tworzÈcÈ instancjÚ cechy Bar. Po wywoïaniu implicitly[Foo.Bar] wartoĂÊ domniemana zostanie odnaleziona w ze- wnÚtrznej klasie Foo. Ta technika jest bardzo podobna do umieszczania skïadowych do- mniemanych bezpoĂrednio w obiekcie towarzyszÈcym. Definiowanie domniemanych skïadowych dla typów zagnieĝdĝonych przydaje siÚ, gdy zakres zewnÚtrzny ma kilka podtypów. TechnikÚ tÚ moĝemy stosowaÊ, jeĂli nie jest moĝliwe utworzenie zmiennej domniemanej w obiekcie towarzyszÈcym. Obiekty towarzyszÈce w Scali nie mogÈ byÊ oznaczane jako implicit. Domniemane encje zwiÈzane z typem obiektu, jeĂli majÈ byÊ dostÚpne w zakresie domniemanym, muszÈ byÊ dostarczone w zakresie zewnÚtrznym. Oto przykïad: scala object Foo { | object Bar { override def toString = Bar } | implicit def b : Bar.type = Bar | } defined module Foo scala implicitly[Foo.Bar.type] res1: Foo.Bar.type = Bar Obiekt Bar jest zagnieĝdĝony wewnÈtrz obiektu Foo. Obiekt Foo definiuje domniemanÈ skïadowÈ zwracajÈcÈ Bar.type. DziÚki takiej definicji wywoïanie implicitly[Foo. ´Bar.type] zwróci obiekt Bar. W ten sposób jest moĝliwe definiowanie domniema- nych obiektów. 5.2. Wzmacnianie klas za pomocÈ domniemanych widoków 119 Kolejny przypadek zagnieĝdĝania, który moĝe zdziwiÊ osoby nieprzyzwyczajone do niego, to obiekty pakietowe. PoczÈwszy od wersji 2.8, obiekty mogÈ byÊ definiowane jako obiekty pakietowe. Obiekt pakietowy to obiekt zdefiniowany z uĝyciem sïowa kluczowego package. Konwencja w Scali nakazuje umieszczaÊ wszystkie obiekty pakietowe w pliku o nazwie package.scala w katalogu odpowiadajÈcym nazwie pakietu. Kaĝda klasa zdefiniowana wewnÈtrz pakietu jest w nim zagnieĝdĝona. Wszelkie encje domniemane zdefiniowane w obiekcie pakietowym bÚdÈ dostÚpne w zakresie domnie- manym wszystkich typów zdefiniowanych wewnÈtrz pakietu. DziÚki temu moĝna skïa- dowaÊ wartoĂci domniemane w wygodnej lokalizacji, bez potrzeby tworzenia obiektów towarzyszÈcych dla kaĝdego typu w pakiecie. Pokazuje to nastÚpujÈcy przykïad: package object foo { implicit def foo = new Foo } package foo { class Foo { override def toString = FOO! } } Obiekt pakietowy foo zawiera jedno pole implicit, zwracajÈce nowÈ instancjÚ klasy Foo. NastÚpnie definiujemy klasÚ Foo wewnÈtrz pakietu foo. W Scali pakiety mogÈ byÊ de- finiowane w wielu plikach, które w koñcu zostanÈ zagregowane i utworzÈ jeden kom- pletny pakiet. Jeden pakiet moĝe mieÊ tylko jeden obiekt pakietowy, niezaleĝnie od tego, na ile plików zostaï podzielony pakiet. Klasa Foo przesïania metodÚ toString — jej im- plementacja wypisuje ïañcuch FOO! . Skompilujmy pakiet foo i przetestujmy go w REPL: scala implicitly[foo.Foo] res0: foo.Foo = FOO! Nie musieliĂmy importowaÊ obiektu pakietowego ani jego skïadowych. Kompilator sam odnalazï domniemanÈ wartoĂÊ obiektu foo.Foo. W Scali czÚsto moĝna natknÈÊ siÚ na ze- staw definicji domniemanych encji wewnÈtrz obiektu pakietowego danej biblioteki. Z re- guïy obiekt pakietowy zawiera takĝe domniemane widoki, sïuĝÈce do konwersji typów. 5.2. Wzmacnianie klas za pomocą domniemanych widoków Domniemany widok to automatyczna konwersja z jednego typu na drugi w celu speïnie- nia warunków stawianych przez wyraĝenie. Definicja domniemanego widoku ma nastÚ- pujÈcÈ ogólnÈ postaÊ: implicit def nazwaKonwersji ( nazwaArgumentu : TypOryginalny) : TypWidoku Powyĝsza konwersja w sposób domniemany przeksztaïca wartoĂÊ typu TypOryginalny na wartoĂÊ typu TypWidoku, jeĂli jest dostÚpna w zakresie domniemanym. Przeanalizujmy prosty przykïad, w którym podejmiemy próbÚ konwersji zmiennej typu caïkowitego na ïañcuch znaków: scala def foo(msg : String) = println(msg) foo: (msg: String)Unit scala foo(5) 120 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu console :7: error: type mismatch; found : Int(5) required: String foo(5) Metoda foo pobiera wartoĂÊ String i wypisuje jÈ w konsoli. Wywoïanie foo z uĝyciem wartoĂci 5 koñczy siÚ bïÚdem, poniewaĝ typy nie sÈ zgodne. Domniemany widok jest w stanie umoĝliwiÊ to wywoïanie: scala implicit def intToString(x : Int) = x.toString intToString: (x: Int)java.lang.String scala foo(5) 5 Metoda intToString zostaïa zdefiniowana jako implicit. Pobiera ona wartoĂÊ typu Int i zwraca String. Metoda ta jest domniemanym widokiem, czÚsto opisywanym jako Int = String. Teraz gdy wywoïamy metodÚ foo z wartoĂciÈ 5, wypisze ona ïañcuch 5 . Kompilator wykryje, ĝe typy nie sÈ zgodne, a takĝe ĝe istnieje widok, który moĝe roz- wiÈzaÊ problem. Domniemane widoki wykorzystuje siÚ w dwóch sytuacjach: Q Wyraĝenie nie pasuje do typu oczekiwanego przez kompilator. Wtedy kompilator poszuka domniemanego widoku, który pozwoli przeksztaïciÊ wartoĂÊ do oczekiwanej postaci. Przykïad to przekazanie zmiennej typu Int do funkcji oczekujÈcej wartoĂci String. Wówczas jest wymagane, by w zakresie istniaï domniemany widok String = Int. Q Uĝyta zostaïa selekcja e.t, przy czym typ e nie ma skïadowej t. Kompilator wyszuka domniemany widok, który zastosuje do e i którego typ zwracany zawiera skïadowÈ t. Dla przykïadu, jeĂli spróbujemy wywoïaÊ metodÚ foo na wartoĂci String, kompilator wyszuka domniemany widok, który umoĝliwi kompilacjÚ wyraĝenia. Wyraĝenie foo .foo() wymagaïoby domniemanego widoku wyglÈdajÈcego mniej wiÚcej tak: implicit def stringToFoo(x : String) = new { def foo() : Unit = println( foo ) } Domniemane widoki wykorzystujÈ ten sam zakres domniemany co domniemane para- metry. Jednak gdy kompilator sprawdza moĝliwoĂci przeksztaïcenia typu, wyszukuje wedïug typu ěródïowego, a nie docelowego. Przykïad: scala object test { | trait Foo | trait Bar | object Foo { | implicit def fooToBar(foo : Foo) = new Bar {} | } | } defined module test scala import test._ import test._ 5.2. Wzmacnianie klas za pomocÈ domniemanych widoków 121 Obiekt test jest kontenerem, który pozwala nam na utworzenie obiektu towarzyszÈcego w ramach sesji REPL. Zawiera on cechy Foo oraz Bar, a takĝe obiekt towarzyszÈcy Foo. Obiekt towarzyszÈcy Foo obejmuje domniemany widok przeksztaïcajÈcy Foo na Bar. Pa- miÚtaj, ĝe gdy kompilator szuka domniemanych widoków, to typ ěródïowy definiuje domniemany zakres. Oznacza to, ĝe domniemane widoki zdefiniowane w obiekcie towa- rzyszÈcym Foo zostanÈ przeszukane tylko podczas próby konwersji z Foo na inny typ. Na potrzeby testów zdefiniujmy metodÚ oczekujÈcÈ typu Bar: scala def bar(x : Bar) = println( bar ) bar: (x: test.Bar)Unit Metoda bar pobiera obiekt Bar i wypisuje ïañcuch znaków bar . Spróbujmy wywoïaÊ jÈ z argumentem typu Foo i zobaczmy, co siÚ stanie: scala val x = new Foo {} x: java.lang.Object with test.Foo = $anon$1@15e565bd scala bar(x) bar WartoĂÊ x jest typu Foo. Wyraĝenie bar(x) zmusza kompilator do wyszukania do- mniemanego widoku. Poniewaĝ typ zmiennej x to Foo, kompilator szuka wĂród typów powiÈzanych z Foo. Wreszcie znajduje widok fooToBar i dodaje odpowiedniÈ transforma- cjÚ, dziÚki czemu kompilacja koñczy siÚ sukcesem. Mechanizm domniemanych konwersji pozwala nam na dopasowywanie do siebie róĝnych bibliotek, a takĝe na dodawanie do istniejÈcych typów naszych wïasnych metod pomocniczych. Adaptacja bibliotek Javy do postaci, w której dobrze wspóïpracujÈ z bi- bliotekÈ standardowÈ Scali, to dosyÊ czÚsta praktyka. Dla przykïadu biblioteka standar- dowa definiuje moduï scala.collection.JavaConversions, usprawniajÈcy wspóïpracÚ bibliotek do obsïugi kolekcji w obu jÚzykach. Moduï ten jest zestawem domniemanych widoków, które moĝna zaimportowaÊ do zakresu bieĝÈcego w celu umoĝliwienia domnie- manych konwersji pomiÚdzy kolekcjami w Javie i w Scali, przez co moĝliwe staje siÚ takĝe „dodawanie” metod do kolekcji Javy. Adaptacja bibliotek Javy czy wszelkich innych ze- wnÚtrznych bibliotek za pomocÈ domniemanych widoków to popularna praktyka w Scali. Przeanalizujmy odpowiedni przykïad. BÚdziemy chcieli opakowaÊ pakiet java.security tak, by korzystanie z niego w Scali byïo wygodniejsze. Chodzi nam zwïaszcza o uproszczenie zadania uruchamiania uprzy- wilejowanego kodu za pomocÈ java.security.AccessController. Klasa AccessController (kontroler dostÚpu) zawiera statycznÈ metodÚ doPrivileged (wykonaj uprzywilejowane), która pozwala na uruchamianie kodu w uprzywilejowanym stanie uprawnieñ. Metoda doPrivileged ma dwa warianty. Pierwszy przyznaje kodowi uprawnienia z bieĝÈcego kontekstu, drugi pobiera obiekt AccessControlContext (kontekst kontroli dostÚpu), w któ- rym sÈ zdefiniowane uprawnienia do przyznania. Metoda doPrivileged pobiera argu- ment typu PrivilegedExceptionAction (uprzywilejowana akcja), który jest cechÈ defi- niujÈcÈ jednÈ metodÚ: run (uruchom). Cecha ta przypomina cechÚ Scali Function0, a my chcielibyĂmy móc uĝyÊ funkcji anonimowej podczas wywoïywania metody doPrivileged. Stwórzmy domniemany widok przeksztaïcajÈcy typ Function0 do postaci metody doPrivileged: 122 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu object ScalaSecurityImplicits { implicit def functionToPrivilegedAction[A](func : Function0[A]) = new PrivilegedAction[A] { override def run() = func() } } ZdefiniowaliĂmy obiekt ScalaSecurityImplicits zawierajÈcy widok domniemany. Widok functionToPrivilegedAction pobiera Function0 i zwraca nowy obiekt Privileged ´Action, którego metoda run wywoïuje funkcjÚ. Skorzystajmy z tego widoku: scala import ScalaSecurityImplicits._ import ScalaSecurityImplicits._ scala AccessController.doPrivileged( () = | println( Ma przywileje )) Ma przywileje Pierwsza instrukcja importuje domniemany widok do zakresu. NastÚpnie wywoïujemy metodÚ doPrivileged, przekazujÈc jej anonimowÈ funkcjÚ () = println( Ma przywileje ). Po raz kolejny kompilator wykrywa, ĝe funkcja anonimowa nie pasuje do oczekiwanego typu. Rozpoczyna wówczas wyszukiwanie i odnajduje domniemany widok zdefiniowany w ScalaSecurityImplicits. TÚ samÈ technikÚ moĝna wykorzystaÊ do opakowywania obiektów Javy w obiekty Scali. CzÚsto pisze siÚ klasy opakowujÈce istniejÈce biblioteki Javy tak, by korzystaïy z bar- dziej zaawansowanych konstrukcji Scali. Domniemane konwersje Scali moĝna zastoso- waÊ w celu przeksztaïcania typu oryginalnego w opakowany i z powrotem. Dla przy- kïadu dodajmy kilka wygodnych metod do klasy java.io.File. Zaczniemy od wprowadzenia specjalnej notacji — operator / bÚdzie tworzyï nowe pliki w danym katalogu. Stwórzmy klasÚ opakowujÈcÈ, która wprowadzi ten operator: class FileWrapper(val file: java.io.File) { def /(next : String) = new FileWrapper(new java.io.File(file, next)) override def toString = file.getCanonicalPath } Klasa FileWrapper w konstruktorze pobiera obiekt java.io.File. Definiuje ona nowÈ metodÚ /, która pobiera String i zwraca nowy obiekt FileWrapper. Nowy obiekt jest powiÈzany z plikiem o nazwie przekazanej metodzie /, wewnÈtrz katalogu zwiÈzanego z oryginalnym plikiem. Na przykïad jeĂli oryginalny FileWrapper o nazwie file byï zwiÈ- zany z katalogiem /tmp, to wyraĝenie file / mylog.txt zwróci nowy obiekt FileWrapper powiÈzany z plikiem /tmp/mylog.txt. Chcemy skorzystaÊ z domniemanych widoków do automatycznej konwersji pomiÚdzy java.io.File i FileWrapper. Zacznijmy od dodania domniemanego widoku do obiektu towarzyszÈcego FileWrapper: object FileWrapper { implicit def wrap(file : java.io.File) = new FileWrapper(file) } Obiekt towarzyszÈcy FileWrapper definiuje jednÈ metodÚ, wrap, pobierajÈcÈ java.io.File i zwracajÈcÈ FileWrapper. Przetestujmy go teraz w sesji REPL: scala import FileWrapper.wrap import FileWrapper.wrap 5.2. Wzmacnianie klas za pomocÈ domniemanych widoków 123 scala val cur = new java.io.File( . ) cur: java.io.File = . scala cur / temp.txt res0: FileWrapper = .../temp.txt Pierwsza linia to import domniemanego widoku do naszego zakresu. Druga linia tworzy nowy obiekt java.io.File, przekazujÈc konstruktorowi parametr . . Ostatnia linia to wywoïanie metody / na zmiennej cur typu java.io.File. Kompilator nie znajdzie tej me- tody w java.io.File, spróbuje wiÚc wyszukaÊ odpowiedni widok domniemany, umoĝ- liwiajÈcy kompilacjÚ. Po znalezieniu metody wrap kompilator opakuje java.io.File w FileWrapper i wywoïa metodÚ /. W wyniku tego zostanie zwrócony obiekt FileWrapper. Przedstawiony tu mechanizm stanowi doskonaïy sposób dodawania metod do istnie- jÈcych klas Javy czy teĝ do klas z róĝnych zewnÚtrznych bibliotek. Tworzenie obiektu opakowujÈcego moĝe wpïynÈÊ na wydajnoĂÊ, jednak optymalizator HotSpot ma szansÚ na zminimalizowanie tego problemu. PiszÚ „ma szansÚ”, poniewaĝ nie mamy gwarancji, ĝe usunie on alokacjÚ obiektu opakowujÈcego, jednak kilka niewielkich testów potwier- dziïo, ĝe to robi. Jak zwykle lepiej jest przeprowadziÊ profilowanie aplikacji w celu wykrycia problematycznych fragmentów, niĝ zakïadaÊ, ĝe optymalizator zrobi wszystko za nas. Z metodÈ / wiÈĝe siÚ pewien problem. Zwraca ona nowy obiekt FileWrapper. Oznacza to, ĝe nie moĝemy przekazaÊ jej wyniku bezpoĂrednio do metody oczekujÈcej zwykïego obiektu java.io.File. MoglibyĂmy zmieniÊ jÈ, by zwracaïa java.io.File, ale Scala oferuje jeszcze inne rozwiÈzanie. Gdy przekaĝemy FileWrapper do metody oczeku- jÈcej java.io.File, kompilator rozpocznie wyszukiwanie odpowiedniego widoku. Jak juĝ wspomniaïem, przeszukany zostanie takĝe obiekt towarzyszÈcy typowi FileWrapper. Dodajmy do niego domniemany widok unwrap (rozpakuj) i zobaczmy, czy zadziaïa: object FileWrapper { implicit def wrap(file : java.io.File) = new FileWrapper(file) implicit def unwrap(wrapper : FileWrapper) = wrapper.file } Obiekt towarzyszÈcy FileWrapper ma teraz dwie metody: wrap i unwrap. Metoda unwrap pobiera instancjÚ FileWrapper i zwraca odpakowany typ java.io.File. Przetestujmy jÈ teraz w REPL. scala import test.FileWrapper.wrap import test.FileWrapper.wrap scala val cur = new java.io.File( . ) cur: java.io.File = . scala def useFile(file : java.io.File) = println(file.getCanonicalPath) useFile: (file: java.io.File)Unit scala useFile(cur / temp.txt ) /home/jsuereth/projects/book/scala-in-depth/chapter5/wrappers/temp.txt Pierwsza linia importuje widok domniemany wrap. NastÚpna konstruuje obiekt java.io.File wskazujÈcy na bieĝÈcy katalog. Trzecia linia definiuje metodÚ useFile. Metoda ta oczekuje wejĂcia typu java.io.File, którego ĂcieĝkÚ wypisze w konsoli. 124 ROZDZIAà 5. Domniemane wartoĂci i widoki podstawÈ ekspresywnego kodu Ostatnia linia to wywoïanie metody useFile z argumentem w postaci wyraĝenia cur / temp.txt . Kompilator, jak zwykle, na widok metody / rozpocznie wyszukiwanie odpo- wiedniego widoku domniemanego do przeprowadzenia konwersji. Wynikiem konwersji bÚdzie FileWrapper, ale metoda useFile oczekuje typu java.io.File. Kompilator prze- prowadzi kolejne wyszukiwanie za pomocÈ typu Function1[java.io.File, FileWrapper]. W ten sposób znajdzie widok domniemany unwrap w obiekcie towarzyszÈcym FileWrapper. Wszystkie typy siÚ zgadzajÈ, zatem kompilacja koñczy siÚ sukcesem. W czasie wykonania pojawi siÚ oczekiwana wartoĂÊ typu String. ZwróÊ uwagÚ na to, ĝe do wywoïania widoku unwrap nie jest nam potrzebna instrukcja import, wymagana w przypadku metody wrap. Jest tak dlatego, ĝe obiekt wrap byï po- trzebny w sytuacji, gdy kompilator nie znaï typu wymaganego do speïnienia wyraĝenia cur / temp.txt , dlatego sp
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Scala od podszewki
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: